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

Add new PythonRequest::Any and VersionRequest::Any variants #7517

Merged
merged 2 commits into from
Sep 19, 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
82 changes: 62 additions & 20 deletions crates/uv-python/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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?){}$",
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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,
Expand All @@ -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(),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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,
Expand All @@ -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),
Expand All @@ -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,
Expand Down Expand Up @@ -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}"),
Expand All @@ -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()),
Expand Down Expand Up @@ -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}")
}
_ => {
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-python/src/downloads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(_)
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-python/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-python/src/managed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/python/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub(crate) async fn list(
};

let installed = find_python_installations(
&PythonRequest::Default,
&PythonRequest::Any,
EnvironmentPreference::OnlySystem,
python_preference,
cache,
Expand Down
Loading