From 568cb1c645ac32a3369e8a3d04da46531326c0fc Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Sat, 15 Mar 2025 15:39:00 -0500 Subject: [PATCH 1/2] Prefer stable releases over pre-releases in `uv python install` e.g., `uv python install 3` should not install the 3.14 alpha --- crates/uv-python/src/discovery.rs | 6 +++--- crates/uv-python/src/downloads.rs | 23 +++++++++++++++++---- crates/uv/tests/it/common/mod.rs | 2 +- crates/uv/tests/it/python_install.rs | 31 ++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index b5a4972d69f2..1aa58c20e356 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -2275,9 +2275,9 @@ impl VersionRequest { match self { Self::Default => false, Self::Any => true, - Self::Major(..) => true, - Self::MajorMinor(..) => true, - Self::MajorMinorPatch(..) => true, + Self::Major(..) => false, + Self::MajorMinor(..) => false, + Self::MajorMinorPatch(..) => false, Self::MajorMinorPrerelease(..) => true, Self::Range(specifiers, _) => specifiers.iter().any(VersionSpecifier::any_prerelease), } diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index aa5ca64fd7f8..eca1baa9ac06 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -455,13 +455,28 @@ pub enum DownloadResult { impl ManagedPythonDownload { /// Return the first [`ManagedPythonDownload`] matching a request, if any. + /// + /// If there is no stable version matching the request, a compatible pre-release version will + /// be searched for — even if a pre-release was not explicitly requested. pub fn from_request( request: &PythonDownloadRequest, ) -> Result<&'static ManagedPythonDownload, Error> { - request - .iter_downloads() - .next() - .ok_or(Error::NoDownloadFound(request.clone())) + if let Some(download) = request.iter_downloads().next() { + return Ok(download); + } + + if !request.allows_prereleases() { + if let Some(download) = request + .clone() + .with_prereleases(true) + .iter_downloads() + .next() + { + return Ok(download); + } + } + + Err(Error::NoDownloadFound(request.clone())) } /// Iterate over all [`ManagedPythonDownload`]s. diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 7407cba7c98b..89937d92e616 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -257,7 +257,7 @@ impl TestContext { pub fn with_filtered_python_keys(mut self) -> Self { // Filter platform keys self.filters.push(( - r"((?:cpython|pypy)-\d+\.\d+(?:\.(?:\[X\]|\d+))?[a-z]?(?:\+[a-z]+)?)-[a-z0-9]+-[a-z0-9_]+-[a-z]+" + r"((?:cpython|pypy)-\d+\.\d+(?:\.(?:\[X\]|\d+))?[a-z]?(?:[a-z][0-9])?(?:\+[a-z]+)?)-[a-z0-9]+-[a-z0-9_]+-[a-z]+" .to_string(), "$1-[PLATFORM]".to_string(), )); diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index fbc945e466bf..31169c1aac4f 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -1201,3 +1201,34 @@ fn python_install_patch_dylib() { ----- stderr ----- "###); } + +#[test] +fn python_install_314() { + let context: TestContext = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + // Install 3.14 + // For now, this provides test coverage of pre-release handling + uv_snapshot!(context.filters(), context.python_install().arg("3.14"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.14.0a5 in [TIME] + + cpython-3.14.0a5-[PLATFORM] + "); + + // Install a specific pre-release + uv_snapshot!(context.filters(), context.python_install().arg("3.14.0a4"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.14.0a4 in [TIME] + + cpython-3.14.0a4-[PLATFORM] + "); +} From 64a37233f58758c25428a8a334709742a1d59bf3 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 18 Mar 2025 12:06:24 -0500 Subject: [PATCH 2/2] Add `python find` test case --- crates/uv/tests/it/python_install.rs | 44 ++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 31169c1aac4f..f0de1a71f610 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -1217,8 +1217,8 @@ fn python_install_314() { ----- stdout ----- ----- stderr ----- - Installed Python 3.14.0a5 in [TIME] - + cpython-3.14.0a5-[PLATFORM] + Installed Python 3.14.0a6 in [TIME] + + cpython-3.14.0a6-[PLATFORM] "); // Install a specific pre-release @@ -1231,4 +1231,44 @@ fn python_install_314() { Installed Python 3.14.0a4 in [TIME] + cpython-3.14.0a4-[PLATFORM] "); + + // We should be able to find this version without opt-in, because there is no stable release + // installed + uv_snapshot!(context.filters(), context.python_find().arg("3.14"), @r" + success: true + exit_code: 0 + ----- stdout ----- + [TEMP_DIR]/managed/cpython-3.14.0a6-[PLATFORM]/bin/python3.14 + + ----- stderr ----- + "); + + uv_snapshot!(context.filters(), context.python_find().arg("3"), @r" + success: true + exit_code: 0 + ----- stdout ----- + [TEMP_DIR]/managed/cpython-3.14.0a6-[PLATFORM]/bin/python3.14 + + ----- stderr ----- + "); + + // If we install a stable version, that should be preferred though + uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Installed Python 3.13.2 in [TIME] + + cpython-3.13.2-[PLATFORM] + "); + + uv_snapshot!(context.filters(), context.python_find().arg("3"), @r" + success: true + exit_code: 0 + ----- stdout ----- + [TEMP_DIR]/managed/cpython-3.13.2-[PLATFORM]/bin/python3.13 + + ----- stderr ----- + "); }