From 91410acf82a958f1dbbc3c99a240e543de2d5255 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 18 Apr 2025 11:25:52 -0500 Subject: [PATCH] Fix `PythonDownloadRequest` parsing for partial keys (#12925) In #12909, I noticed we failed to parse partial download keys with `any` placeholders. Here, parsing for that is fixed. --- crates/uv-python/src/discovery.rs | 76 +++++++++++++++++++++++++++++++ crates/uv-python/src/downloads.rs | 52 +++++++++------------ crates/uv/tests/it/python_find.rs | 10 ++++ 3 files changed, 107 insertions(+), 31 deletions(-) diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index 3c301b7c7..0a6d8685b 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -2705,12 +2705,15 @@ mod tests { use std::{path::PathBuf, str::FromStr}; use assert_fs::{prelude::*, TempDir}; + use target_lexicon::{Aarch64Architecture, Architecture}; use test_log::test; use uv_pep440::{Prerelease, PrereleaseKind, VersionSpecifiers}; use crate::{ discovery::{PythonRequest, VersionRequest}, + downloads::PythonDownloadRequest, implementation::ImplementationName, + platform::{Arch, Libc, Os}, }; use super::{Error, PythonVariant}; @@ -2763,6 +2766,7 @@ mod tests { PythonRequest::parse("cpython"), PythonRequest::Implementation(ImplementationName::CPython) ); + assert_eq!( PythonRequest::parse("cpython3.12.2"), PythonRequest::ImplementationVersion( @@ -2770,6 +2774,78 @@ mod tests { VersionRequest::from_str("3.12.2").unwrap(), ) ); + + assert_eq!( + PythonRequest::parse("cpython-3.13.2"), + PythonRequest::Key(PythonDownloadRequest { + version: Some(VersionRequest::MajorMinorPatch( + 3, + 13, + 2, + PythonVariant::Default + )), + implementation: Some(ImplementationName::CPython), + arch: None, + os: None, + libc: None, + prereleases: None + }) + ); + assert_eq!( + PythonRequest::parse("cpython-3.13.2-macos-aarch64-none"), + PythonRequest::Key(PythonDownloadRequest { + version: Some(VersionRequest::MajorMinorPatch( + 3, + 13, + 2, + PythonVariant::Default + )), + implementation: Some(ImplementationName::CPython), + arch: Some(Arch { + family: Architecture::Aarch64(Aarch64Architecture::Aarch64), + variant: None + }), + os: Some(Os(target_lexicon::OperatingSystem::Darwin(None))), + libc: Some(Libc::None), + prereleases: None + }) + ); + assert_eq!( + PythonRequest::parse("any-3.13.2"), + PythonRequest::Key(PythonDownloadRequest { + version: Some(VersionRequest::MajorMinorPatch( + 3, + 13, + 2, + PythonVariant::Default + )), + implementation: None, + arch: None, + os: None, + libc: None, + prereleases: None + }) + ); + assert_eq!( + PythonRequest::parse("any-3.13.2-any-aarch64"), + PythonRequest::Key(PythonDownloadRequest { + version: Some(VersionRequest::MajorMinorPatch( + 3, + 13, + 2, + PythonVariant::Default + )), + implementation: None, + arch: Some(Arch { + family: Architecture::Aarch64(Aarch64Architecture::Aarch64), + variant: None + }), + os: None, + libc: None, + prereleases: None + }) + ); + assert_eq!( PythonRequest::parse("pypy"), PythonRequest::Implementation(ImplementationName::PyPy) diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index c3c52018e..58ec9792c 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -107,15 +107,15 @@ pub struct ManagedPythonDownload { #[derive(Debug, Clone, Default, Eq, PartialEq)] pub struct PythonDownloadRequest { - version: Option, - implementation: Option, - arch: Option, - os: Option, - libc: Option, + pub(crate) version: Option, + pub(crate) implementation: Option, + pub(crate) arch: Option, + pub(crate) os: Option, + pub(crate) libc: Option, /// Whether to allow pre-releases or not. If not set, defaults to true if [`Self::version`] is /// not None, and false otherwise. - prereleases: Option, + pub(crate) prereleases: Option, } impl PythonDownloadRequest { @@ -424,39 +424,29 @@ impl FromStr for PythonDownloadRequest { let mut arch = None; let mut libc = None; + let mut position = 0; loop { // Consume each part let Some(part) = parts.next() else { break }; + position += 1; - if implementation.is_none() { - implementation = Some(ImplementationName::from_str(part)?); + if part.eq_ignore_ascii_case("any") { continue; } - if version.is_none() { - version = Some( - VersionRequest::from_str(part) - .map_err(|_| Error::InvalidPythonVersion(part.to_string()))?, - ); - continue; + match position { + 1 => implementation = Some(ImplementationName::from_str(part)?), + 2 => { + version = Some( + VersionRequest::from_str(part) + .map_err(|_| Error::InvalidPythonVersion(part.to_string()))?, + ); + } + 3 => os = Some(Os::from_str(part)?), + 4 => arch = Some(Arch::from_str(part)?), + 5 => libc = Some(Libc::from_str(part)?), + _ => return Err(Error::TooManyParts(s.to_string())), } - - if os.is_none() { - os = Some(Os::from_str(part)?); - continue; - } - - if arch.is_none() { - arch = Some(Arch::from_str(part)?); - continue; - } - - if libc.is_none() { - libc = Some(Libc::from_str(part)?); - continue; - } - - return Err(Error::TooManyParts(s.to_string())); } Ok(Self::new(version, implementation, arch, os, libc, None)) } diff --git a/crates/uv/tests/it/python_find.rs b/crates/uv/tests/it/python_find.rs index 90fabed2c..923133fe3 100644 --- a/crates/uv/tests/it/python_find.rs +++ b/crates/uv/tests/it/python_find.rs @@ -82,6 +82,16 @@ fn python_find() { ----- stderr ----- "###); + // Request Python 3.12 via partial key syntax with placeholders + uv_snapshot!(context.filters(), context.python_find().arg("any-3.12-any"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + [PYTHON-3.12] + + ----- stderr ----- + "###); + // Request CPython 3.12 for the current platform let os = Os::from_env(); let arch = Arch::from_env();