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.
This commit is contained in:
Zanie Blue 2025-04-18 11:25:52 -05:00 committed by GitHub
parent 6eb6475afd
commit 91410acf82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 107 additions and 31 deletions

View file

@ -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)

View file

@ -107,15 +107,15 @@ pub struct ManagedPythonDownload {
#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub struct PythonDownloadRequest {
version: Option<VersionRequest>,
implementation: Option<ImplementationName>,
arch: Option<Arch>,
os: Option<Os>,
libc: Option<Libc>,
pub(crate) version: Option<VersionRequest>,
pub(crate) implementation: Option<ImplementationName>,
pub(crate) arch: Option<Arch>,
pub(crate) os: Option<Os>,
pub(crate) libc: Option<Libc>,
/// Whether to allow pre-releases or not. If not set, defaults to true if [`Self::version`] is
/// not None, and false otherwise.
prereleases: Option<bool>,
pub(crate) prereleases: Option<bool>,
}
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))
}

View file

@ -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();