mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-19 11:15:01 +00:00
Avoid selecting prerelease Python installations without opt-in (#7300)
Similar to our semantics for packages with pre-release versions. We will not use prerelease versions unless there are only prerelease versions available, a specific version is requested, or the prerelease version is found in a reasonable source (active environment, explicit path, etc. but not `PATH`). For example, `uv python install 3.13 && uv run python --version` will no longer use `3.13.0rc2` unless that is the only Python version available, `--python 3.13` is used, or that's the Python version that is present in `.venv`.
This commit is contained in:
parent
c124cda098
commit
f22e5ef69a
3 changed files with 84 additions and 15 deletions
|
@ -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;
|
||||
|
@ -877,19 +877,43 @@ 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()
|
||||
&& !installation.source.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.
|
||||
|
@ -1296,6 +1320,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(_))
|
||||
}
|
||||
|
@ -1320,9 +1355,21 @@ impl PythonRequest {
|
|||
}
|
||||
|
||||
impl PythonSource {
|
||||
pub fn is_managed(&self) -> bool {
|
||||
pub fn is_managed(self) -> bool {
|
||||
matches!(self, Self::Managed)
|
||||
}
|
||||
|
||||
/// Whether a pre-release Python installation from the source should be used without opt-in.
|
||||
pub(crate) fn allows_prereleases(self) -> bool {
|
||||
match self {
|
||||
Self::Managed | Self::Registry | Self::SearchPath | Self::MicrosoftStore => false,
|
||||
Self::CondaPrefix
|
||||
| Self::ProvidedPath
|
||||
| Self::ParentInterpreter
|
||||
| Self::ActiveEnvironment
|
||||
| Self::DiscoveredEnvironment => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PythonPreference {
|
||||
|
@ -1589,6 +1636,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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue