Use comparable representation for `PackageId` (#1543)

## Summary

By using the display representation of `Version` to form a `PackageId`,
we run the risk (as seen in the linked issue) of thinking that versions
like `2021.1` and `2021.1.0` are not equivalent.

Closes https://github.com/astral-sh/uv/issues/1536
This commit is contained in:
Charlie Marsh 2024-02-16 16:30:54 -05:00 committed by GitHub
parent 7c08e61b73
commit b4ea48955b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 10 deletions

View File

@ -1,18 +1,35 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use url::Url;
use pep440_rs::Version;
use uv_normalize::PackageName;
/// A unique identifier for a package (e.g., `black==23.10.0`). /// A unique identifier for a package (e.g., `black==23.10.0`).
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PackageId(String); pub enum PackageId {
NameVersion(PackageName, Version),
Url(String),
}
impl PackageId { impl PackageId {
pub fn new(id: impl Into<String>) -> Self { /// Create a new [`PackageId`] from a package name and version.
Self(id.into()) pub fn from_registry(name: PackageName, version: Version) -> Self {
Self::NameVersion(name, version)
}
/// Create a new [`PackageId`] from a URL.
pub fn from_url(url: &Url) -> Self {
Self::Url(cache_key::digest(&cache_key::CanonicalUrl::new(url)))
} }
} }
impl Display for PackageId { impl Display for PackageId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0) match self {
PackageId::NameVersion(name, version) => write!(f, "{name}-{version}"),
PackageId::Url(url) => write!(f, "{url}"),
}
} }
} }

View File

@ -31,14 +31,12 @@ pub trait DistributionMetadata: Name {
/// registry-based distributions (e.g., different wheels for the same package and version) /// registry-based distributions (e.g., different wheels for the same package and version)
/// will return the same package ID, but different distribution IDs. /// will return the same package ID, but different distribution IDs.
fn package_id(&self) -> PackageId { fn package_id(&self) -> PackageId {
PackageId::new(match self.version_or_url() { match self.version_or_url() {
VersionOrUrl::Version(version) => { VersionOrUrl::Version(version) => {
// https://packaging.python.org/en/latest/specifications/recording-installed-packages/#the-dist-info-directory PackageId::from_registry(self.name().clone(), version.clone())
// `version` is normalized by its `ToString` impl
format!("{}-{}", self.name(), version)
} }
VersionOrUrl::Url(url) => cache_key::digest(&cache_key::CanonicalUrl::new(url)), VersionOrUrl::Url(url) => PackageId::from_url(url),
}) }
} }
} }

View File

@ -3376,3 +3376,31 @@ fn compile_none_extra() -> Result<()> {
Ok(()) Ok(())
} }
/// Resolve a package (`pytz`) with a preference that omits a trailing zero.
///
/// See: <https://github.com/astral-sh/uv/issues/1536>
#[test]
fn compile_types_pytz() -> Result<()> {
let context = TestContext::new("3.12");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("types-pytz")?;
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("types-pytz==2021.1")?;
uv_snapshot!(context
.compile()
.arg("requirements.in")
.arg("-o")
.arg("requirements.txt"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
"###);
Ok(())
}