diff --git a/crates/uv-client/src/flat_index.rs b/crates/uv-client/src/flat_index.rs
index 91668c5c4..c30df5199 100644
--- a/crates/uv-client/src/flat_index.rs
+++ b/crates/uv-client/src/flat_index.rs
@@ -7,8 +7,7 @@ use url::Url;
use uv_cache::{Cache, CacheBucket};
use uv_cache_key::cache_digest;
-use uv_distribution_filename::DistFilename;
-use uv_distribution_types::{File, FileLocation, IndexUrl, UrlString};
+use uv_distribution_types::{File, FileLocation, IndexEntryFilename, IndexUrl, UrlString};
use uv_pypi_types::HashDigests;
use uv_redacted::DisplaySafeUrl;
use uv_small_str::SmallString;
@@ -40,7 +39,7 @@ pub enum FindLinksDirectoryError {
/// An entry in a `--find-links` index.
#[derive(Debug, Clone)]
pub struct FlatIndexEntry {
- pub filename: DistFilename,
+ pub filename: IndexEntryFilename,
pub file: File,
pub index: IndexUrl,
}
@@ -238,7 +237,9 @@ impl<'a> FlatIndexClient<'a> {
})
.filter_map(|file| {
Some(FlatIndexEntry {
- filename: DistFilename::try_from_normalized_filename(&file.filename)?,
+ filename: IndexEntryFilename::try_from_normalized_filename(
+ &file.filename,
+ )?,
file,
index: flat_index.clone(),
})
@@ -307,9 +308,10 @@ impl<'a> FlatIndexClient<'a> {
yanked: None,
};
- let Some(filename) = DistFilename::try_from_normalized_filename(filename) else {
+ // Try to parse as a distribution filename first
+ let Some(filename) = IndexEntryFilename::try_from_normalized_filename(filename) else {
debug!(
- "Ignoring `--find-links` entry (expected a wheel or source distribution filename): {}",
+ "Ignoring `--find-links` entry (expected a wheel, source distribution, or variants.json filename): {}",
entry.path().display()
);
continue;
diff --git a/crates/uv-client/src/html.rs b/crates/uv-client/src/html.rs
index 1b6b01347..d335f38c5 100644
--- a/crates/uv-client/src/html.rs
+++ b/crates/uv-client/src/html.rs
@@ -1374,4 +1374,63 @@ mod tests {
}
"#);
}
+
+ #[test]
+ fn parse_variants_json() {
+ // A variants.json without wheels doesn't make much sense, but it's sufficient to test
+ // parsing.
+ let text = r#"
+
+
+
+Links for jinja2
+jinja2-3.1.2-variants.json
+
+
+
+ "#;
+ let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
+ let result = SimpleHtml::parse(text, &base).unwrap();
+ insta::assert_debug_snapshot!(result, @r#"
+ SimpleHtml {
+ base: BaseUrl(
+ DisplaySafeUrl {
+ scheme: "https",
+ cannot_be_a_base: false,
+ username: "",
+ password: None,
+ host: Some(
+ Domain(
+ "download.pytorch.org",
+ ),
+ ),
+ port: None,
+ path: "/whl/jinja2/",
+ query: None,
+ fragment: None,
+ },
+ ),
+ files: [
+ File {
+ core_metadata: None,
+ filename: "jinja2-3.1.2-variants.json",
+ hashes: Hashes {
+ md5: None,
+ sha256: Some(
+ "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
+ ),
+ sha384: None,
+ sha512: None,
+ blake2b: None,
+ },
+ requires_python: None,
+ size: None,
+ upload_time: None,
+ url: "/whl/jinja2-3.1.2-variants.json",
+ yanked: None,
+ },
+ ],
+ }
+ "#);
+ }
}
diff --git a/crates/uv-configuration/src/name_specifiers.rs b/crates/uv-configuration/src/name_specifiers.rs
index 1fd25ea0b..3efeee1f2 100644
--- a/crates/uv-configuration/src/name_specifiers.rs
+++ b/crates/uv-configuration/src/name_specifiers.rs
@@ -1,4 +1,6 @@
-use std::{borrow::Cow, str::FromStr};
+#[cfg(feature = "schemars")]
+use std::borrow::Cow;
+use std::str::FromStr;
use uv_pep508::PackageName;
diff --git a/crates/uv-configuration/src/required_version.rs b/crates/uv-configuration/src/required_version.rs
index 7135dfdab..93327a0ac 100644
--- a/crates/uv-configuration/src/required_version.rs
+++ b/crates/uv-configuration/src/required_version.rs
@@ -1,5 +1,7 @@
+#[cfg(feature = "schemars")]
+use std::borrow::Cow;
+use std::fmt::Formatter;
use std::str::FromStr;
-use std::{borrow::Cow, fmt::Formatter};
use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers, VersionSpecifiersParseError};
diff --git a/crates/uv-configuration/src/trusted_host.rs b/crates/uv-configuration/src/trusted_host.rs
index 9f3efb6fc..07ff2998a 100644
--- a/crates/uv-configuration/src/trusted_host.rs
+++ b/crates/uv-configuration/src/trusted_host.rs
@@ -1,5 +1,7 @@
use serde::{Deserialize, Deserializer};
-use std::{borrow::Cow, str::FromStr};
+#[cfg(feature = "schemars")]
+use std::borrow::Cow;
+use std::str::FromStr;
use url::Url;
/// A host specification (wildcard, or host, with optional scheme and/or port) for which
diff --git a/crates/uv-distribution-types/src/dist_or_variant_filename.rs b/crates/uv-distribution-types/src/dist_or_variant_filename.rs
index bb8c4f536..69e2058b2 100644
--- a/crates/uv-distribution-types/src/dist_or_variant_filename.rs
+++ b/crates/uv-distribution-types/src/dist_or_variant_filename.rs
@@ -1,9 +1,32 @@
-use crate::VariantJson;
use uv_distribution_filename::DistFilename;
+use uv_normalize::PackageName;
-/// On an index page, there can be wheels, source distributions and `variant.json` files.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum DistOrVariantFilename {
+use crate::VariantsJson;
+
+/// On an index page, there can be wheels, source distributions and `variants.json` files.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub enum IndexEntryFilename {
DistFilename(DistFilename),
- VariantJson(VariantJson),
+ VariantJson(VariantsJson),
+}
+
+impl IndexEntryFilename {
+ pub fn name(&self) -> &PackageName {
+ match self {
+ Self::DistFilename(filename) => filename.name(),
+ Self::VariantJson(variant_json) => &variant_json.name,
+ }
+ }
+
+ /// Parse a filename as either a distribution filename or a `variants.json` filename.
+ #[allow(clippy::manual_map)]
+ pub fn try_from_normalized_filename(filename: &str) -> Option {
+ if let Some(dist_filename) = DistFilename::try_from_normalized_filename(filename) {
+ Some(Self::DistFilename(dist_filename))
+ } else if let Some(variant_json) = VariantsJson::try_from_normalized_filename(filename) {
+ Some(Self::VariantJson(variant_json))
+ } else {
+ None
+ }
+ }
}
diff --git a/crates/uv-distribution-types/src/pip_index.rs b/crates/uv-distribution-types/src/pip_index.rs
index 888c0df0d..18671e42f 100644
--- a/crates/uv-distribution-types/src/pip_index.rs
+++ b/crates/uv-distribution-types/src/pip_index.rs
@@ -3,7 +3,9 @@
//! flags set.
use serde::{Deserialize, Deserializer, Serialize};
-use std::{borrow::Cow, path::Path};
+#[cfg(feature = "schemars")]
+use std::borrow::Cow;
+use std::path::Path;
use crate::{Index, IndexUrl};
diff --git a/crates/uv-distribution-types/src/prioritized_distribution.rs b/crates/uv-distribution-types/src/prioritized_distribution.rs
index 847140b22..d68568eea 100644
--- a/crates/uv-distribution-types/src/prioritized_distribution.rs
+++ b/crates/uv-distribution-types/src/prioritized_distribution.rs
@@ -13,7 +13,7 @@ use uv_variants::VariantPriority;
use crate::{
File, InstalledDist, KnownPlatform, RegistryBuiltDist, RegistryBuiltWheel, RegistrySourceDist,
- ResolvedDistRef, VariantJson,
+ ResolvedDistRef, VariantsJson,
};
/// A collection of distributions that have been filtered by relevance.
@@ -31,7 +31,7 @@ struct PrioritizedDistInner {
/// The set of all wheels associated with this distribution.
wheels: Vec<(RegistryBuiltWheel, WheelCompatibility)>,
/// The `variants.json` file associated with the package version.
- variants_json: Option,
+ variants_json: Option,
/// The hashes for each distribution.
hashes: Vec,
/// The set of supported platforms for the distribution, described in terms of their markers.
@@ -375,8 +375,8 @@ impl PrioritizedDist {
}))
}
- /// Create a new [`PrioritizedDist`] from the `variant.json`.
- pub fn from_variant_json(variant_json: VariantJson) -> Self {
+ /// Create a new [`PrioritizedDist`] from the `variants.json`.
+ pub fn from_variant_json(variant_json: VariantsJson) -> Self {
Self(Box::new(PrioritizedDistInner {
markers: MarkerTree::TRUE,
best_wheel_index: None,
@@ -440,10 +440,10 @@ impl PrioritizedDist {
}
}
- pub fn insert_variant_json(&mut self, variant_json: VariantJson) {
+ pub fn insert_variant_json(&mut self, variant_json: VariantsJson) {
debug_assert!(
self.0.variants_json.is_none(),
- "The variant.json filename is unique"
+ "The variants.json filename is unique"
);
self.0.variants_json = Some(variant_json);
}
diff --git a/crates/uv-distribution-types/src/status_code_strategy.rs b/crates/uv-distribution-types/src/status_code_strategy.rs
index 709f68be1..b019d0329 100644
--- a/crates/uv-distribution-types/src/status_code_strategy.rs
+++ b/crates/uv-distribution-types/src/status_code_strategy.rs
@@ -1,4 +1,6 @@
-use std::{borrow::Cow, ops::Deref};
+#[cfg(feature = "schemars")]
+use std::borrow::Cow;
+use std::ops::Deref;
use http::StatusCode;
use rustc_hash::FxHashSet;
diff --git a/crates/uv-distribution-types/src/variant_json.rs b/crates/uv-distribution-types/src/variant_json.rs
index cf2d2ae56..4ecf55193 100644
--- a/crates/uv-distribution-types/src/variant_json.rs
+++ b/crates/uv-distribution-types/src/variant_json.rs
@@ -1,11 +1,39 @@
-use crate::FileLocation;
+use std::str::FromStr;
+
use uv_normalize::PackageName;
use uv_pep440::Version;
-/// A `--variant.json` file.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct VariantJson {
+/// A `--variants.json` file.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct VariantsJson {
pub name: PackageName,
pub version: Version,
- pub file_location: FileLocation,
+}
+
+impl VariantsJson {
+ /// Parse a `--variants.json` filename.
+ ///
+ /// name and version must be normalized, i.e., they don't contain dashes.
+ pub fn try_from_normalized_filename(filename: &str) -> Option {
+ let stem = filename.strip_suffix("-variants.json")?;
+
+ let (name, version) = stem.split_once('-')?;
+ let name = PackageName::from_str(name).ok()?;
+ let version = Version::from_str(version).ok()?;
+
+ Some(VariantsJson { name, version })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn variants_json_parsing() {
+ let variant =
+ VariantsJson::try_from_normalized_filename("numpy-1.21.0-variants.json").unwrap();
+ assert_eq!(variant.name.as_str(), "numpy");
+ assert_eq!(variant.version.to_string(), "1.21.0");
+ }
}
diff --git a/crates/uv-resolver/src/flat_index.rs b/crates/uv-resolver/src/flat_index.rs
index 1d3b1b454..0de5f189d 100644
--- a/crates/uv-resolver/src/flat_index.rs
+++ b/crates/uv-resolver/src/flat_index.rs
@@ -8,7 +8,7 @@ use uv_client::{FlatIndexEntries, FlatIndexEntry};
use uv_configuration::BuildOptions;
use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename};
use uv_distribution_types::{
- DistOrVariantFilename, File, HashComparison, HashPolicy, IncompatibleSource, IncompatibleWheel,
+ File, HashComparison, HashPolicy, IncompatibleSource, IncompatibleWheel, IndexEntryFilename,
IndexUrl, PrioritizedDist, RegistryBuiltWheel, RegistrySourceDist, SourceDistCompatibility,
WheelCompatibility,
};
@@ -46,7 +46,7 @@ impl FlatIndex {
let distributions = index.entry(entry.filename.name().clone()).or_default();
distributions.add_file(
entry.file,
- DistOrVariantFilename::DistFilename(entry.filename),
+ entry.filename,
tags,
variants,
hasher,
@@ -92,7 +92,7 @@ impl FlatDistributions {
for entry in entries {
distributions.add_file(
entry.file,
- DistOrVariantFilename::DistFilename(entry.filename),
+ entry.filename,
tags,
variants,
hasher,
@@ -117,7 +117,7 @@ impl FlatDistributions {
fn add_file(
&mut self,
file: File,
- filename: DistOrVariantFilename,
+ filename: IndexEntryFilename,
tags: Option<&Tags>,
variants: Option<&VariantSet>,
hasher: &HashStrategy,
@@ -127,7 +127,7 @@ impl FlatDistributions {
// No `requires-python` here: for source distributions, we don't have that information;
// for wheels, we read it lazily only when selected.
match filename {
- DistOrVariantFilename::DistFilename(DistFilename::WheelFilename(filename)) => {
+ IndexEntryFilename::DistFilename(DistFilename::WheelFilename(filename)) => {
let version = filename.version.clone();
let compatibility = Self::wheel_compatibility(
@@ -152,7 +152,7 @@ impl FlatDistributions {
}
}
}
- DistOrVariantFilename::DistFilename(DistFilename::SourceDistFilename(filename)) => {
+ IndexEntryFilename::DistFilename(DistFilename::SourceDistFilename(filename)) => {
let compatibility = Self::source_dist_compatibility(
&filename,
file.hashes.as_slice(),
@@ -176,7 +176,7 @@ impl FlatDistributions {
}
}
}
- DistOrVariantFilename::VariantJson(variant_json) => {
+ IndexEntryFilename::VariantJson(variant_json) => {
match self.0.entry(variant_json.version.clone()) {
Entry::Occupied(mut entry) => {
entry.get_mut().insert_variant_json(variant_json);