Skip to content

Commit

Permalink
Avoid selecting prerelease Python installations without opt-in
Browse files Browse the repository at this point in the history
Similar to our sementics for packages with pre-release versions, we will prefer non-prerelease versions unless there are only prerelease versions available.
  • Loading branch information
zanieb committed Sep 11, 2024
1 parent 566f070 commit 553325e
Showing 1 changed file with 56 additions and 13 deletions.
69 changes: 56 additions & 13 deletions crates/uv-python/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use thiserror::Error;
use tracing::{debug, instrument, trace};
use which::{which, which_all};

use pep440_rs::{Version, VersionSpecifiers};
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
use uv_cache::Cache;
use uv_fs::Simplified;
use uv_warnings::warn_user_once;
Expand Down Expand Up @@ -877,19 +877,40 @@ pub(crate) fn find_python_installation(
preference: PythonPreference,
cache: &Cache,
) -> Result<FindPythonResult, Error> {
let mut installations = find_python_installations(request, environments, preference, cache);
if let Some(result) = installations.find(|result| {
// Return the first critical discovery error or result
result.as_ref().err().map_or(true, Error::is_critical)
}) {
result
} else {
Ok(FindPythonResult::Err(PythonNotFound {
request: request.clone(),
environment_preference: environments,
python_preference: preference,
}))
let installations = find_python_installations(request, environments, preference, cache);
let mut first_prerelease = None;
for result in installations {
// Iterate until the first critical error or happy result
if !result.as_ref().err().map_or(true, Error::is_critical) {
continue;
}

// If it's an error, we're done.
let Ok(Ok(ref installation)) = result else {
return result;
};

// If it's a pre-release, and pre-releases aren't allowed skip it but store it for later
if installation.python_version().pre().is_some() && !request.allows_prereleases() {
debug!("Skipping pre-release {}", installation.key());
first_prerelease = Some(installation.clone());
continue;
}

// If we didn't skip it, this is the installation to use
return result;
}

// If we only found pre-releases, they're implicitly allowed and we should return the first one
if let Some(installation) = first_prerelease {
return Ok(Ok(installation));
}

Ok(FindPythonResult::Err(PythonNotFound {
request: request.clone(),
environment_preference: environments,
python_preference: preference,
}))
}

/// Find the best-matching Python installation.
Expand Down Expand Up @@ -1296,6 +1317,17 @@ impl PythonRequest {
}
}

pub(crate) fn allows_prereleases(&self) -> bool {
match self {
Self::Any => false,
Self::Version(version) => version.allows_prereleases(),
Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
Self::Implementation(_) => false,
Self::ImplementationVersion(_, _) => true,
Self::Key(request) => request.allows_prereleases(),
}
}

pub(crate) fn is_explicit_system(&self) -> bool {
matches!(self, Self::File(_) | Self::Directory(_))
}
Expand Down Expand Up @@ -1589,6 +1621,17 @@ impl VersionRequest {
Self::Range(_) => self,
}
}

/// Whether this request should allow selection of pre-release versions.
pub(crate) fn allows_prereleases(&self) -> bool {
match self {
Self::Any => false,
Self::Major(_) => true,
Self::MajorMinor(..) => true,
Self::MajorMinorPatch(..) => true,
Self::Range(specifiers) => specifiers.iter().any(VersionSpecifier::any_prerelease),
}
}
}

impl FromStr for VersionRequest {
Expand Down

0 comments on commit 553325e

Please sign in to comment.