Skip to content

Commit 04291df

Browse files
committed
Prefer stable releases over pre-releases in uv python install
e.g., `uv python install 3` should not install the 3.14 alpha
1 parent 4f70d14 commit 04291df

File tree

4 files changed

+54
-8
lines changed

4 files changed

+54
-8
lines changed

crates/uv-python/src/discovery.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -2275,9 +2275,9 @@ impl VersionRequest {
22752275
match self {
22762276
Self::Default => false,
22772277
Self::Any => true,
2278-
Self::Major(..) => true,
2279-
Self::MajorMinor(..) => true,
2280-
Self::MajorMinorPatch(..) => true,
2278+
Self::Major(..) => false,
2279+
Self::MajorMinor(..) => false,
2280+
Self::MajorMinorPatch(..) => false,
22812281
Self::MajorMinorPrerelease(..) => true,
22822282
Self::Range(specifiers, _) => specifiers.iter().any(VersionSpecifier::any_prerelease),
22832283
}

crates/uv-python/src/downloads.rs

+19-4
Original file line numberDiff line numberDiff line change
@@ -455,13 +455,28 @@ pub enum DownloadResult {
455455

456456
impl ManagedPythonDownload {
457457
/// Return the first [`ManagedPythonDownload`] matching a request, if any.
458+
///
459+
/// If there is no stable version matching the request, a compatible pre-release version will
460+
/// be searched for — even if a pre-release was not explicitly requested.
458461
pub fn from_request(
459462
request: &PythonDownloadRequest,
460463
) -> Result<&'static ManagedPythonDownload, Error> {
461-
request
462-
.iter_downloads()
463-
.next()
464-
.ok_or(Error::NoDownloadFound(request.clone()))
464+
if let Some(download) = request.iter_downloads().next() {
465+
return Ok(download);
466+
}
467+
468+
if !request.allows_prereleases() {
469+
if let Some(download) = request
470+
.clone()
471+
.with_prereleases(true)
472+
.iter_downloads()
473+
.next()
474+
{
475+
return Ok(download);
476+
}
477+
}
478+
479+
Err(Error::NoDownloadFound(request.clone()))
465480
}
466481

467482
/// Iterate over all [`ManagedPythonDownload`]s.

crates/uv/tests/it/common/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ impl TestContext {
256256
pub fn with_filtered_python_keys(mut self) -> Self {
257257
// Filter platform keys
258258
self.filters.push((
259-
r"((?:cpython|pypy)-\d+\.\d+(?:\.(?:\[X\]|\d+))?[a-z]?(?:\+[a-z]+)?)-[a-z0-9]+-[a-z0-9_]+-[a-z]+"
259+
r"((?:cpython|pypy)-\d+\.\d+(?:\.(?:\[X\]|\d+(?:[a-z][0-9])?)?[a-z]?(?:\+[a-z]+)?))-[a-z0-9]+-[a-z0-9_]+-[a-z]+"
260260
.to_string(),
261261
"$1-[PLATFORM]".to_string(),
262262
));

crates/uv/tests/it/python_install.rs

+31
Original file line numberDiff line numberDiff line change
@@ -1201,3 +1201,34 @@ fn python_install_patch_dylib() {
12011201
----- stderr -----
12021202
"###);
12031203
}
1204+
1205+
#[test]
1206+
fn python_install_314() {
1207+
let context: TestContext = TestContext::new_with_versions(&[])
1208+
.with_filtered_python_keys()
1209+
.with_filtered_exe_suffix()
1210+
.with_managed_python_dirs();
1211+
1212+
// Install 3.14
1213+
// For now, this provides test coverage of pre-release handling
1214+
uv_snapshot!(context.filters(), context.python_install().arg("3.14"), @r"
1215+
success: true
1216+
exit_code: 0
1217+
----- stdout -----
1218+
1219+
----- stderr -----
1220+
Installed Python 3.14.0a5 in [TIME]
1221+
+ cpython-3.14.0a5-[PLATFORM]
1222+
");
1223+
1224+
// Install a specific pre-release
1225+
uv_snapshot!(context.filters(), context.python_install().arg("3.14.0a4"), @r"
1226+
success: true
1227+
exit_code: 0
1228+
----- stdout -----
1229+
1230+
----- stderr -----
1231+
Installed Python 3.14.0a4 in [TIME]
1232+
+ cpython-3.14.0a4-[PLATFORM]
1233+
");
1234+
}

0 commit comments

Comments
 (0)