mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-24 13:43:45 +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
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -1405,7 +1405,7 @@ jobs:
|
|||
run: echo $(which python3.13)
|
||||
|
||||
- name: "Validate global Python install"
|
||||
run: python3.13 scripts/check_system_python.py --uv ./uv
|
||||
run: python3.13 scripts/check_system_python.py --uv ./uv --python 3.13
|
||||
|
||||
system-test-conda:
|
||||
timeout-minutes: 10
|
||||
|
|
|
@ -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,20 +877,44 @@ 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 {
|
||||
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 {
|
||||
|
|
|
@ -189,6 +189,17 @@ a system Python version, uv will use the first compatible version — not the ne
|
|||
If a Python version cannot be found on the system, uv will check for a compatible managed Python
|
||||
version download.
|
||||
|
||||
### Python pre-releases
|
||||
|
||||
Python pre-releases will not be selected by default. Python pre-releases will be used if there is no
|
||||
other available installation matching the request. For example, if only a pre-release version is
|
||||
available it will be used but otherwise a stable release version will be used. Similarly, if the
|
||||
path to a pre-release Python executable is provided then no other Python version matches the request
|
||||
and the pre-release version will be used.
|
||||
|
||||
If a pre-release Python version is available and matches the request, uv will not download a stable
|
||||
Python version instead.
|
||||
|
||||
## Disabling automatic Python downloads
|
||||
|
||||
By default, uv will automatically download Python versions when needed.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue