From 84d6a913ac4d9067f2f2b85755f8cf16a7f5df49 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 9 Oct 2025 11:39:26 -0500 Subject: [PATCH] Ignore pre-release Python versions when a patch version is requested (#16210) I think this is ostensively breaking, though I think the impact would be small given this will go out in 0.9.1 and should only affect people using pre-release Python versions. When `3.14.0` is requested (opposed to `3.14`), we treat this as a request for a final / stable version and ignore pre-releases. I think this is a fairly clean way to allow users to explicitly request the stable version. Closes https://github.com/astral-sh/uv/issues/16175 Follows #16208 --- crates/uv-python/src/discovery.rs | 6 ++++ crates/uv/tests/it/python_find.rs | 47 +++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index d741a3e4e..04bc874b1 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -2690,6 +2690,9 @@ impl VersionRequest { interpreter.python_minor(), interpreter.python_patch(), ) == (*major, *minor, *patch) + // When a patch version is included, we treat it as a request for a stable + // release + && interpreter.python_version().pre().is_none() && variant.matches_interpreter(interpreter) } Self::Range(specifiers, variant) => { @@ -2814,6 +2817,9 @@ impl VersionRequest { } Self::MajorMinorPatch(self_major, self_minor, self_patch, _) => { (*self_major, *self_minor, *self_patch) == (major, minor, patch) + // When a patch version is included, we treat it as a request for a stable + // release + && prerelease.is_none() } Self::Range(specifiers, _) => specifiers.contains( &Version::new([u64::from(major), u64::from(minor), u64::from(patch)]) diff --git a/crates/uv/tests/it/python_find.rs b/crates/uv/tests/it/python_find.rs index 29e56d005..efe52e509 100644 --- a/crates/uv/tests/it/python_find.rs +++ b/crates/uv/tests/it/python_find.rs @@ -1433,3 +1433,50 @@ fn python_find_prerelease_version_specifiers() { ----- stderr ----- "); } + +#[test] +fn python_find_prerelease_with_patch_request() { + let context: TestContext = TestContext::new_with_versions(&[]) + .with_filtered_python_keys() + .with_filtered_python_sources() + .with_managed_python_dirs() + .with_python_download_cache() + .with_filtered_python_install_bin() + .with_filtered_python_names() + .with_filtered_exe_suffix(); + + // Install 3.14.0rc3 + context.python_install().arg("3.14.0rc3").assert().success(); + + // When no `.0` patch version is included, we'll allow selection of a pre-release + uv_snapshot!(context.filters(), context.python_find().arg("3.14"), @r" + success: true + exit_code: 0 + ----- stdout ----- + [TEMP_DIR]/managed/cpython-3.14.0rc3-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + + ----- stderr ----- + "); + + // When `.0` is explicitly included, we will require a stable release + uv_snapshot!(context.filters(), context.python_find().arg("3.14.0"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for Python 3.14.0 in [PYTHON SOURCES] + "); + + // Install 3.14.0 stable + context.python_install().arg("3.14.0").assert().success(); + + uv_snapshot!(context.filters(), context.python_find().arg("3.14.0"), @r" + success: true + exit_code: 0 + ----- stdout ----- + [TEMP_DIR]/managed/cpython-3.14.0-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + + ----- stderr ----- + "); +}