diff --git a/crates/uv/src/commands/python/pin.rs b/crates/uv/src/commands/python/pin.rs index 8ba2e698c..e0b241bcc 100644 --- a/crates/uv/src/commands/python/pin.rs +++ b/crates/uv/src/commands/python/pin.rs @@ -7,17 +7,22 @@ use owo_colors::OwoColorize; use tracing::debug; use uv_cache::Cache; +use uv_client::BaseClientBuilder; use uv_dirs::user_uv_config_dir; use uv_fs::Simplified; use uv_python::{ - EnvironmentPreference, PYTHON_VERSION_FILENAME, PythonInstallation, PythonPreference, - PythonRequest, PythonVersionFile, VersionFileDiscoveryOptions, + EnvironmentPreference, PYTHON_VERSION_FILENAME, PythonDownloads, PythonInstallation, + PythonPreference, PythonRequest, PythonVersionFile, VersionFileDiscoveryOptions, }; +use uv_settings::PythonInstallMirrors; use uv_warnings::warn_user_once; use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceCache}; -use crate::commands::{ExitStatus, project::find_requires_python}; +use crate::commands::{ + ExitStatus, project::find_requires_python, reporters::PythonDownloadReporter, +}; use crate::printer::Printer; +use crate::settings::NetworkSettings; /// Pin to a specific Python version. #[allow(clippy::fn_params_excessive_bools)] @@ -26,9 +31,12 @@ pub(crate) async fn pin( request: Option, resolved: bool, python_preference: PythonPreference, + python_downloads: PythonDownloads, no_project: bool, global: bool, rm: bool, + install_mirrors: PythonInstallMirrors, + network_settings: NetworkSettings, cache: &Cache, printer: Printer, ) -> Result { @@ -98,12 +106,26 @@ pub(crate) async fn pin( bail!("Requests for arbitrary names (e.g., `{name}`) are not supported in version files"); } - let python = match PythonInstallation::find( - &request, + let client_builder = BaseClientBuilder::new() + .connectivity(network_settings.connectivity) + .native_tls(network_settings.native_tls) + .allow_insecure_host(network_settings.allow_insecure_host.clone()); + let reporter = PythonDownloadReporter::single(printer); + + let python = match PythonInstallation::find_or_download( + Some(&request), EnvironmentPreference::OnlySystem, python_preference, + python_downloads, + &client_builder, cache, - ) { + Some(&reporter), + install_mirrors.python_install_mirror.as_deref(), + install_mirrors.pypy_install_mirror.as_deref(), + install_mirrors.python_downloads_json_url.as_deref(), + ) + .await + { Ok(python) => Some(python), // If no matching Python version is found, don't fail unless `resolved` was requested Err(uv_python::Error::MissingPython(err)) if !resolved => { diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 1d4aa932c..b7b1a7859 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1464,9 +1464,12 @@ async fn run(mut cli: Cli) -> Result { args.request, args.resolved, globals.python_preference, + globals.python_downloads, args.no_project, args.global, args.rm, + args.install_mirrors, + globals.network_settings, &cache, printer, ) diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 38d3d16fb..b5eb2f5d0 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1057,12 +1057,13 @@ pub(crate) struct PythonPinSettings { pub(crate) no_project: bool, pub(crate) global: bool, pub(crate) rm: bool, + pub(crate) install_mirrors: PythonInstallMirrors, } impl PythonPinSettings { /// Resolve the [`PythonPinSettings`] from the CLI and workspace configuration. #[allow(clippy::needless_pass_by_value)] - pub(crate) fn resolve(args: PythonPinArgs, _filesystem: Option) -> Self { + pub(crate) fn resolve(args: PythonPinArgs, filesystem: Option) -> Self { let PythonPinArgs { request, no_resolved, @@ -1072,12 +1073,17 @@ impl PythonPinSettings { rm, } = args; + let install_mirrors = filesystem + .map(|fs| fs.install_mirrors.clone()) + .unwrap_or_default(); + Self { request, resolved: flag(resolved, no_resolved).unwrap_or(false), no_project, global, rm, + install_mirrors, } } } diff --git a/crates/uv/tests/it/python_pin.rs b/crates/uv/tests/it/python_pin.rs index cf220d6b1..4cbc98ab0 100644 --- a/crates/uv/tests/it/python_pin.rs +++ b/crates/uv/tests/it/python_pin.rs @@ -816,6 +816,32 @@ fn python_pin_with_comments() -> Result<()> { Ok(()) } +#[test] +#[cfg(feature = "python-managed")] +fn python_pin_install() { + let context: TestContext = TestContext::new_with_versions(&[]).with_filtered_python_sources(); + + // Should not install 3.12 when downloads are not automatic + uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Pinned `.python-version` to `3.12` + + ----- stderr ----- + warning: No interpreter found for Python 3.12 in [PYTHON SOURCES] + "); + + uv_snapshot!(context.filters(), context.python_pin().arg("3.12").env("UV_PYTHON_DOWNLOADS", "auto"), @r" + success: true + exit_code: 0 + ----- stdout ----- + Pinned `.python-version` to `3.12` + + ----- stderr ----- + "); +} + #[test] fn python_pin_rm() { let context: TestContext = TestContext::new_with_versions(&["3.12"]);