Fix handling of Python requests with pre-releases in ranges

This commit is contained in:
Zanie Blue 2025-10-09 10:29:58 -05:00
parent 24ebdf02c0
commit fb7d5361b0
2 changed files with 122 additions and 2 deletions

View file

@ -2,6 +2,7 @@ use itertools::{Either, Itertools};
use regex::Regex;
use rustc_hash::{FxBuildHasher, FxHashSet};
use same_file::is_same_file;
use std::borrow::Cow;
use std::env::consts::EXE_SUFFIX;
use std::fmt::{self, Debug, Formatter};
use std::{env, io, iter};
@ -2692,7 +2693,16 @@ impl VersionRequest {
&& variant.matches_interpreter(interpreter)
}
Self::Range(specifiers, variant) => {
let version = interpreter.python_version().only_release();
// If the specifier contains pre-releases, use the full version for comparison.
// Otherwise, strip pre-release so that, e.g., `>=3.14` matches `3.14.0rc3`.
let version = if specifiers
.iter()
.any(uv_pep440::VersionSpecifier::any_prerelease)
{
Cow::Borrowed(interpreter.python_version())
} else {
Cow::Owned(interpreter.python_version().only_release())
};
specifiers.contains(&version) && variant.matches_interpreter(interpreter)
}
Self::MajorMinorPrerelease(major, minor, prerelease, variant) => {
@ -2725,7 +2735,19 @@ impl VersionRequest {
(version.major(), version.minor(), version.patch())
== (*major, *minor, Some(*patch))
}
Self::Range(specifiers, _) => specifiers.contains(&version.version.only_release()),
Self::Range(specifiers, _) => {
// If the specifier contains pre-releases, use the full version for comparison.
// Otherwise, strip pre-release so that, e.g., `>=3.14` matches `3.14.0rc3`.
let version = if specifiers
.iter()
.any(uv_pep440::VersionSpecifier::any_prerelease)
{
Cow::Borrowed(&version.version)
} else {
Cow::Owned(version.version.only_release())
};
specifiers.contains(&version)
}
Self::MajorMinorPrerelease(major, minor, prerelease, _) => {
(version.major(), version.minor(), version.pre())
== (*major, *minor, Some(*prerelease))

View file

@ -1335,3 +1335,101 @@ fn python_find_freethreaded_314() {
----- stderr -----
");
}
#[test]
fn python_find_prerelease_version_specifiers() {
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();
context.python_install().arg("3.14.0rc2").assert().success();
context.python_install().arg("3.14.0rc3").assert().success();
// `>=3.14` should allow pre-release versions
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 -----
");
// `>3.14rc2` should not match rc2
uv_snapshot!(context.filters(), context.python_find().arg(">3.14.0rc2"), @r"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0rc3-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
// `>3.14rc3` should not match rc3
uv_snapshot!(context.filters(), context.python_find().arg(">3.14.0rc3"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found for Python >3.14.0rc3 in [PYTHON SOURCES]
");
// `>=3.14.0rc3` should match rc3
uv_snapshot!(context.filters(), context.python_find().arg(">=3.14.0rc3"), @r"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0rc3-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
// `<3.14.0rc3` should match rc2
uv_snapshot!(context.filters(), context.python_find().arg("<3.14.0rc3"), @r"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0rc2-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
// `<=3.14.0rc3` should match rc3
uv_snapshot!(context.filters(), context.python_find().arg("<=3.14.0rc3"), @r"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0rc3-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
// Install the stable version
context.python_install().arg("3.14.0").assert().success();
// `>=3.14` should prefer stable
uv_snapshot!(context.filters(), context.python_find().arg(">=3.14"), @r"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
// `>3.14rc2` should prefer stable
uv_snapshot!(context.filters(), context.python_find().arg(">3.14.0rc2"), @r"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14.0-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
}