Download versions in `uv python pin` if not found (#13946)

As another follow-up in the vein of
https://github.com/astral-sh/uv/pull/13944, I noticed `uv python pin`
doesn't download Python versions, which is a bit weird because we'll
warn it's not found.
This commit is contained in:
Zanie Blue 2025-06-10 10:56:22 -05:00 committed by GitHub
parent 210b579188
commit f20a25f91f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 64 additions and 7 deletions

View File

@ -7,17 +7,22 @@ use owo_colors::OwoColorize;
use tracing::debug; use tracing::debug;
use uv_cache::Cache; use uv_cache::Cache;
use uv_client::BaseClientBuilder;
use uv_dirs::user_uv_config_dir; use uv_dirs::user_uv_config_dir;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_python::{ use uv_python::{
EnvironmentPreference, PYTHON_VERSION_FILENAME, PythonInstallation, PythonPreference, EnvironmentPreference, PYTHON_VERSION_FILENAME, PythonDownloads, PythonInstallation,
PythonRequest, PythonVersionFile, VersionFileDiscoveryOptions, PythonPreference, PythonRequest, PythonVersionFile, VersionFileDiscoveryOptions,
}; };
use uv_settings::PythonInstallMirrors;
use uv_warnings::warn_user_once; use uv_warnings::warn_user_once;
use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceCache}; 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::printer::Printer;
use crate::settings::NetworkSettings;
/// Pin to a specific Python version. /// Pin to a specific Python version.
#[allow(clippy::fn_params_excessive_bools)] #[allow(clippy::fn_params_excessive_bools)]
@ -26,9 +31,12 @@ pub(crate) async fn pin(
request: Option<String>, request: Option<String>,
resolved: bool, resolved: bool,
python_preference: PythonPreference, python_preference: PythonPreference,
python_downloads: PythonDownloads,
no_project: bool, no_project: bool,
global: bool, global: bool,
rm: bool, rm: bool,
install_mirrors: PythonInstallMirrors,
network_settings: NetworkSettings,
cache: &Cache, cache: &Cache,
printer: Printer, printer: Printer,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
@ -98,12 +106,26 @@ pub(crate) async fn pin(
bail!("Requests for arbitrary names (e.g., `{name}`) are not supported in version files"); bail!("Requests for arbitrary names (e.g., `{name}`) are not supported in version files");
} }
let python = match PythonInstallation::find( let client_builder = BaseClientBuilder::new()
&request, .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, EnvironmentPreference::OnlySystem,
python_preference, python_preference,
python_downloads,
&client_builder,
cache, 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), Ok(python) => Some(python),
// If no matching Python version is found, don't fail unless `resolved` was requested // If no matching Python version is found, don't fail unless `resolved` was requested
Err(uv_python::Error::MissingPython(err)) if !resolved => { Err(uv_python::Error::MissingPython(err)) if !resolved => {

View File

@ -1464,9 +1464,12 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.request, args.request,
args.resolved, args.resolved,
globals.python_preference, globals.python_preference,
globals.python_downloads,
args.no_project, args.no_project,
args.global, args.global,
args.rm, args.rm,
args.install_mirrors,
globals.network_settings,
&cache, &cache,
printer, printer,
) )

View File

@ -1057,12 +1057,13 @@ pub(crate) struct PythonPinSettings {
pub(crate) no_project: bool, pub(crate) no_project: bool,
pub(crate) global: bool, pub(crate) global: bool,
pub(crate) rm: bool, pub(crate) rm: bool,
pub(crate) install_mirrors: PythonInstallMirrors,
} }
impl PythonPinSettings { impl PythonPinSettings {
/// Resolve the [`PythonPinSettings`] from the CLI and workspace configuration. /// Resolve the [`PythonPinSettings`] from the CLI and workspace configuration.
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
pub(crate) fn resolve(args: PythonPinArgs, _filesystem: Option<FilesystemOptions>) -> Self { pub(crate) fn resolve(args: PythonPinArgs, filesystem: Option<FilesystemOptions>) -> Self {
let PythonPinArgs { let PythonPinArgs {
request, request,
no_resolved, no_resolved,
@ -1072,12 +1073,17 @@ impl PythonPinSettings {
rm, rm,
} = args; } = args;
let install_mirrors = filesystem
.map(|fs| fs.install_mirrors.clone())
.unwrap_or_default();
Self { Self {
request, request,
resolved: flag(resolved, no_resolved).unwrap_or(false), resolved: flag(resolved, no_resolved).unwrap_or(false),
no_project, no_project,
global, global,
rm, rm,
install_mirrors,
} }
} }
} }

View File

@ -816,6 +816,32 @@ fn python_pin_with_comments() -> Result<()> {
Ok(()) 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] #[test]
fn python_pin_rm() { fn python_pin_rm() {
let context: TestContext = TestContext::new_with_versions(&["3.12"]); let context: TestContext = TestContext::new_with_versions(&["3.12"]);