Skip to content

Commit

Permalink
Add support for serializing PythonRequest to a canonical string
Browse files Browse the repository at this point in the history
  • Loading branch information
zanieb committed Jul 9, 2024
1 parent dc80fdb commit 821ded2
Showing 1 changed file with 94 additions and 0 deletions.
94 changes: 94 additions & 0 deletions crates/uv-python/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,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`
if value.eq_ignore_ascii_case("any") {
return Self::Any;
}

// e.g. `3.12.1`, `312`, or `>=3.12`
if let Ok(version) = VersionRequest::from_str(value) {
return Self::Version(version);
Expand Down Expand Up @@ -1150,6 +1155,24 @@ impl PythonRequest {
pub(crate) fn is_explicit_system(&self) -> bool {
matches!(self, Self::File(_) | Self::Directory(_))
}

/// Serialize the request to a canonical representation.
///
/// [`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::Any => "any".to_string(),
Self::Version(version) => version.to_string(),
Self::Directory(path) => path.display().to_string(),
Self::File(path) => path.display().to_string(),
Self::ExecutableName(name) => name.clone(),
Self::Implementation(implementation) => implementation.to_string(),
Self::ImplementationVersion(implementation, version) => {
format!("{implementation}@{version}")
}
Self::Key(request) => request.to_string(),
}
}
}

impl PythonPreference {
Expand Down Expand Up @@ -1581,6 +1604,7 @@ mod tests {

#[test]
fn interpreter_request_from_str() {
assert_eq!(PythonRequest::parse("any"), PythonRequest::Any);
assert_eq!(
PythonRequest::parse("3.12"),
PythonRequest::Version(VersionRequest::from_str("3.12").unwrap())
Expand Down Expand Up @@ -1680,6 +1704,76 @@ mod tests {
);
}

#[test]
fn interpreter_request_to_canonical_string() {
assert_eq!(PythonRequest::Any.to_canonical_string(), "any");
assert_eq!(
PythonRequest::Version(VersionRequest::from_str("3.12").unwrap()).to_canonical_string(),
"3.12"
);
assert_eq!(
PythonRequest::Version(VersionRequest::from_str(">=3.12").unwrap())
.to_canonical_string(),
">=3.12"
);
assert_eq!(
PythonRequest::Version(VersionRequest::from_str(">=3.12,<3.13").unwrap())
.to_canonical_string(),
">=3.12, <3.13"
);
assert_eq!(
PythonRequest::ExecutableName("foo".to_string()).to_canonical_string(),
"foo"
);
assert_eq!(
PythonRequest::Implementation(ImplementationName::CPython).to_canonical_string(),
"cpython"
);
assert_eq!(
PythonRequest::ImplementationVersion(
ImplementationName::CPython,
VersionRequest::from_str("3.12.2").unwrap()
)
.to_canonical_string(),
"[email protected]"
);
assert_eq!(
PythonRequest::Implementation(ImplementationName::PyPy).to_canonical_string(),
"pypy"
);
assert_eq!(
PythonRequest::ImplementationVersion(
ImplementationName::PyPy,
VersionRequest::from_str("3.10").unwrap()
)
.to_canonical_string(),
"[email protected]"
);

let tempdir = TempDir::new().unwrap();
assert_eq!(
PythonRequest::Directory(tempdir.path().to_path_buf()).to_canonical_string(),
tempdir.path().to_str().unwrap(),
"An existing directory is treated as a directory"
);
assert_eq!(
PythonRequest::File(tempdir.child("foo").path().to_path_buf()).to_canonical_string(),
tempdir.child("foo").path().to_str().unwrap(),
"A path that does not exist is treated as a file"
);
tempdir.child("bar").touch().unwrap();
assert_eq!(
PythonRequest::File(tempdir.child("bar").path().to_path_buf()).to_canonical_string(),
tempdir.child("bar").path().to_str().unwrap(),
"An existing file is treated as a file"
);
assert_eq!(
PythonRequest::File(PathBuf::from_str("./foo").unwrap()).to_canonical_string(),
"./foo",
"A string with a file system separator is treated as a file"
);
}

#[test]
fn version_request_from_str() {
assert_eq!(
Expand Down

0 comments on commit 821ded2

Please sign in to comment.