Prefer system Python installations over managed ones when `--system` is used (#15061)

This fixes a regression from 0.8.0 from
https://github.com/astral-sh/uv/pull/7934 and follows
https://github.com/astral-sh/uv/pull/15059

The regression is from [this
change](https://github.com/astral-sh/uv/pull/7934/files#diff-c7a660ac39628d5e12f388b0cacc7360affa3d7bb21191184d7ee78489675e83),
which was made because we'd otherwise (with the other changes in that
pull request) _filter out_ managed Python interpreters found in virtual
environments.

When `--system` is used we'll convert the default Python preference of
`managed` to `system` which avoids things like `uv pip install --system`
targeting a managed Python installation.

The basic test is

```
uv python install
uv pip install --system anyio
```

Prior to this change, we'd read a managed interpreter from our managed
installation directory and target that. After this change, without
#15059, we'd read a managed interpreter from the PATH and target that.
Both of those experiences are bad, because the managed interpreters are
marked as externally managed. After this change, with #15059, we
properly target the system interpreter.

Since we use `system` instead of `only-system`, if there is not a system
interpreter we'll still retain our existing behavior and use a managed
interpreter. This should limit breakage from the change. Given the
source of the regression, we could probably use `only-system` here. I
don't feel strongly. I think the main benefit of doing so would be that
we'd omit the check for managed installations in error messages when an
interpreter cannot be found?

We can't really add test coverage here because the test suite always has
externally managed interpreters :)
This commit is contained in:
Zanie Blue 2025-08-04 19:53:59 -05:00 committed by GitHub
parent c77cb2023f
commit 8db61abb50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 50 additions and 17 deletions

View File

