mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-24 13:43:45 +00:00
Add new PythonRequest::Any
and VersionRequest::Any
variants (#7517)
Follow-up to https://github.com/astral-sh/uv/pull/7514 and #7526 adding the `Any` variants again with slightly different semantics (i.e., they allow pre-releases) We'll be able to use this in https://github.com/astral-sh/uv/pull/7508 to avoid querying a bunch of alternative interpreters unnecessarily in the non-list case.
This commit is contained in:
parent
0f9a2e3870
commit
09a2ebca8b
5 changed files with 66 additions and 24 deletions
|
@ -36,12 +36,14 @@ use crate::{Interpreter, PythonVersion};
|
|||
/// See [`PythonRequest::from_str`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub enum PythonRequest {
|
||||
/// Use an appropriate default Python installation
|
||||
/// An appropriate default Python installation
|
||||
///
|
||||
/// This may skip some Python installations, such as pre-release versions or alternative
|
||||
/// implementations.
|
||||
#[default]
|
||||
Default,
|
||||
/// Any Python installation
|
||||
Any,
|
||||
/// A Python version without an implementation name e.g. `3.10` or `>=3.12,<3.13`
|
||||
Version(VersionRequest),
|
||||
/// A path to a directory containing a Python installation, e.g. `.venv`
|
||||
|
@ -137,6 +139,8 @@ pub enum VersionRequest {
|
|||
/// Allow an appropriate default Python version.
|
||||
#[default]
|
||||
Default,
|
||||
/// Allow any Python version.
|
||||
Any,
|
||||
Major(u8),
|
||||
MajorMinor(u8, u8),
|
||||
MajorMinorPatch(u8, u8, u8),
|
||||
|
@ -488,7 +492,10 @@ fn find_all_minor(
|
|||
dir: &Path,
|
||||
) -> impl Iterator<Item = PathBuf> {
|
||||
match version_request {
|
||||
VersionRequest::Default | VersionRequest::Major(_) | VersionRequest::Range(_) => {
|
||||
&VersionRequest::Any
|
||||
| VersionRequest::Default
|
||||
| VersionRequest::Major(_)
|
||||
| VersionRequest::Range(_) => {
|
||||
let regex = if let Some(implementation) = implementation {
|
||||
Regex::new(&format!(
|
||||
r"^({}|python3)\.(?<minor>\d\d?){}$",
|
||||
|
@ -780,9 +787,31 @@ pub fn find_python_installations<'a>(
|
|||
))))
|
||||
}
|
||||
}
|
||||
PythonRequest::Any => Box::new({
|
||||
debug!("Searching for any Python interpreter in {preference}");
|
||||
python_interpreters(
|
||||
Some(&VersionRequest::Any),
|
||||
None,
|
||||
environments,
|
||||
preference,
|
||||
cache,
|
||||
)
|
||||
.map(|result| {
|
||||
result
|
||||
.map(PythonInstallation::from_tuple)
|
||||
.map(FindPythonResult::Ok)
|
||||
})
|
||||
}),
|
||||
PythonRequest::Default => Box::new({
|
||||
debug!("Searching for Python interpreter in {preference}");
|
||||
python_interpreters(None, None, environments, preference, cache).map(|result| {
|
||||
debug!("Searching for default Python interpreter in {preference}");
|
||||
python_interpreters(
|
||||
Some(&VersionRequest::Default),
|
||||
None,
|
||||
environments,
|
||||
preference,
|
||||
cache,
|
||||
)
|
||||
.map(|result| {
|
||||
result
|
||||
.map(PythonInstallation::from_tuple)
|
||||
.map(FindPythonResult::Ok)
|
||||
|
@ -979,7 +1008,7 @@ pub fn find_best_python_installation(
|
|||
}
|
||||
|
||||
// If a Python version was requested but cannot be fulfilled, just take any version
|
||||
debug!("Looking for Python installation with any version");
|
||||
debug!("Looking for a default Python installation");
|
||||
let request = PythonRequest::Default;
|
||||
Ok(
|
||||
find_python_installation(&request, environments, preference, cache)?.map_err(|err| {
|
||||
|
@ -1144,8 +1173,11 @@ impl PythonRequest {
|
|||
///
|
||||
/// This cannot fail, which means weird inputs will be parsed as [`PythonRequest::File`] or [`PythonRequest::ExecutableName`].
|
||||
pub fn parse(value: &str) -> Self {
|
||||
// e.g. `any`
|
||||
// Literals, e.g. `any` or `default`
|
||||
if value.eq_ignore_ascii_case("any") {
|
||||
return Self::Any;
|
||||
}
|
||||
if value.eq_ignore_ascii_case("default") {
|
||||
return Self::Default;
|
||||
}
|
||||
|
||||
|
@ -1250,7 +1282,7 @@ impl PythonRequest {
|
|||
}
|
||||
|
||||
match self {
|
||||
PythonRequest::Default => true,
|
||||
PythonRequest::Default | PythonRequest::Any => true,
|
||||
PythonRequest::Version(version_request) => {
|
||||
version_request.matches_interpreter(interpreter)
|
||||
}
|
||||
|
@ -1335,6 +1367,7 @@ impl PythonRequest {
|
|||
pub(crate) fn allows_prereleases(&self) -> bool {
|
||||
match self {
|
||||
Self::Default => false,
|
||||
Self::Any => true,
|
||||
Self::Version(version) => version.allows_prereleases(),
|
||||
Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
|
||||
Self::Implementation(_) => false,
|
||||
|
@ -1352,7 +1385,8 @@ impl PythonRequest {
|
|||
/// [`Self::parse`] should always return the same request when given the output of this method.
|
||||
pub fn to_canonical_string(&self) -> String {
|
||||
match self {
|
||||
Self::Default => "any".to_string(),
|
||||
Self::Any => "any".to_string(),
|
||||
Self::Default => "default".to_string(),
|
||||
Self::Version(version) => version.to_string(),
|
||||
Self::Directory(path) => path.display().to_string(),
|
||||
Self::File(path) => path.display().to_string(),
|
||||
|
@ -1453,7 +1487,7 @@ impl VersionRequest {
|
|||
};
|
||||
|
||||
match self {
|
||||
Self::Default | Self::Range(_) => [Some(python3), Some(python), None, None],
|
||||
Self::Any | Self::Default | Self::Range(_) => [Some(python3), Some(python), None, None],
|
||||
Self::Major(major) => [
|
||||
Some(Cow::Owned(format!("python{major}{extension}"))),
|
||||
Some(python),
|
||||
|
@ -1504,7 +1538,9 @@ impl VersionRequest {
|
|||
};
|
||||
|
||||
match self {
|
||||
Self::Default | Self::Range(_) => [Some(python3), Some(python), None, None],
|
||||
Self::Any | Self::Default | Self::Range(_) => {
|
||||
[Some(python3), Some(python), None, None]
|
||||
}
|
||||
Self::Major(major) => [
|
||||
Some(Cow::Owned(format!("{name}{major}{extension}"))),
|
||||
Some(python),
|
||||
|
@ -1541,7 +1577,7 @@ impl VersionRequest {
|
|||
|
||||
pub(crate) fn check_supported(&self) -> Result<(), String> {
|
||||
match self {
|
||||
Self::Default => (),
|
||||
Self::Any | Self::Default => (),
|
||||
Self::Major(major) => {
|
||||
if *major < 3 {
|
||||
return Err(format!(
|
||||
|
@ -1580,7 +1616,7 @@ impl VersionRequest {
|
|||
/// Check if a interpreter matches the requested Python version.
|
||||
pub(crate) fn matches_interpreter(&self, interpreter: &Interpreter) -> bool {
|
||||
match self {
|
||||
Self::Default => true,
|
||||
Self::Any | Self::Default => true,
|
||||
Self::Major(major) => interpreter.python_major() == *major,
|
||||
Self::MajorMinor(major, minor) => {
|
||||
(interpreter.python_major(), interpreter.python_minor()) == (*major, *minor)
|
||||
|
@ -1612,7 +1648,7 @@ impl VersionRequest {
|
|||
|
||||
pub(crate) fn matches_version(&self, version: &PythonVersion) -> bool {
|
||||
match self {
|
||||
Self::Default => true,
|
||||
Self::Any | Self::Default => true,
|
||||
Self::Major(major) => version.major() == *major,
|
||||
Self::MajorMinor(major, minor) => {
|
||||
(version.major(), version.minor()) == (*major, *minor)
|
||||
|
@ -1631,7 +1667,7 @@ impl VersionRequest {
|
|||
|
||||
fn matches_major_minor(&self, major: u8, minor: u8) -> bool {
|
||||
match self {
|
||||
Self::Default => true,
|
||||
Self::Any | Self::Default => true,
|
||||
Self::Major(self_major) => *self_major == major,
|
||||
Self::MajorMinor(self_major, self_minor) => {
|
||||
(*self_major, *self_minor) == (major, minor)
|
||||
|
@ -1650,7 +1686,7 @@ impl VersionRequest {
|
|||
|
||||
pub(crate) fn matches_major_minor_patch(&self, major: u8, minor: u8, patch: u8) -> bool {
|
||||
match self {
|
||||
Self::Default => true,
|
||||
Self::Any | Self::Default => true,
|
||||
Self::Major(self_major) => *self_major == major,
|
||||
Self::MajorMinor(self_major, self_minor) => {
|
||||
(*self_major, *self_minor) == (major, minor)
|
||||
|
@ -1673,7 +1709,7 @@ impl VersionRequest {
|
|||
/// Return true if a patch version is present in the request.
|
||||
fn has_patch(&self) -> bool {
|
||||
match self {
|
||||
Self::Default => false,
|
||||
Self::Any | Self::Default => false,
|
||||
Self::Major(..) => false,
|
||||
Self::MajorMinor(..) => false,
|
||||
Self::MajorMinorPatch(..) => true,
|
||||
|
@ -1689,6 +1725,7 @@ impl VersionRequest {
|
|||
fn without_patch(self) -> Self {
|
||||
match self {
|
||||
Self::Default => Self::Default,
|
||||
Self::Any => Self::Any,
|
||||
Self::Major(major) => Self::Major(major),
|
||||
Self::MajorMinor(major, minor) => Self::MajorMinor(major, minor),
|
||||
Self::MajorMinorPatch(major, minor, _) => Self::MajorMinor(major, minor),
|
||||
|
@ -1703,6 +1740,7 @@ impl VersionRequest {
|
|||
pub(crate) fn allows_prereleases(&self) -> bool {
|
||||
match self {
|
||||
Self::Default => false,
|
||||
Self::Any => true,
|
||||
Self::Major(_) => true,
|
||||
Self::MajorMinor(..) => true,
|
||||
Self::MajorMinorPatch(..) => true,
|
||||
|
@ -1788,6 +1826,7 @@ impl From<&PythonVersion> for VersionRequest {
|
|||
impl fmt::Display for VersionRequest {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Any => f.write_str("any"),
|
||||
Self::Default => f.write_str("default"),
|
||||
Self::Major(major) => write!(f, "{major}"),
|
||||
Self::MajorMinor(major, minor) => write!(f, "{major}.{minor}"),
|
||||
|
@ -1805,7 +1844,8 @@ impl fmt::Display for VersionRequest {
|
|||
impl fmt::Display for PythonRequest {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => write!(f, "any Python"),
|
||||
Self::Default => write!(f, "a default Python"),
|
||||
Self::Any => write!(f, "any Python"),
|
||||
Self::Version(version) => write!(f, "Python {version}"),
|
||||
Self::Directory(path) => write!(f, "directory `{}`", path.user_display()),
|
||||
Self::File(path) => write!(f, "path `{}`", path.user_display()),
|
||||
|
@ -1887,7 +1927,7 @@ impl fmt::Display for PythonNotFound {
|
|||
};
|
||||
|
||||
match self.request {
|
||||
PythonRequest::Default => {
|
||||
PythonRequest::Default | PythonRequest::Any => {
|
||||
write!(f, "No interpreter found in {sources}")
|
||||
}
|
||||
_ => {
|
||||
|
@ -1965,7 +2005,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn interpreter_request_from_str() {
|
||||
assert_eq!(PythonRequest::parse("any"), PythonRequest::Default);
|
||||
assert_eq!(PythonRequest::parse("any"), PythonRequest::Any);
|
||||
assert_eq!(PythonRequest::parse("default"), PythonRequest::Default);
|
||||
assert_eq!(
|
||||
PythonRequest::parse("3.12"),
|
||||
PythonRequest::Version(VersionRequest::from_str("3.12").unwrap())
|
||||
|
@ -2127,7 +2168,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn interpreter_request_to_canonical_string() {
|
||||
assert_eq!(PythonRequest::Default.to_canonical_string(), "any");
|
||||
assert_eq!(PythonRequest::Default.to_canonical_string(), "default");
|
||||
assert_eq!(PythonRequest::Any.to_canonical_string(), "any");
|
||||
assert_eq!(
|
||||
PythonRequest::Version(VersionRequest::from_str("3.12").unwrap()).to_canonical_string(),
|
||||
"3.12"
|
||||
|
|
|
@ -173,7 +173,7 @@ impl PythonDownloadRequest {
|
|||
.with_version(version.clone()),
|
||||
),
|
||||
PythonRequest::Key(request) => Some(request.clone()),
|
||||
PythonRequest::Default => Some(Self::default()),
|
||||
PythonRequest::Default | PythonRequest::Any => Some(Self::default()),
|
||||
// We can't download a managed installation for these request kinds
|
||||
PythonRequest::Directory(_)
|
||||
| PythonRequest::ExecutableName(_)
|
||||
|
|
|
@ -80,7 +80,7 @@ impl fmt::Display for EnvironmentNotFound {
|
|||
EnvironmentPreference::OnlyVirtual => SearchType::Virtual,
|
||||
};
|
||||
|
||||
if matches!(self.request, PythonRequest::Default) {
|
||||
if matches!(self.request, PythonRequest::Default | PythonRequest::Any) {
|
||||
write!(f, "No {search_type} found")?;
|
||||
} else {
|
||||
write!(f, "No {search_type} found for {}", self.request)?;
|
||||
|
|
|
@ -305,7 +305,7 @@ impl ManagedPythonInstallation {
|
|||
pub fn satisfies(&self, request: &PythonRequest) -> bool {
|
||||
match request {
|
||||
PythonRequest::File(path) => self.executable() == *path,
|
||||
PythonRequest::Default => true,
|
||||
PythonRequest::Default | PythonRequest::Any => true,
|
||||
PythonRequest::Directory(path) => self.path() == *path,
|
||||
PythonRequest::ExecutableName(name) => self
|
||||
.executable()
|
||||
|
|
|
@ -66,7 +66,7 @@ pub(crate) async fn list(
|
|||
};
|
||||
|
||||
let installed = find_python_installations(
|
||||
&PythonRequest::Default,
|
||||
&PythonRequest::Any,
|
||||
EnvironmentPreference::OnlySystem,
|
||||
python_preference,
|
||||
cache,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue