-
Notifications
You must be signed in to change notification settings - Fork 739
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
} | ||
} | ||
Comment on lines
+1362
to
+1372
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's still failing :/ presumably because there's another Python version available There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at a fix in #7306 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See #7300 (comment) too |
||
} | ||
|
||
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 { | ||
|
There was a problem hiding this comment.
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.