Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid selecting prerelease Python installations without opt-in #7300

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A similar change was required on Windows.

uv will now prefer another installation on the system, if it exists. In this case, Python 3.10 is available and breaks our expectations.


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,
}
}
Comment on lines +1362 to +1372
Copy link
Member Author

@zanieb zanieb Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't have this at first and the Python 3.13 system test failed at https://github.com/astral-sh/uv/actions/runs/10817366263/job/30011226637

Copy link
Member Author

@zanieb zanieb Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still failing :/ presumably because there's another Python version available

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at a fix in #7306

Copy link
Member Author

@zanieb zanieb Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #7300 (comment) too

}

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
Loading