mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Validate that discovered interpreters meet the Python preference
This commit is contained in:
parent
cda72b297f
commit
94c125a1ee
5 changed files with 144 additions and 7 deletions
|
@ -661,6 +661,9 @@ fn python_interpreters<'a>(
|
|||
false
|
||||
}
|
||||
})
|
||||
.filter_ok(move |(source, interpreter)| {
|
||||
satisfies_python_preference(*source, interpreter, preference)
|
||||
})
|
||||
}
|
||||
|
||||
/// Lazily convert Python executables into interpreters.
|
||||
|
@ -788,6 +791,93 @@ fn source_satisfies_environment_preference(
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns true if a Python interpreter matches the [`PythonPreference`].
|
||||
pub fn satisfies_python_preference(
|
||||
source: PythonSource,
|
||||
interpreter: &Interpreter,
|
||||
preference: PythonPreference,
|
||||
) -> bool {
|
||||
// If the source is "explicit", we will not apply the Python preference, e.g., if the user has
|
||||
// activated a virtual environment, we should always allow it. We may want to invalidate the
|
||||
// environment in some cases, like in projects, but we can't distinguish between explicit
|
||||
// requests for a different Python preference or a persistent preference in a configuration file
|
||||
// which would result in overly aggressive invalidation.
|
||||
let is_explicit = match source {
|
||||
PythonSource::ProvidedPath
|
||||
| PythonSource::ParentInterpreter
|
||||
| PythonSource::ActiveEnvironment
|
||||
| PythonSource::CondaPrefix => true,
|
||||
PythonSource::Managed
|
||||
| PythonSource::DiscoveredEnvironment
|
||||
| PythonSource::SearchPath
|
||||
| PythonSource::SearchPathFirst
|
||||
| PythonSource::Registry
|
||||
| PythonSource::MicrosoftStore
|
||||
| PythonSource::BaseCondaPrefix => false,
|
||||
};
|
||||
|
||||
match preference {
|
||||
PythonPreference::OnlyManaged => {
|
||||
// Perform a fast check using the source before querying the interpreter
|
||||
if matches!(source, PythonSource::Managed) || interpreter.is_managed() {
|
||||
true
|
||||
} else {
|
||||
if is_explicit {
|
||||
debug!(
|
||||
"Allowing unmanaged Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}",
|
||||
interpreter.sys_executable().display()
|
||||
);
|
||||
true
|
||||
} else {
|
||||
debug!(
|
||||
"Ignoring Python interpreter at `{}`: only managed interpreters allowed",
|
||||
interpreter.sys_executable().display()
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
// If not "only" a kind, any interpreter is okay
|
||||
PythonPreference::Managed | PythonPreference::System => true,
|
||||
PythonPreference::OnlySystem => {
|
||||
let is_system = match source {
|
||||
// A managed interpreter is never a system interpreter
|
||||
PythonSource::Managed => false,
|
||||
// We can't be sure if this is a system interpreter without checking
|
||||
PythonSource::ProvidedPath
|
||||
| PythonSource::ParentInterpreter
|
||||
| PythonSource::ActiveEnvironment
|
||||
| PythonSource::CondaPrefix
|
||||
| PythonSource::DiscoveredEnvironment
|
||||
| PythonSource::SearchPath
|
||||
| PythonSource::SearchPathFirst
|
||||
| PythonSource::Registry
|
||||
| PythonSource::BaseCondaPrefix => !interpreter.is_managed(),
|
||||
// Managed interpreters should never be found in the store
|
||||
PythonSource::MicrosoftStore => true,
|
||||
};
|
||||
|
||||
if is_system {
|
||||
true
|
||||
} else {
|
||||
if is_explicit {
|
||||
debug!(
|
||||
"Allowing managed Python interpreter at `{}` (in conflict with the `python-preference`) since it is from source: {source}",
|
||||
interpreter.sys_executable().display()
|
||||
);
|
||||
true
|
||||
} else {
|
||||
debug!(
|
||||
"Ignoring Python interpreter at `{}`: only system interpreters allowed",
|
||||
interpreter.sys_executable().display()
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if an encountered error is critical and should stop discovery.
|
||||
///
|
||||
/// Returns false when an error could be due to a faulty Python installation and we should continue searching for a working one.
|
||||
|
@ -2576,6 +2666,18 @@ impl PythonPreference {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the canonical name.
|
||||
// TODO(zanieb): This should be a `Display` impl and we should have a different view for
|
||||
// the sources
|
||||
pub fn canonical_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::OnlyManaged => "only managed",
|
||||
Self::Managed => "prefer managed",
|
||||
Self::System => "prefer system",
|
||||
Self::OnlySystem => "only system",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PythonPreference {
|
||||
|
|
|
@ -150,8 +150,7 @@ impl PythonEnvironment {
|
|||
let installation = match find_python_installation(
|
||||
request,
|
||||
preference,
|
||||
// Ignore managed installations when looking for environments
|
||||
PythonPreference::OnlySystem,
|
||||
PythonPreference::default(),
|
||||
cache,
|
||||
)? {
|
||||
Ok(installation) => installation,
|
||||
|
|
|
@ -26,6 +26,7 @@ use uv_platform_tags::{Tags, TagsError};
|
|||
use uv_pypi_types::{ResolverMarkerEnvironment, Scheme};
|
||||
|
||||
use crate::implementation::LenientImplementationName;
|
||||
use crate::managed::ManagedPythonInstallations;
|
||||
use crate::platform::{Arch, Libc, Os};
|
||||
use crate::pointer_size::PointerSize;
|
||||
use crate::{
|
||||
|
@ -263,6 +264,21 @@ impl Interpreter {
|
|||
self.prefix.is_some()
|
||||
}
|
||||
|
||||
/// Returns `true` if this interpreter is managed by uv.
|
||||
///
|
||||
/// Returns `false` if we cannot determine the path of the uv managed Python interpreters.
|
||||
pub fn is_managed(&self) -> bool {
|
||||
let Ok(installations) = ManagedPythonInstallations::from_settings(None) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
installations
|
||||
.find_all()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(|install| install.path() == self.sys_base_prefix)
|
||||
}
|
||||
|
||||
/// Returns `Some` if the environment is externally managed, optionally including an error
|
||||
/// message from the `EXTERNALLY-MANAGED` file.
|
||||
///
|
||||
|
|
|
@ -5,8 +5,9 @@ use thiserror::Error;
|
|||
use uv_static::EnvVars;
|
||||
|
||||
pub use crate::discovery::{
|
||||
find_python_installations, EnvironmentPreference, Error as DiscoveryError, PythonDownloads,
|
||||
PythonNotFound, PythonPreference, PythonRequest, PythonSource, PythonVariant, VersionRequest,
|
||||
find_python_installations, satisfies_python_preference, EnvironmentPreference,
|
||||
Error as DiscoveryError, PythonDownloads, PythonNotFound, PythonPreference, PythonRequest,
|
||||
PythonSource, PythonVariant, VersionRequest,
|
||||
};
|
||||
pub use crate::environment::{InvalidEnvironmentKind, PythonEnvironment};
|
||||
pub use crate::implementation::ImplementationName;
|
||||
|
|
|
@ -29,9 +29,9 @@ use uv_pep440::{Version, VersionSpecifiers};
|
|||
use uv_pep508::MarkerTreeContents;
|
||||
use uv_pypi_types::{ConflictPackage, ConflictSet, Conflicts};
|
||||
use uv_python::{
|
||||
EnvironmentPreference, Interpreter, InvalidEnvironmentKind, PythonDownloads, PythonEnvironment,
|
||||
PythonInstallation, PythonPreference, PythonRequest, PythonVariant, PythonVersionFile,
|
||||
VersionFileDiscoveryOptions, VersionRequest,
|
||||
satisfies_python_preference, EnvironmentPreference, Interpreter, InvalidEnvironmentKind,
|
||||
PythonDownloads, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest,
|
||||
PythonSource, PythonVariant, PythonVersionFile, VersionFileDiscoveryOptions, VersionRequest,
|
||||
};
|
||||
use uv_requirements::upgrade::{read_lock_requirements, LockedRequirements};
|
||||
use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification};
|
||||
|
@ -770,6 +770,7 @@ impl ScriptInterpreter {
|
|||
fn environment_is_usable(
|
||||
environment: &PythonEnvironment,
|
||||
python_request: Option<&PythonRequest>,
|
||||
python_preference: PythonPreference,
|
||||
requires_python: Option<&RequiresPython>,
|
||||
cache: &Cache,
|
||||
) -> bool {
|
||||
|
@ -800,6 +801,23 @@ fn environment_is_usable(
|
|||
}
|
||||
}
|
||||
|
||||
if satisfies_python_preference(
|
||||
PythonSource::DiscoveredEnvironment,
|
||||
environment.interpreter(),
|
||||
python_preference,
|
||||
) {
|
||||
trace!(
|
||||
"The virtual environment's Python interpreter meets the Python preference: `{}`",
|
||||
python_preference
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"The virtual environment's Python interpreter does not meet the Python preference: `{}`",
|
||||
python_preference
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -843,6 +861,7 @@ impl ProjectInterpreter {
|
|||
if environment_is_usable(
|
||||
&venv,
|
||||
python_request.as_ref(),
|
||||
python_preference,
|
||||
requires_python.as_ref(),
|
||||
cache,
|
||||
) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue