Skip to content

Commit

Permalink
Avoid selecting prerelease Python installations without opt-in (#7300)
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
zanieb committed Sep 11, 2024
1 parent c124cda commit f22e5ef
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 15 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
86 changes: 72 additions & 14 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,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.
Expand Down Expand Up @@ -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(_))
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
11 changes: 11 additions & 0 deletions docs/concepts/python-versions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit f22e5ef

Please sign in to comment.