diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index 03761a75166f4..23abb8c3f8d71 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -1,7 +1,6 @@ use std::collections::BTreeMap; use std::fmt::Debug; use std::path::PathBuf; -use std::str::FromStr; use std::sync::Arc; use std::time::Duration; @@ -22,7 +21,7 @@ use uv_configuration::KeyringProviderType; use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename}; use uv_distribution_types::{ BuiltDist, File, IndexCapabilities, IndexFormat, IndexLocations, IndexMetadataRef, - IndexStatusCodeDecision, IndexStatusCodeStrategy, IndexUrl, Name, + IndexStatusCodeDecision, IndexStatusCodeStrategy, IndexUrl, Name, RegistryBuiltWheel, }; use uv_git::{GIT_LFS, GitError, GitHttpSettings, GitResolver, Reporter}; use uv_metadata::{read_metadata_async_seek, read_metadata_async_stream}; @@ -962,7 +961,7 @@ impl RegistryClient { })? } WheelLocation::Url(url) => { - self.wheel_metadata_registry(&wheel.index, &wheel.file, &url, capabilities) + self.wheel_metadata_registry(wheel, &url, capabilities) .await? } } @@ -1055,13 +1054,17 @@ impl RegistryClient { /// Fetch the metadata from a wheel file. async fn wheel_metadata_registry( &self, - index: &IndexUrl, - file: &File, + wheel: &RegistryBuiltWheel, url: &DisplaySafeUrl, capabilities: &IndexCapabilities, ) -> Result { + let RegistryBuiltWheel { + filename, + file, + index, + } = wheel; + // If the metadata file is available at its own url (PEP 658), download it from there. - let filename = WheelFilename::from_str(&file.filename).map_err(ErrorKind::WheelFilename)?; if file.dist_info_metadata { let mut url = url.clone(); let path = format!("{}.metadata", url.path()); @@ -1124,7 +1127,7 @@ impl RegistryClient { // `.dist-info/METADATA` file from the zip, and if that also fails, download the whole wheel // into the cache and read from there self.wheel_metadata_no_pep658( - &filename, + filename, url, Some(index), WheelCache::Index(index), diff --git a/crates/uv-distribution-filename/src/lib.rs b/crates/uv-distribution-filename/src/lib.rs index 9bfe955de8939..bb83a50aa3bce 100644 --- a/crates/uv-distribution-filename/src/lib.rs +++ b/crates/uv-distribution-filename/src/lib.rs @@ -21,6 +21,17 @@ mod splitter; mod wheel; mod wheel_tag; +fn normalized_package_name_matches(actual: &str, expected: &PackageName) -> bool { + actual + .bytes() + .map(|byte| match byte { + b'A'..=b'Z' => byte.to_ascii_lowercase(), + b'_' | b'.' => b'-', + _ => byte, + }) + .eq(expected.as_ref().bytes()) +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum DistFilename { SourceDistFilename(SourceDistFilename), @@ -44,7 +55,7 @@ impl DistFilename { package_name: &PackageName, ) -> Result { match DistExtension::from_path(filename) { - Ok(DistExtension::Wheel) => match WheelFilename::from_str(filename) { + Ok(DistExtension::Wheel) => match WheelFilename::from_hint(filename, package_name) { Ok(filename) => Ok(Self::WheelFilename(filename)), Err(err) => Err(DistFilenameError::InvalidWheel(err)), }, diff --git a/crates/uv-distribution-filename/src/source_dist.rs b/crates/uv-distribution-filename/src/source_dist.rs index 6bd71c1190db3..e58228bf88b48 100644 --- a/crates/uv-distribution-filename/src/source_dist.rs +++ b/crates/uv-distribution-filename/src/source_dist.rs @@ -1,7 +1,7 @@ use std::fmt::{Display, Formatter}; use std::str::FromStr; -use crate::SourceDistExtension; +use crate::{SourceDistExtension, normalized_package_name_matches}; use serde::{Deserialize, Serialize}; use thiserror::Error; use uv_normalize::{InvalidNameError, PackageName}; @@ -140,17 +140,6 @@ impl SourceDistFilename { } } -fn normalized_package_name_matches(actual: &str, expected: &PackageName) -> bool { - actual - .bytes() - .map(|byte| match byte { - b'A'..=b'Z' => byte.to_ascii_lowercase(), - b'_' | b'.' => b'-', - _ => byte, - }) - .eq(expected.as_ref().bytes()) -} - impl Display for SourceDistFilename { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( diff --git a/crates/uv-distribution-filename/src/wheel.rs b/crates/uv-distribution-filename/src/wheel.rs index 3c5afb2ed56a6..e34ffd613fc34 100644 --- a/crates/uv-distribution-filename/src/wheel.rs +++ b/crates/uv-distribution-filename/src/wheel.rs @@ -16,7 +16,7 @@ use uv_platform_tags::{ use crate::splitter::MemchrSplitter; use crate::wheel_tag::{TagSet, WheelTag, WheelTagLarge, WheelTagSmall}; -use crate::{BuildTag, BuildTagError}; +use crate::{BuildTag, BuildTagError, normalized_package_name_matches}; #[derive( Debug, @@ -41,13 +41,7 @@ impl FromStr for WheelFilename { type Err = WheelFilenameError; fn from_str(filename: &str) -> Result { - let stem = filename.strip_suffix(".whl").ok_or_else(|| { - WheelFilenameError::InvalidWheelFileName( - filename.to_string(), - "Must end with .whl".to_string(), - ) - })?; - Self::parse(stem, filename) + Self::parse_filename(filename, None) } } @@ -64,6 +58,26 @@ impl Display for WheelFilename { } impl WheelFilename { + fn parse_filename( + filename: &str, + hint: Option<&PackageName>, + ) -> Result { + let stem = filename.strip_suffix(".whl").ok_or_else(|| { + WheelFilenameError::InvalidWheelFileName( + filename.to_string(), + "Must end with .whl".to_string(), + ) + })?; + Self::parse(stem, filename, hint) + } + + pub(crate) fn from_hint( + filename: &str, + hint: &PackageName, + ) -> Result { + Self::parse_filename(filename, Some(hint)) + } + /// Create a [`WheelFilename`] from its components. pub fn new( name: PackageName, @@ -161,13 +175,17 @@ impl WheelFilename { { return Err(WheelFilenameError::UnexpectedExtension(stem.to_string())); } - Self::parse(stem, stem) + Self::parse(stem, stem, None) } /// Parse a wheel filename from the stem (e.g., `foo-1.2.3-py3-none-any`). /// /// The originating `filename` is used for high-fidelity error messages. - fn parse(stem: &str, filename: &str) -> Result { + fn parse( + stem: &str, + filename: &str, + hint: Option<&PackageName>, + ) -> Result { // The wheel filename should contain either five or six entries. If six, then the third // entry is the build tag. If five, then the third entry is the Python tag. // https://www.python.org/dev/peps/pep-0427/#file-name-convention @@ -234,8 +252,14 @@ impl WheelFilename { ) }; - let name = PackageName::from_str(name) - .map_err(|err| WheelFilenameError::InvalidPackageName(filename.to_string(), err))?; + let name = if let Some(hint) = hint + && normalized_package_name_matches(name, hint) + { + hint.clone() + } else { + PackageName::from_str(name) + .map_err(|err| WheelFilenameError::InvalidPackageName(filename.to_string(), err))? + }; let version = Version::from_str(version) .map_err(|err| WheelFilenameError::InvalidVersion(filename.to_string(), err))?; let build_tag = build_tag