Use an enum for free-threaded Python requests (#7871)

Follow-up to #7431 improving readability
This commit is contained in:
Zanie Blue 2024-10-02 13:10:45 -05:00 committed by GitHub
parent 64c74ac552
commit 891c91dd3a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 150 additions and 97 deletions

View file

@ -132,6 +132,13 @@ pub enum EnvironmentPreference {
Any,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PythonVariant {
#[default]
Default,
Freethreaded,
}
/// A Python discovery version request.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum VersionRequest {
@ -140,11 +147,11 @@ pub enum VersionRequest {
Default,
/// Allow any Python version.
Any,
Major(u8, bool),
MajorMinor(u8, u8, bool),
MajorMinorPatch(u8, u8, u8, bool),
MajorMinorPrerelease(u8, u8, Prerelease, bool),
Range(VersionSpecifiers, bool),
Major(u8, PythonVariant),
MajorMinor(u8, u8, PythonVariant),
MajorMinorPatch(u8, u8, u8, PythonVariant),
MajorMinorPrerelease(u8, u8, Prerelease, PythonVariant),
Range(VersionSpecifiers, PythonVariant),
}
/// The result of an Python installation search.
@ -1540,7 +1547,7 @@ pub(crate) struct ExecutableName {
minor: Option<u8>,
patch: Option<u8>,
prerelease: Option<Prerelease>,
free_threaded: bool,
variant: PythonVariant,
}
impl ExecutableName {
@ -1575,8 +1582,8 @@ impl ExecutableName {
}
#[must_use]
fn with_free_threaded(mut self, free_threaded: bool) -> Self {
self.free_threaded = free_threaded;
fn with_variant(mut self, variant: PythonVariant) -> Self {
self.variant = variant;
self
}
}
@ -1589,7 +1596,7 @@ impl Default for ExecutableName {
minor: None,
patch: None,
prerelease: None,
free_threaded: false,
variant: PythonVariant::Default,
}
}
}
@ -1609,9 +1616,12 @@ impl std::fmt::Display for ExecutableName {
if let Some(prerelease) = &self.prerelease {
write!(f, "{prerelease}")?;
}
if self.free_threaded {
f.write_str("t")?;
}
match self.variant {
PythonVariant::Default => {}
PythonVariant::Freethreaded => {
f.write_str("t")?;
}
};
f.write_str(std::env::consts::EXE_SUFFIX)?;
Ok(())
}
@ -1691,9 +1701,9 @@ impl VersionRequest {
}
// Include free-threaded variants
if self.is_free_threaded_requested() {
if self.is_freethreaded() {
for i in 0..names.len() {
let name = names[i].with_free_threaded(true);
let name = names[i].with_variant(PythonVariant::Freethreaded);
names.push(name);
}
}
@ -1766,7 +1776,7 @@ impl VersionRequest {
Self::Range(_, _) => (),
}
if self.is_free_threaded_requested() {
if self.is_freethreaded() {
if let Self::MajorMinor(major, minor, _) = self.clone().without_patch() {
if (major, minor) < (3, 13) {
return Err(format!(
@ -1781,7 +1791,7 @@ impl VersionRequest {
/// Check if a interpreter matches the requested Python version.
pub(crate) fn matches_interpreter(&self, interpreter: &Interpreter) -> bool {
if self.is_free_threaded_requested() && !interpreter.gil_disabled() {
if self.is_freethreaded() && !interpreter.gil_disabled() {
return false;
}
match self {
@ -1895,15 +1905,13 @@ impl VersionRequest {
match self {
Self::Default => Self::Default,
Self::Any => Self::Any,
Self::Major(major, free_threaded) => Self::Major(major, free_threaded),
Self::MajorMinor(major, minor, free_threaded) => {
Self::MajorMinor(major, minor, free_threaded)
Self::Major(major, variant) => Self::Major(major, variant),
Self::MajorMinor(major, minor, variant) => Self::MajorMinor(major, minor, variant),
Self::MajorMinorPatch(major, minor, _, variant) => {
Self::MajorMinor(major, minor, variant)
}
Self::MajorMinorPatch(major, minor, _, free_threaded) => {
Self::MajorMinor(major, minor, free_threaded)
}
Self::MajorMinorPrerelease(major, minor, prerelease, free_threaded) => {
Self::MajorMinorPrerelease(major, minor, prerelease, free_threaded)
Self::MajorMinorPrerelease(major, minor, prerelease, variant) => {
Self::MajorMinorPrerelease(major, minor, prerelease, variant)
}
Self::Range(_, _) => self,
}
@ -1922,14 +1930,14 @@ impl VersionRequest {
}
}
pub(crate) fn is_free_threaded_requested(&self) -> bool {
pub(crate) fn is_freethreaded(&self) -> bool {
match self {
Self::Any | Self::Default => false,
Self::Major(_, free_threaded) => *free_threaded,
Self::MajorMinor(_, _, free_threaded) => *free_threaded,
Self::MajorMinorPatch(_, _, _, free_threaded) => *free_threaded,
Self::MajorMinorPrerelease(_, _, _, free_threaded) => *free_threaded,
Self::Range(_, free_threaded) => *free_threaded,
Self::Major(_, variant)
| Self::MajorMinor(_, _, variant)
| Self::MajorMinorPatch(_, _, _, variant)
| Self::MajorMinorPrerelease(_, _, _, variant)
| Self::Range(_, variant) => variant == &PythonVariant::Freethreaded,
}
}
}
@ -1939,15 +1947,19 @@ impl FromStr for VersionRequest {
fn from_str(s: &str) -> Result<Self, Self::Err> {
// Check if the version request is for a free-threaded Python version
let (s, free_threaded) = s.strip_suffix('t').map_or((s, false), |s| (s, true));
let (s, variant) = s
.strip_suffix('t')
.map_or((s, PythonVariant::Default), |s| {
(s, PythonVariant::Freethreaded)
});
if free_threaded && s.ends_with('t') {
if variant == PythonVariant::Freethreaded && s.ends_with('t') {
// More than one trailing "t" is not allowed
return Err(Error::InvalidVersionRequest(format!("{s}t")));
}
let Ok(version) = Version::from_str(s) else {
return parse_version_specifiers_request(s, free_threaded);
return parse_version_specifiers_request(s, variant);
};
// Split the release component if it uses the wheel tag format (e.g., `38`)
@ -1972,19 +1984,16 @@ impl FromStr for VersionRequest {
if prerelease.is_some() {
return Err(Error::InvalidVersionRequest(s.to_string()));
}
Ok(Self::Major(*major, free_threaded))
Ok(Self::Major(*major, variant))
}
// e.g. `3.12` or `312` or `3.13rc1`
[major, minor] => {
if let Some(prerelease) = prerelease {
return Ok(Self::MajorMinorPrerelease(
*major,
*minor,
prerelease,
free_threaded,
*major, *minor, prerelease, variant,
));
}
Ok(Self::MajorMinor(*major, *minor, free_threaded))
Ok(Self::MajorMinor(*major, *minor, variant))
}
// e.g. `3.12.1` or `3.13.0rc1`
[major, minor, patch] => {
@ -1995,27 +2004,27 @@ impl FromStr for VersionRequest {
return Err(Error::InvalidVersionRequest(s.to_string()));
}
return Ok(Self::MajorMinorPrerelease(
*major,
*minor,
prerelease,
free_threaded,
*major, *minor, prerelease, variant,
));
}
Ok(Self::MajorMinorPatch(*major, *minor, *patch, free_threaded))
Ok(Self::MajorMinorPatch(*major, *minor, *patch, variant))
}
_ => Err(Error::InvalidVersionRequest(s.to_string())),
}
}
}
fn parse_version_specifiers_request(s: &str, free_threaded: bool) -> Result<VersionRequest, Error> {
fn parse_version_specifiers_request(
s: &str,
variant: PythonVariant,
) -> Result<VersionRequest, Error> {
let Ok(specifiers) = VersionSpecifiers::from_str(s) else {
return Err(Error::InvalidVersionRequest(s.to_string()));
};
if specifiers.is_empty() {
return Err(Error::InvalidVersionRequest(s.to_string()));
}
Ok(VersionRequest::Range(specifiers, free_threaded))
Ok(VersionRequest::Range(specifiers, variant))
}
impl From<&PythonVersion> for VersionRequest {
@ -2030,20 +2039,22 @@ impl fmt::Display for VersionRequest {
match self {
Self::Any => f.write_str("any"),
Self::Default => f.write_str("default"),
Self::Major(major, false) => write!(f, "{major}"),
Self::Major(major, true) => write!(f, "{major}t"),
Self::MajorMinor(major, minor, false) => write!(f, "{major}.{minor}"),
Self::MajorMinor(major, minor, true) => write!(f, "{major}.{minor}t"),
Self::MajorMinorPatch(major, minor, patch, false) => {
Self::Major(major, PythonVariant::Default) => write!(f, "{major}"),
Self::Major(major, PythonVariant::Freethreaded) => write!(f, "{major}t"),
Self::MajorMinor(major, minor, PythonVariant::Default) => write!(f, "{major}.{minor}"),
Self::MajorMinor(major, minor, PythonVariant::Freethreaded) => {
write!(f, "{major}.{minor}t")
}
Self::MajorMinorPatch(major, minor, patch, PythonVariant::Default) => {
write!(f, "{major}.{minor}.{patch}")
}
Self::MajorMinorPatch(major, minor, patch, true) => {
Self::MajorMinorPatch(major, minor, patch, PythonVariant::Freethreaded) => {
write!(f, "{major}.{minor}.{patch}t")
}
Self::MajorMinorPrerelease(major, minor, prerelease, false) => {
Self::MajorMinorPrerelease(major, minor, prerelease, PythonVariant::Default) => {
write!(f, "{major}.{minor}{prerelease}")
}
Self::MajorMinorPrerelease(major, minor, prerelease, true) => {
Self::MajorMinorPrerelease(major, minor, prerelease, PythonVariant::Freethreaded) => {
write!(f, "{major}.{minor}{prerelease}t")
}
Self::Range(specifiers, _) => write!(f, "{specifiers}"),
@ -2211,7 +2222,7 @@ mod tests {
implementation::ImplementationName,
};
use super::Error;
use super::{Error, PythonVariant};
#[test]
fn interpreter_request_from_str() {
@ -2492,32 +2503,32 @@ mod tests {
fn version_request_from_str() {
assert_eq!(
VersionRequest::from_str("3").unwrap(),
VersionRequest::Major(3, false)
VersionRequest::Major(3, PythonVariant::Default)
);
assert_eq!(
VersionRequest::from_str("3.12").unwrap(),
VersionRequest::MajorMinor(3, 12, false)
VersionRequest::MajorMinor(3, 12, PythonVariant::Default)
);
assert_eq!(
VersionRequest::from_str("3.12.1").unwrap(),
VersionRequest::MajorMinorPatch(3, 12, 1, false)
VersionRequest::MajorMinorPatch(3, 12, 1, PythonVariant::Default)
);
assert!(VersionRequest::from_str("1.foo.1").is_err());
assert_eq!(
VersionRequest::from_str("3").unwrap(),
VersionRequest::Major(3, false)
VersionRequest::Major(3, PythonVariant::Default)
);
assert_eq!(
VersionRequest::from_str("38").unwrap(),
VersionRequest::MajorMinor(3, 8, false)
VersionRequest::MajorMinor(3, 8, PythonVariant::Default)
);
assert_eq!(
VersionRequest::from_str("312").unwrap(),
VersionRequest::MajorMinor(3, 12, false)
VersionRequest::MajorMinor(3, 12, PythonVariant::Default)
);
assert_eq!(
VersionRequest::from_str("3100").unwrap(),
VersionRequest::MajorMinor(3, 100, false)
VersionRequest::MajorMinor(3, 100, PythonVariant::Default)
);
assert_eq!(
VersionRequest::from_str("3.13a1").unwrap(),
@ -2528,7 +2539,7 @@ mod tests {
kind: PrereleaseKind::Alpha,
number: 1
},
false
PythonVariant::Default
)
);
assert_eq!(
@ -2540,7 +2551,7 @@ mod tests {
kind: PrereleaseKind::Beta,
number: 1
},
false
PythonVariant::Default
)
);
assert_eq!(
@ -2552,7 +2563,7 @@ mod tests {
kind: PrereleaseKind::Beta,
number: 2
},
false
PythonVariant::Default
)
);
assert_eq!(
@ -2564,7 +2575,7 @@ mod tests {
kind: PrereleaseKind::Rc,
number: 3
},
false
PythonVariant::Default
)
);
assert!(
@ -2611,27 +2622,36 @@ mod tests {
);
assert_eq!(
VersionRequest::from_str("3t").unwrap(),
VersionRequest::Major(3, true)
VersionRequest::Major(3, PythonVariant::Freethreaded)
);
assert_eq!(
VersionRequest::from_str("313t").unwrap(),
VersionRequest::MajorMinor(3, 13, true)
VersionRequest::MajorMinor(3, 13, PythonVariant::Freethreaded)
);
assert_eq!(
VersionRequest::from_str("3.13t").unwrap(),
VersionRequest::MajorMinor(3, 13, true)
VersionRequest::MajorMinor(3, 13, PythonVariant::Freethreaded)
);
assert_eq!(
VersionRequest::from_str(">=3.13t").unwrap(),
VersionRequest::Range(VersionSpecifiers::from_str(">=3.13").unwrap(), true)
VersionRequest::Range(
VersionSpecifiers::from_str(">=3.13").unwrap(),
PythonVariant::Freethreaded
)
);
assert_eq!(
VersionRequest::from_str(">=3.13").unwrap(),
VersionRequest::Range(VersionSpecifiers::from_str(">=3.13").unwrap(), false)
VersionRequest::Range(
VersionSpecifiers::from_str(">=3.13").unwrap(),
PythonVariant::Default
)
);
assert_eq!(
VersionRequest::from_str(">=3.12,<3.14t").unwrap(),
VersionRequest::Range(VersionSpecifiers::from_str(">=3.12,<3.14").unwrap(), true)
VersionRequest::Range(
VersionSpecifiers::from_str(">=3.12,<3.14").unwrap(),
PythonVariant::Freethreaded
)
);
assert!(matches!(
VersionRequest::from_str("3.13tt"),

View file

@ -264,7 +264,7 @@ impl PythonDownloadRequest {
if !version.matches_major_minor_patch(key.major, key.minor, key.patch) {
return false;
}
if version.is_free_threaded_requested() {
if version.is_freethreaded() {
debug!("Installing managed free-threaded Python is not yet supported");
return false;
}

View file

@ -3,7 +3,7 @@ use thiserror::Error;
pub use crate::discovery::{
find_python_installations, EnvironmentPreference, Error as DiscoveryError, PythonDownloads,
PythonNotFound, PythonPreference, PythonRequest, PythonSource, VersionRequest,
PythonNotFound, PythonPreference, PythonRequest, PythonSource, PythonVariant, VersionRequest,
};
pub use crate::environment::{InvalidEnvironment, InvalidEnvironmentKind, PythonEnvironment};
pub use crate::implementation::ImplementationName;