From 8db61abb500838e174dcd53fe5b0c4fce87bcc9b Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 4 Aug 2025 19:53:59 -0500 Subject: [PATCH] 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 :) --- crates/uv-dev/src/compile.rs | 3 ++- crates/uv-python/src/discovery.rs | 24 ++++++++++++++++++++++++ crates/uv-python/src/environment.rs | 17 +++++++---------- crates/uv/src/commands/pip/check.rs | 2 ++ crates/uv/src/commands/pip/compile.rs | 1 + crates/uv/src/commands/pip/freeze.rs | 2 ++ crates/uv/src/commands/pip/install.rs | 3 ++- crates/uv/src/commands/pip/list.rs | 3 ++- crates/uv/src/commands/pip/show.rs | 3 ++- crates/uv/src/commands/pip/sync.rs | 3 ++- crates/uv/src/commands/pip/tree.rs | 3 ++- crates/uv/src/commands/pip/uninstall.rs | 3 ++- 12 files changed, 50 insertions(+), 17 deletions(-) diff --git a/crates/uv-dev/src/compile.rs b/crates/uv-dev/src/compile.rs index 0e1392f63..af0e66339 100644 --- a/crates/uv-dev/src/compile.rs +++ b/crates/uv-dev/src/compile.rs @@ -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(), )? diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index 551258008..ff6c2a7e3 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -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 { diff --git a/crates/uv-python/src/environment.rs b/crates/uv-python/src/environment.rs index f82319074..9c588d8e7 100644 --- a/crates/uv-python/src/environment.rs +++ b/crates/uv-python/src/environment.rs @@ -152,19 +152,16 @@ impl PythonEnvironment { pub fn find( request: &PythonRequest, preference: EnvironmentPreference, + python_preference: PythonPreference, cache: &Cache, preview: Preview, ) -> Result { - let installation = match find_python_installation( - request, - preference, - PythonPreference::default(), - cache, - preview, - )? { - Ok(installation) => installation, - Err(err) => return Err(EnvironmentNotFound::from(err).into()), - }; + let installation = + match find_python_installation(request, preference, python_preference, cache, preview)? + { + Ok(installation) => installation, + Err(err) => return Err(EnvironmentNotFound::from(err).into()), + }; Ok(Self::from_installation(installation)) } diff --git a/crates/uv/src/commands/pip/check.rs b/crates/uv/src/commands/pip/check.rs index cf5ab350b..86c00885d 100644 --- a/crates/uv/src/commands/pip/check.rs +++ b/crates/uv/src/commands/pip/check.rs @@ -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, )?; diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 48bba2532..92ee634eb 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -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( diff --git a/crates/uv/src/commands/pip/freeze.rs b/crates/uv/src/commands/pip/freeze.rs index 6f663e03b..87be582e6 100644 --- a/crates/uv/src/commands/pip/freeze.rs +++ b/crates/uv/src/commands/pip/freeze.rs @@ -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, )?; diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index e5b69dbe9..86ae52273 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -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, )?; diff --git a/crates/uv/src/commands/pip/list.rs b/crates/uv/src/commands/pip/list.rs index fb7f69011..95c1aa372 100644 --- a/crates/uv/src/commands/pip/list.rs +++ b/crates/uv/src/commands/pip/list.rs @@ -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, )?; diff --git a/crates/uv/src/commands/pip/show.rs b/crates/uv/src/commands/pip/show.rs index f7a46ea7a..f3f823496 100644 --- a/crates/uv/src/commands/pip/show.rs +++ b/crates/uv/src/commands/pip/show.rs @@ -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, )?; diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 411d1a0ba..1293c120b 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -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, )?; diff --git a/crates/uv/src/commands/pip/tree.rs b/crates/uv/src/commands/pip/tree.rs index 2d6c8a4f7..a288f7ef7 100644 --- a/crates/uv/src/commands/pip/tree.rs +++ b/crates/uv/src/commands/pip/tree.rs @@ -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, )?; diff --git a/crates/uv/src/commands/pip/uninstall.rs b/crates/uv/src/commands/pip/uninstall.rs index af7dfc8f3..2c2b617a2 100644 --- a/crates/uv/src/commands/pip/uninstall.rs +++ b/crates/uv/src/commands/pip/uninstall.rs @@ -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, )?;