@ -5,7 +5,7 @@ use tracing::info;
use uv_cache::{Cache, CacheArgs};
use uv_configuration::{Concurrency, Preview};
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest};
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonPreference, PythonRequest};
#[derive(Parser)]
pub(crate) struct CompileArgs {
@ -25,6 +25,7 @@ pub(crate) async fn compile(args: CompileArgs) -> anyhow::Result<()> {
let interpreter = PythonEnvironment::find(
&PythonRequest::default(),
EnvironmentPreference::OnlyVirtual,
PythonPreference::default(),
&cache,
Preview::default(),
)?

View File

@ -2093,6 +2093,30 @@ impl PythonPreference {
Self::Managed | Self::System | Self::OnlyManaged => true,
}
}
/// Returns a new preference when the `--system` flag is used.
///
/// This will convert [`PythonPreference::Managed`] to [`PythonPreference::System`] when system
/// is set.
#[must_use]
pub fn with_system_flag(self, system: bool) -> Self {
match self {
// TODO(zanieb): It's not clear if we want to allow `--system` to override
// `--managed-python`. We should probably make this `from_system_flag` and refactor
// handling of the `PythonPreference` to use an `Option` so we can tell if the user
// provided it?
Self::OnlyManaged => self,
Self::Managed => {
if system {
Self::System
} else {
self
}
}
Self::System => self,
Self::OnlySystem => self,
}
}
}
impl PythonDownloads {

View File

@ -152,16 +152,13 @@ impl PythonEnvironment {
pub fn find(
request: &PythonRequest,
preference: EnvironmentPreference,
python_preference: PythonPreference,
cache: &Cache,
preview: Preview,
) -> Result<Self, Error> {
let installation = match find_python_installation(
request,
preference,
PythonPreference::default(),
cache,
preview,
)? {
let installation =
match find_python_installation(request, preference, python_preference, cache, preview)?
{
Ok(installation) => installation,
Err(err) => return Err(EnvironmentNotFound::from(err).into()),
};

View File

@ -8,6 +8,7 @@ use uv_cache::Cache;
use uv_configuration::Preview;
use uv_distribution_types::{Diagnostic, InstalledDist};
use uv_installer::{SitePackages, SitePackagesDiagnostic};
use uv_python::PythonPreference;
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest};
use crate::commands::pip::operations::report_target_environment;
@ -28,6 +29,7 @@ pub(crate) fn pip_check(
let environment = PythonEnvironment::find(
&python.map(PythonRequest::parse).unwrap_or_default(),
EnvironmentPreference::from_system_flag(system, false),
PythonPreference::default().with_system_flag(system),
cache,
preview,
)?;

View File

@ -284,6 +284,7 @@ pub(crate) async fn pip_compile(
// Find an interpreter to use for building distributions
let environment_preference = EnvironmentPreference::from_system_flag(system, false);
let python_preference = python_preference.with_system_flag(system);
let interpreter = if let Some(python) = python.as_ref() {
let request = PythonRequest::parse(python);
PythonInstallation::find(

View File

@ -9,6 +9,7 @@ use uv_cache::Cache;
use uv_configuration::Preview;
use uv_distribution_types::{Diagnostic, InstalledDist, Name};
use uv_installer::SitePackages;
use uv_python::PythonPreference;
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest};
use crate::commands::ExitStatus;
@ -30,6 +31,7 @@ pub(crate) fn pip_freeze(
let environment = PythonEnvironment::find(
&python.map(PythonRequest::parse).unwrap_or_default(),
EnvironmentPreference::from_system_flag(system, false),
PythonPreference::default().with_system_flag(system),
cache,
preview,
)?;

View File

@ -194,7 +194,7 @@ pub(crate) async fn pip_install(
.map(PythonRequest::parse)
.unwrap_or_default(),
EnvironmentPreference::from_system_flag(system, false),
python_preference,
python_preference.with_system_flag(system),
&cache,
preview,
)?;
@ -207,6 +207,7 @@ pub(crate) async fn pip_install(
.map(PythonRequest::parse)
.unwrap_or_default(),
EnvironmentPreference::from_system_flag(system, true),
PythonPreference::default().with_system_flag(system),
&cache,
preview,
)?;

View File

@ -25,7 +25,7 @@ use uv_installer::SitePackages;
use uv_normalize::PackageName;
use uv_pep440::Version;
use uv_python::PythonRequest;
use uv_python::{EnvironmentPreference, PythonEnvironment};
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonPreference};
use uv_resolver::{ExcludeNewer, PrereleaseMode};
use crate::commands::ExitStatus;
@ -65,6 +65,7 @@ pub(crate) async fn pip_list(
let environment = PythonEnvironment::find(
&python.map(PythonRequest::parse).unwrap_or_default(),
EnvironmentPreference::from_system_flag(system, false),
PythonPreference::default().with_system_flag(system),
cache,
preview,
)?;

View File

@ -13,7 +13,7 @@ use uv_fs::Simplified;
use uv_install_wheel::read_record_file;
use uv_installer::SitePackages;
use uv_normalize::PackageName;
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest};
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonPreference, PythonRequest};
use crate::commands::ExitStatus;
use crate::commands::pip::operations::report_target_environment;
@ -47,6 +47,7 @@ pub(crate) fn pip_show(
let environment = PythonEnvironment::find(
&python.map(PythonRequest::parse).unwrap_or_default(),
EnvironmentPreference::from_system_flag(system, false),
PythonPreference::default().with_system_flag(system),
cache,
preview,
)?;

View File

@ -171,7 +171,7 @@ pub(crate) async fn pip_sync(
.map(PythonRequest::parse)
.unwrap_or_default(),
EnvironmentPreference::from_system_flag(system, false),
python_preference,
python_preference.with_system_flag(system),
&cache,
preview,
)?;
@ -184,6 +184,7 @@ pub(crate) async fn pip_sync(
.map(PythonRequest::parse)
.unwrap_or_default(),
EnvironmentPreference::from_system_flag(system, true),
PythonPreference::default().with_system_flag(system),
&cache,
preview,
)?;

View File

@ -20,7 +20,7 @@ use uv_normalize::PackageName;
use uv_pep440::Version;
use uv_pep508::{Requirement, VersionOrUrl};
use uv_pypi_types::{ResolutionMetadata, ResolverMarkerEnvironment, VerbatimParsedUrl};
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest};
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonPreference, PythonRequest};
use uv_resolver::{ExcludeNewer, PrereleaseMode};
use crate::commands::ExitStatus;
@ -58,6 +58,7 @@ pub(crate) async fn pip_tree(
let environment = PythonEnvironment::find(
&python.map(PythonRequest::parse).unwrap_or_default(),
EnvironmentPreference::from_system_flag(system, false),
PythonPreference::default().with_system_flag(system),
cache,
preview,
)?;

View File

@ -13,8 +13,8 @@ use uv_distribution_types::{InstalledMetadata, Name, UnresolvedRequirement};
use uv_fs::Simplified;
use uv_pep508::UnnamedRequirement;
use uv_pypi_types::VerbatimParsedUrl;
use uv_python::EnvironmentPreference;
use uv_python::PythonRequest;
use uv_python::{EnvironmentPreference, PythonPreference};
use uv_python::{Prefix, PythonEnvironment, Target};
use uv_requirements::{RequirementsSource, RequirementsSpecification};
@ -58,6 +58,7 @@ pub(crate) async fn pip_uninstall(
.map(PythonRequest::parse)
.unwrap_or_default(),
EnvironmentPreference::from_system_flag(system, true),
PythonPreference::default().with_system_flag(system),
&cache,
preview,
)?;