Rename `provides_extras` to `provides_extra` (#15825)

## Summary

This is now consistent with `requires_dist` (singular).
This commit is contained in:
Charlie Marsh 2025-09-14 09:27:45 -04:00 committed by GitHub
parent 312084f2dd
commit b770639c91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 94 additions and 96 deletions

View File

@ -547,7 +547,7 @@ impl PyProjectToml {
license_files, license_files,
classifiers: self.project.classifiers.clone().unwrap_or_default(), classifiers: self.project.classifiers.clone().unwrap_or_default(),
requires_dist: requires_dist.iter().map(ToString::to_string).collect(), requires_dist: requires_dist.iter().map(ToString::to_string).collect(),
provides_extras: extras.iter().map(ToString::to_string).collect(), provides_extra: extras.iter().map(ToString::to_string).collect(),
// Not commonly set. // Not commonly set.
provides_dist: vec![], provides_dist: vec![],
// Not supported. // Not supported.

View File

@ -1002,7 +1002,7 @@ impl CacheBucket {
Self::Interpreter => "interpreter-v4", Self::Interpreter => "interpreter-v4",
// Note that when bumping this, you'll also need to bump it // Note that when bumping this, you'll also need to bump it
// in `crates/uv/tests/it/cache_clean.rs`. // in `crates/uv/tests/it/cache_clean.rs`.
Self::Simple => "simple-v17", Self::Simple => "simple-v18",
// Note that when bumping this, you'll also need to bump it // Note that when bumping this, you'll also need to bump it
// in `crates/uv/tests/it/cache_prune.rs`. // in `crates/uv/tests/it/cache_prune.rs`.
Self::Wheels => "wheels-v5", Self::Wheels => "wheels-v5",

View File

@ -1245,7 +1245,7 @@ impl SimpleMetadata {
version: version.clone(), version: version.clone(),
requires_dist: metadata.requires_dist, requires_dist: metadata.requires_dist,
requires_python: metadata.requires_python, requires_python: metadata.requires_python,
provides_extras: metadata.provides_extras, provides_extra: metadata.provides_extra,
dynamic: false, dynamic: false,
}); });
SimpleMetadatum { SimpleMetadatum {

View File

@ -49,7 +49,7 @@ impl DependencyMetadata {
version: version.clone(), version: version.clone(),
requires_dist: metadata.requires_dist.clone(), requires_dist: metadata.requires_dist.clone(),
requires_python: metadata.requires_python.clone(), requires_python: metadata.requires_python.clone(),
provides_extras: metadata.provides_extras.clone(), provides_extra: metadata.provides_extra.clone(),
dynamic: false, dynamic: false,
}) })
} else { } else {
@ -70,7 +70,7 @@ impl DependencyMetadata {
version, version,
requires_dist: metadata.requires_dist.clone(), requires_dist: metadata.requires_dist.clone(),
requires_python: metadata.requires_python.clone(), requires_python: metadata.requires_python.clone(),
provides_extras: metadata.provides_extras.clone(), provides_extra: metadata.provides_extra.clone(),
dynamic: false, dynamic: false,
}) })
} }
@ -109,6 +109,6 @@ pub struct StaticMetadata {
) )
)] )]
pub requires_python: Option<VersionSpecifiers>, pub requires_python: Option<VersionSpecifiers>,
#[serde(default)] #[serde(default, alias = "provides-extras")]
pub provides_extras: Box<[ExtraName]>, pub provides_extra: Box<[ExtraName]>,
} }

View File

@ -58,7 +58,7 @@ pub struct Metadata {
// Optional fields // Optional fields
pub requires_dist: Box<[Requirement]>, pub requires_dist: Box<[Requirement]>,
pub requires_python: Option<VersionSpecifiers>, pub requires_python: Option<VersionSpecifiers>,
pub provides_extras: Box<[ExtraName]>, pub provides_extra: Box<[ExtraName]>,
pub dependency_groups: BTreeMap<GroupName, Box<[Requirement]>>, pub dependency_groups: BTreeMap<GroupName, Box<[Requirement]>>,
pub dynamic: bool, pub dynamic: bool,
} }
@ -74,7 +74,7 @@ impl Metadata {
.map(Requirement::from) .map(Requirement::from)
.collect(), .collect(),
requires_python: metadata.requires_python, requires_python: metadata.requires_python,
provides_extras: metadata.provides_extras, provides_extra: metadata.provides_extra,
dependency_groups: BTreeMap::default(), dependency_groups: BTreeMap::default(),
dynamic: metadata.dynamic, dynamic: metadata.dynamic,
} }
@ -94,13 +94,13 @@ impl Metadata {
let requires_dist = uv_pypi_types::RequiresDist { let requires_dist = uv_pypi_types::RequiresDist {
name: metadata.name, name: metadata.name,
requires_dist: metadata.requires_dist, requires_dist: metadata.requires_dist,
provides_extras: metadata.provides_extras, provides_extra: metadata.provides_extra,
dynamic: metadata.dynamic, dynamic: metadata.dynamic,
}; };
let RequiresDist { let RequiresDist {
name, name,
requires_dist, requires_dist,
provides_extras, provides_extra,
dependency_groups, dependency_groups,
dynamic, dynamic,
} = RequiresDist::from_project_maybe_workspace( } = RequiresDist::from_project_maybe_workspace(
@ -119,7 +119,7 @@ impl Metadata {
version: metadata.version, version: metadata.version,
requires_dist, requires_dist,
requires_python: metadata.requires_python, requires_python: metadata.requires_python,
provides_extras, provides_extra,
dependency_groups, dependency_groups,
dynamic, dynamic,
}) })

View File

@ -19,7 +19,7 @@ use crate::metadata::{GitWorkspaceMember, LoweredRequirement, MetadataError};
pub struct RequiresDist { pub struct RequiresDist {
pub name: PackageName, pub name: PackageName,
pub requires_dist: Box<[Requirement]>, pub requires_dist: Box<[Requirement]>,
pub provides_extras: Box<[ExtraName]>, pub provides_extra: Box<[ExtraName]>,
pub dependency_groups: BTreeMap<GroupName, Box<[Requirement]>>, pub dependency_groups: BTreeMap<GroupName, Box<[Requirement]>>,
pub dynamic: bool, pub dynamic: bool,
} }
@ -33,7 +33,7 @@ impl RequiresDist {
requires_dist: Box::into_iter(metadata.requires_dist) requires_dist: Box::into_iter(metadata.requires_dist)
.map(Requirement::from) .map(Requirement::from)
.collect(), .collect(),
provides_extras: metadata.provides_extras, provides_extra: metadata.provides_extra,
dependency_groups: BTreeMap::default(), dependency_groups: BTreeMap::default(),
dynamic: metadata.dynamic, dynamic: metadata.dynamic,
} }
@ -198,7 +198,7 @@ impl RequiresDist {
name: metadata.name, name: metadata.name,
requires_dist, requires_dist,
dependency_groups, dependency_groups,
provides_extras: metadata.provides_extras, provides_extra: metadata.provides_extra,
dynamic: metadata.dynamic, dynamic: metadata.dynamic,
}) })
} }
@ -216,7 +216,7 @@ impl RequiresDist {
for source in sources.iter() { for source in sources.iter() {
if let Some(extra) = source.extra() { if let Some(extra) = source.extra() {
// If the extra doesn't exist at all, error. // If the extra doesn't exist at all, error.
if !metadata.provides_extras.contains(extra) { if !metadata.provides_extra.contains(extra) {
return Err(MetadataError::MissingSourceExtra( return Err(MetadataError::MissingSourceExtra(
name.clone(), name.clone(),
extra.clone(), extra.clone(),
@ -268,7 +268,7 @@ impl From<Metadata> for RequiresDist {
Self { Self {
name: metadata.name, name: metadata.name,
requires_dist: metadata.requires_dist, requires_dist: metadata.requires_dist,
provides_extras: metadata.provides_extras, provides_extra: metadata.provides_extra,
dependency_groups: metadata.dependency_groups, dependency_groups: metadata.dependency_groups,
dynamic: metadata.dynamic, dynamic: metadata.dynamic,
} }

View File

@ -746,7 +746,7 @@ impl FormMetadata {
requires_python, requires_python,
requires_external, requires_external,
project_urls, project_urls,
provides_extras, provides_extra,
dynamic, dynamic,
} = metadata(file, filename).await?; } = metadata(file, filename).await?;
@ -808,7 +808,7 @@ impl FormMetadata {
add_vec("platform", platforms); add_vec("platform", platforms);
add_vec("project_urls", project_urls); add_vec("project_urls", project_urls);
add_vec("provides_dist", provides_dist); add_vec("provides_dist", provides_dist);
add_vec("provides_extra", provides_extras); add_vec("provides_extra", provides_extra);
add_vec("requires_dist", requires_dist); add_vec("requires_dist", requires_dist);
add_vec("requires_external", requires_external); add_vec("requires_external", requires_external);

View File

@ -99,7 +99,7 @@ pub struct Metadata23 {
/// A string containing the name of an optional feature. Must be a valid Python identifier. /// A string containing the name of an optional feature. Must be a valid Python identifier.
/// May be used to make a dependency conditional on whether the optional feature has been /// May be used to make a dependency conditional on whether the optional feature has been
/// requested. /// requested.
pub provides_extras: Vec<String>, pub provides_extra: Vec<String>,
/// A string containing the name of another core metadata field. /// A string containing the name of another core metadata field.
pub dynamic: Vec<String>, pub dynamic: Vec<String>,
} }
@ -145,7 +145,7 @@ impl Metadata23 {
let requires_python = headers.get_first_value("Requires-Python"); let requires_python = headers.get_first_value("Requires-Python");
let requires_external = headers.get_all_values("Requires-External").collect(); let requires_external = headers.get_all_values("Requires-External").collect();
let project_urls = headers.get_all_values("Project-URL").collect(); let project_urls = headers.get_all_values("Project-URL").collect();
let provides_extras = headers.get_all_values("Provides-Extra").collect(); let provides_extra = headers.get_all_values("Provides-Extra").collect();
let description_content_type = headers.get_first_value("Description-Content-Type"); let description_content_type = headers.get_first_value("Description-Content-Type");
let dynamic = headers.get_all_values("Dynamic").collect(); let dynamic = headers.get_all_values("Dynamic").collect();
Ok(Self { Ok(Self {
@ -174,7 +174,7 @@ impl Metadata23 {
requires_python, requires_python,
requires_external, requires_external,
project_urls, project_urls,
provides_extras, provides_extra,
dynamic, dynamic,
}) })
} }
@ -264,7 +264,7 @@ impl Metadata23 {
); );
write_all(&mut writer, "Requires-External", &self.requires_external); write_all(&mut writer, "Requires-External", &self.requires_external);
write_all(&mut writer, "Project-URL", &self.project_urls); write_all(&mut writer, "Project-URL", &self.project_urls);
write_all(&mut writer, "Provides-Extra", &self.provides_extras); write_all(&mut writer, "Provides-Extra", &self.provides_extra);
write_opt_str( write_opt_str(
&mut writer, &mut writer,
"Description-Content-Type", "Description-Content-Type",

View File

@ -31,7 +31,8 @@ pub struct ResolutionMetadata {
// Optional fields // Optional fields
pub requires_dist: Box<[Requirement<VerbatimParsedUrl>]>, pub requires_dist: Box<[Requirement<VerbatimParsedUrl>]>,
pub requires_python: Option<VersionSpecifiers>, pub requires_python: Option<VersionSpecifiers>,
pub provides_extras: Box<[ExtraName]>, #[serde(alias = "provides-extras")]
pub provides_extra: Box<[ExtraName]>,
/// Whether the version field is dynamic. /// Whether the version field is dynamic.
#[serde(default)] #[serde(default)]
pub dynamic: bool, pub dynamic: bool,
@ -64,7 +65,7 @@ impl ResolutionMetadata {
.map(|requires_python| LenientVersionSpecifiers::from_str(&requires_python)) .map(|requires_python| LenientVersionSpecifiers::from_str(&requires_python))
.transpose()? .transpose()?
.map(VersionSpecifiers::from); .map(VersionSpecifiers::from);
let provides_extras = headers let provides_extra = headers
.get_all_values("Provides-Extra") .get_all_values("Provides-Extra")
.filter_map( .filter_map(
|provides_extra| match ExtraName::from_owned(provides_extra) { |provides_extra| match ExtraName::from_owned(provides_extra) {
@ -85,7 +86,7 @@ impl ResolutionMetadata {
version, version,
requires_dist, requires_dist,
requires_python, requires_python,
provides_extras, provides_extra,
dynamic, dynamic,
}) })
} }
@ -144,7 +145,7 @@ impl ResolutionMetadata {
.map(|requires_python| LenientVersionSpecifiers::from_str(&requires_python)) .map(|requires_python| LenientVersionSpecifiers::from_str(&requires_python))
.transpose()? .transpose()?
.map(VersionSpecifiers::from); .map(VersionSpecifiers::from);
let provides_extras = headers let provides_extra = headers
.get_all_values("Provides-Extra") .get_all_values("Provides-Extra")
.filter_map( .filter_map(
|provides_extra| match ExtraName::from_owned(provides_extra) { |provides_extra| match ExtraName::from_owned(provides_extra) {
@ -162,7 +163,7 @@ impl ResolutionMetadata {
version, version,
requires_dist, requires_dist,
requires_python, requires_python,
provides_extras, provides_extra,
dynamic, dynamic,
}) })
} }
@ -250,7 +251,7 @@ impl ResolutionMetadata {
.collect::<Result<Box<_>, _>>()?; .collect::<Result<Box<_>, _>>()?;
// Extract the optional dependencies. // Extract the optional dependencies.
let provides_extras = project let provides_extra = project
.optional_dependencies .optional_dependencies
.unwrap_or_default() .unwrap_or_default()
.into_keys() .into_keys()
@ -261,7 +262,7 @@ impl ResolutionMetadata {
version, version,
requires_dist, requires_dist,
requires_python, requires_python,
provides_extras, provides_extra,
dynamic, dynamic,
}) })
} }
@ -370,7 +371,7 @@ mod tests {
assert_eq!(meta.version, Version::new([1, 0])); assert_eq!(meta.version, Version::new([1, 0]));
assert!(meta.requires_python.is_none()); assert!(meta.requires_python.is_none());
assert!(meta.requires_dist.is_empty()); assert!(meta.requires_dist.is_empty());
assert!(meta.provides_extras.is_empty()); assert!(meta.provides_extra.is_empty());
let s = r#" let s = r#"
[project] [project]
@ -384,7 +385,7 @@ mod tests {
assert_eq!(meta.version, Version::new([1, 0])); assert_eq!(meta.version, Version::new([1, 0]));
assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap())); assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap()));
assert!(meta.requires_dist.is_empty()); assert!(meta.requires_dist.is_empty());
assert!(meta.provides_extras.is_empty()); assert!(meta.provides_extra.is_empty());
let s = r#" let s = r#"
[project] [project]
@ -399,7 +400,7 @@ mod tests {
assert_eq!(meta.version, Version::new([1, 0])); assert_eq!(meta.version, Version::new([1, 0]));
assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap())); assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap()));
assert_eq!(*meta.requires_dist, ["foo".parse().unwrap()]); assert_eq!(*meta.requires_dist, ["foo".parse().unwrap()]);
assert!(meta.provides_extras.is_empty()); assert!(meta.provides_extra.is_empty());
let s = r#" let s = r#"
[project] [project]
@ -423,6 +424,6 @@ mod tests {
"bar; extra == \"dotenv\"".parse().unwrap() "bar; extra == \"dotenv\"".parse().unwrap()
] ]
); );
assert_eq!(*meta.provides_extras, ["dotenv".parse().unwrap()]); assert_eq!(*meta.provides_extra, ["dotenv".parse().unwrap()]);
} }
} }

View File

@ -1,7 +1,6 @@
use std::str::FromStr; use std::str::FromStr;
use itertools::Itertools; use itertools::Itertools;
use serde::{Deserialize, Serialize};
use uv_normalize::{ExtraName, PackageName}; use uv_normalize::{ExtraName, PackageName};
use uv_pep508::Requirement; use uv_pep508::Requirement;
@ -15,13 +14,11 @@ use crate::{LenientRequirement, MetadataError, VerbatimParsedUrl};
/// This is a subset of [`ResolutionMetadata`]; specifically, it omits the `version` and `requires-python` /// This is a subset of [`ResolutionMetadata`]; specifically, it omits the `version` and `requires-python`
/// fields, which aren't necessary when extracting the requirements of a package without installing /// fields, which aren't necessary when extracting the requirements of a package without installing
/// the package itself. /// the package itself.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Debug, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct RequiresDist { pub struct RequiresDist {
pub name: PackageName, pub name: PackageName,
pub requires_dist: Box<[Requirement<VerbatimParsedUrl>]>, pub requires_dist: Box<[Requirement<VerbatimParsedUrl>]>,
pub provides_extras: Box<[ExtraName]>, pub provides_extra: Box<[ExtraName]>,
#[serde(default)]
pub dynamic: bool, pub dynamic: bool,
} }
@ -86,7 +83,7 @@ impl RequiresDist {
.collect::<Result<Box<_>, _>>()?; .collect::<Result<Box<_>, _>>()?;
// Extract the optional dependencies. // Extract the optional dependencies.
let provides_extras = project let provides_extra = project
.optional_dependencies .optional_dependencies
.unwrap_or_default() .unwrap_or_default()
.into_keys() .into_keys()
@ -95,7 +92,7 @@ impl RequiresDist {
Ok(Self { Ok(Self {
name, name,
requires_dist, requires_dist,
provides_extras, provides_extra,
dynamic, dynamic,
}) })
} }

View File

@ -1,19 +1,19 @@
use crate::{LenientRequirement, MetadataError, VerbatimParsedUrl};
use serde::Deserialize;
use std::io::BufRead; use std::io::BufRead;
use std::str::FromStr; use std::str::FromStr;
use uv_normalize::ExtraName; use uv_normalize::ExtraName;
use uv_pep508::{ExtraOperator, MarkerExpression, MarkerTree, MarkerValueExtra, Requirement}; use uv_pep508::{ExtraOperator, MarkerExpression, MarkerTree, MarkerValueExtra, Requirement};
use crate::{LenientRequirement, MetadataError, VerbatimParsedUrl};
/// `requires.txt` metadata as defined in <https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html#dependency-metadata>. /// `requires.txt` metadata as defined in <https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html#dependency-metadata>.
/// ///
/// This is a subset of the full metadata specification, and only includes the fields that are /// This is a subset of the full metadata specification, and only includes the fields that are
/// included in the legacy `requires.txt` file. /// included in the legacy `requires.txt` file.
#[derive(Deserialize, Debug, Clone)] #[derive(Debug, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct RequiresTxt { pub struct RequiresTxt {
pub requires_dist: Vec<Requirement<VerbatimParsedUrl>>, pub requires_dist: Vec<Requirement<VerbatimParsedUrl>>,
pub provides_extras: Vec<ExtraName>, pub provides_extra: Vec<ExtraName>,
} }
impl RequiresTxt { impl RequiresTxt {
@ -22,7 +22,7 @@ impl RequiresTxt {
/// See: <https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html#dependency-metadata> /// See: <https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html#dependency-metadata>
pub fn parse(content: &[u8]) -> Result<Self, MetadataError> { pub fn parse(content: &[u8]) -> Result<Self, MetadataError> {
let mut requires_dist = vec![]; let mut requires_dist = vec![];
let mut provides_extras = vec![]; let mut provides_extra = vec![];
let mut current_marker = MarkerTree::default(); let mut current_marker = MarkerTree::default();
for line in content.lines() { for line in content.lines() {
@ -52,7 +52,7 @@ impl RequiresTxt {
// Parse the extra. // Parse the extra.
let extra = if let Some(extra) = extra { let extra = if let Some(extra) = extra {
if let Ok(extra) = ExtraName::from_str(extra) { if let Ok(extra) = ExtraName::from_str(extra) {
provides_extras.push(extra.clone()); provides_extra.push(extra.clone());
Some(MarkerValueExtra::Extra(extra)) Some(MarkerValueExtra::Extra(extra))
} else { } else {
Some(MarkerValueExtra::Arbitrary(extra.to_string())) Some(MarkerValueExtra::Arbitrary(extra.to_string()))
@ -103,7 +103,7 @@ impl RequiresTxt {
Ok(Self { Ok(Self {
requires_dist, requires_dist,
provides_extras, provides_extra,
}) })
} }
} }

View File

@ -237,8 +237,8 @@ pub struct CoreMetadatum {
pub requires_python: Option<VersionSpecifiers>, pub requires_python: Option<VersionSpecifiers>,
#[serde(default)] #[serde(default)]
pub requires_dist: Box<[Requirement<VerbatimParsedUrl>]>, pub requires_dist: Box<[Requirement<VerbatimParsedUrl>]>,
#[serde(default)] #[serde(default, alias = "provides-extras")]
pub provides_extras: Box<[ExtraName]>, pub provides_extra: Box<[ExtraName]>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -113,7 +113,7 @@ impl<'a, Context: BuildContext> ExtrasResolver<'a, Context> {
// Sort extras for consistency. // Sort extras for consistency.
let extras = { let extras = {
let mut extras = metadata.provides_extras.to_vec(); let mut extras = metadata.provides_extra.to_vec();
extras.sort_unstable(); extras.sort_unstable();
extras extras
}; };

View File

@ -91,7 +91,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
// Determine the extras to include when resolving the requirements. // Determine the extras to include when resolving the requirements.
let extras = self let extras = self
.extras .extras
.extra_names(metadata.provides_extras.iter()) .extra_names(metadata.provides_extra.iter())
.cloned() .cloned()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -111,7 +111,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
let requirements = requirements.into_boxed_slice(); let requirements = requirements.into_boxed_slice();
let project = metadata.name; let project = metadata.name;
let extras = metadata.provides_extras; let extras = metadata.provides_extra;
Ok(SourceTreeResolution { Ok(SourceTreeResolution {
requirements, requirements,

View File

@ -1229,11 +1229,11 @@ impl Lock {
if let Some(requires_python) = metadata.requires_python.as_ref() { if let Some(requires_python) = metadata.requires_python.as_ref() {
table.insert("requires-python", value(requires_python.to_string())); table.insert("requires-python", value(requires_python.to_string()));
} }
if !metadata.provides_extras.is_empty() { if !metadata.provides_extra.is_empty() {
table.insert( table.insert(
"provides-extras", "provides-extras",
value(serde::Serialize::serialize( value(serde::Serialize::serialize(
&metadata.provides_extras, &metadata.provides_extra,
toml_edit::ser::ValueSerializer::new(), toml_edit::ser::ValueSerializer::new(),
)?), )?),
); );
@ -1332,7 +1332,7 @@ impl Lock {
} }
let expected: BTreeSet<_> = provides_extra.iter().collect(); let expected: BTreeSet<_> = provides_extra.iter().collect();
let actual: BTreeSet<_> = package.metadata.provides_extras.iter().collect(); let actual: BTreeSet<_> = package.metadata.provides_extra.iter().collect();
if expected != actual { if expected != actual {
let expected = Box::into_iter(provides_extra).collect(); let expected = Box::into_iter(provides_extra).collect();
@ -1783,7 +1783,7 @@ impl Lock {
} }
// Validate the `provides-extras` metadata. // Validate the `provides-extras` metadata.
match self.satisfies_provides_extra(metadata.provides_extras, package) { match self.satisfies_provides_extra(metadata.provides_extra, package) {
SatisfiesResult::Satisfied => {} SatisfiesResult::Satisfied => {}
result => return Ok(result), result => return Ok(result),
} }
@ -1824,7 +1824,7 @@ impl Lock {
} }
// Validate that the extras are unchanged. // Validate that the extras are unchanged.
if let SatisfiesResult::Satisfied = self.satisfies_provides_extra(metadata.provides_extras, package, ) { if let SatisfiesResult::Satisfied = self.satisfies_provides_extra(metadata.provides_extra, package, ) {
debug!("Static `provides-extra` for `{}` is up-to-date", package.id); debug!("Static `provides-extra` for `{}` is up-to-date", package.id);
} else { } else {
debug!("Static `provides-extra` for `{}` is out-of-date; falling back to distribution database", package.id); debug!("Static `provides-extra` for `{}` is out-of-date; falling back to distribution database", package.id);
@ -1905,7 +1905,7 @@ impl Lock {
} }
// Validate that the extras are unchanged. // Validate that the extras are unchanged.
match self.satisfies_provides_extra(metadata.provides_extras, package) { match self.satisfies_provides_extra(metadata.provides_extra, package) {
SatisfiesResult::Satisfied => {} SatisfiesResult::Satisfied => {}
result => return Ok(result), result => return Ok(result),
} }
@ -2346,14 +2346,14 @@ impl Package {
.collect::<Result<_, _>>() .collect::<Result<_, _>>()
.map_err(LockErrorKind::RequirementRelativePath)? .map_err(LockErrorKind::RequirementRelativePath)?
}; };
let provides_extras = if id.source.is_immutable() { let provides_extra = if id.source.is_immutable() {
Box::default() Box::default()
} else { } else {
annotated_dist annotated_dist
.metadata .metadata
.as_ref() .as_ref()
.expect("metadata is present") .expect("metadata is present")
.provides_extras .provides_extra
.clone() .clone()
}; };
let dependency_groups = if id.source.is_immutable() { let dependency_groups = if id.source.is_immutable() {
@ -2386,7 +2386,7 @@ impl Package {
dependency_groups: BTreeMap::default(), dependency_groups: BTreeMap::default(),
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist, requires_dist,
provides_extras, provides_extra,
dependency_groups, dependency_groups,
}, },
}) })
@ -2995,10 +2995,10 @@ impl Package {
} }
} }
if !self.metadata.provides_extras.is_empty() { if !self.metadata.provides_extra.is_empty() {
let provides_extras = self let provides_extras = self
.metadata .metadata
.provides_extras .provides_extra
.iter() .iter()
.map(|extra| { .map(|extra| {
serde::Serialize::serialize(&extra, toml_edit::ser::ValueSerializer::new()) serde::Serialize::serialize(&extra, toml_edit::ser::ValueSerializer::new())
@ -3136,7 +3136,7 @@ impl Package {
/// Returns the extras the package provides, if any. /// Returns the extras the package provides, if any.
pub fn provides_extras(&self) -> &[ExtraName] { pub fn provides_extras(&self) -> &[ExtraName] {
&self.metadata.provides_extras &self.metadata.provides_extra
} }
/// Returns the dependency groups the package provides, if any. /// Returns the dependency groups the package provides, if any.
@ -3211,8 +3211,8 @@ struct PackageWire {
struct PackageMetadata { struct PackageMetadata {
#[serde(default)] #[serde(default)]
requires_dist: BTreeSet<Requirement>, requires_dist: BTreeSet<Requirement>,
#[serde(default)] #[serde(default, rename = "provides-extras")]
provides_extras: Box<[ExtraName]>, provides_extra: Box<[ExtraName]>,
#[serde(default, rename = "requires-dev", alias = "dependency-groups")] #[serde(default, rename = "requires-dev", alias = "dependency-groups")]
dependency_groups: BTreeMap<GroupName, BTreeSet<Requirement>>, dependency_groups: BTreeMap<GroupName, BTreeSet<Requirement>>,
} }
@ -3235,7 +3235,7 @@ impl PackageMetadata {
Self { Self {
requires_dist: unwire_requirements(self.requires_dist), requires_dist: unwire_requirements(self.requires_dist),
provides_extras: self.provides_extras, provides_extra: self.provides_extra,
dependency_groups: self dependency_groups: self
.dependency_groups .dependency_groups
.into_iter() .into_iter()

View File

@ -92,7 +92,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },

View File

@ -99,7 +99,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },

View File

@ -95,7 +95,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },

View File

@ -84,7 +84,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },
@ -155,7 +155,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },

View File

@ -84,7 +84,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },
@ -155,7 +155,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },

View File

@ -58,7 +58,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },
@ -106,7 +106,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },
@ -171,7 +171,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },

View File

@ -84,7 +84,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },
@ -155,7 +155,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },

View File

@ -67,7 +67,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },

View File

@ -65,7 +65,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },

View File

@ -60,7 +60,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },

View File

@ -60,7 +60,7 @@ Ok(
dependency_groups: {}, dependency_groups: {},
metadata: PackageMetadata { metadata: PackageMetadata {
requires_dist: {}, requires_dist: {},
provides_extras: [], provides_extra: [],
dependency_groups: {}, dependency_groups: {},
}, },
}, },

View File

@ -358,7 +358,7 @@ impl ResolverOutput {
if let Some(metadata) = metadata.as_ref() { if let Some(metadata) = metadata.as_ref() {
// Validate the extra. // Validate the extra.
if let Some(extra) = extra { if let Some(extra) = extra {
if !metadata.provides_extras.contains(extra) { if !metadata.provides_extra.contains(extra) {
diagnostics.push(ResolutionDiagnostic::MissingExtra { diagnostics.push(ResolutionDiagnostic::MissingExtra {
dist: dist.clone(), dist: dist.clone(),
extra: extra.clone(), extra: extra.clone(),

View File

@ -719,7 +719,7 @@ pub struct ResolverInstallerSchema {
/// to all versions of the package. /// to all versions of the package.
/// - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`). /// - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).
/// - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`). /// - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).
/// - (Optional) `provides-extras`: The extras provided by the package. /// - (Optional) `provides-extra`: The extras provided by the package.
#[option( #[option(
default = r#"[]"#, default = r#"[]"#,
value_type = "list[dict]", value_type = "list[dict]",
@ -1427,7 +1427,7 @@ pub struct PipOptions {
/// to all versions of the package. /// to all versions of the package.
/// - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`). /// - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).
/// - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`). /// - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).
/// - (Optional) `provides-extras`: The extras provided by the package. /// - (Optional) `provides-extra`: The extras provided by the package.
#[option( #[option(
default = r#"[]"#, default = r#"[]"#,
value_type = "list[dict]", value_type = "list[dict]",

View File

@ -51,7 +51,7 @@ fn clean_package_pypi() -> Result<()> {
// Assert that the `.rkyv` file is created for `iniconfig`. // Assert that the `.rkyv` file is created for `iniconfig`.
let rkyv = context let rkyv = context
.cache_dir .cache_dir
.child("simple-v17") .child("simple-v18")
.child("pypi") .child("pypi")
.child("iniconfig.rkyv"); .child("iniconfig.rkyv");
assert!( assert!(
@ -125,7 +125,7 @@ fn clean_package_index() -> Result<()> {
// Assert that the `.rkyv` file is created for `iniconfig`. // Assert that the `.rkyv` file is created for `iniconfig`.
let rkyv = context let rkyv = context
.cache_dir .cache_dir
.child("simple-v17") .child("simple-v18")
.child("index") .child("index")
.child("e8208120cae3ba69") .child("e8208120cae3ba69")
.child("iniconfig.rkyv"); .child("iniconfig.rkyv");

View File

@ -20009,7 +20009,7 @@ fn lock_dependency_metadata() -> Result<()> {
| |
11 | requires_dist = ["typing-extensions"] 11 | requires_dist = ["typing-extensions"]
| ^^^^^^^^^^^^^ | ^^^^^^^^^^^^^
unknown field `requires_dist`, expected one of `name`, `version`, `requires-dist`, `requires-python`, `provides-extras` unknown field `requires_dist`, expected one of `name`, `version`, `requires-dist`, `requires-python`, `provides-extra`, `provides-extras`
Resolved 4 packages in [TIME] Resolved 4 packages in [TIME]
Added idna v3.6 Added idna v3.6

View File

@ -1047,7 +1047,7 @@ standard, though only the following fields are respected:
to all versions of the package. to all versions of the package.
- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`). - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).
- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`). - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).
- (Optional) `provides-extras`: The extras provided by the package. - (Optional) `provides-extra`: The extras provided by the package.
**Default value**: `[]` **Default value**: `[]`
@ -2428,7 +2428,7 @@ standard, though only the following fields are respected:
to all versions of the package. to all versions of the package.
- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`). - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).
- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`). - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).
- (Optional) `provides-extras`: The extras provided by the package. - (Optional) `provides-extra`: The extras provided by the package.
**Default value**: `[]` **Default value**: `[]`

6
uv.schema.json generated
View File

@ -174,7 +174,7 @@
] ]
}, },
"dependency-metadata": { "dependency-metadata": {
"description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When\nprovided, enables the resolver to use the specified metadata instead of querying the\nregistry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)\nstandard, though only the following fields are respected:\n\n- `name`: The name of the package.\n- (Optional) `version`: The version of the package. If omitted, the metadata will be applied\n to all versions of the package.\n- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).\n- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).\n- (Optional) `provides-extras`: The extras provided by the package.", "description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When\nprovided, enables the resolver to use the specified metadata instead of querying the\nregistry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)\nstandard, though only the following fields are respected:\n\n- `name`: The name of the package.\n- (Optional) `version`: The version of the package. If omitted, the metadata will be applied\n to all versions of the package.\n- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).\n- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).\n- (Optional) `provides-extra`: The extras provided by the package.",
"type": [ "type": [
"array", "array",
"null" "null"
@ -1302,7 +1302,7 @@
] ]
}, },
"dependency-metadata": { "dependency-metadata": {
"description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When\nprovided, enables the resolver to use the specified metadata instead of querying the\nregistry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)\nstandard, though only the following fields are respected:\n\n- `name`: The name of the package.\n- (Optional) `version`: The version of the package. If omitted, the metadata will be applied\n to all versions of the package.\n- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).\n- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).\n- (Optional) `provides-extras`: The extras provided by the package.", "description": "Pre-defined static metadata for dependencies of the project (direct or transitive). When\nprovided, enables the resolver to use the specified metadata instead of querying the\nregistry or building the relevant package from source.\n\nMetadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)\nstandard, though only the following fields are respected:\n\n- `name`: The name of the package.\n- (Optional) `version`: The version of the package. If omitted, the metadata will be applied\n to all versions of the package.\n- (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).\n- (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).\n- (Optional) `provides-extra`: The extras provided by the package.",
"type": [ "type": [
"array", "array",
"null" "null"
@ -2195,7 +2195,7 @@
"name": { "name": {
"$ref": "#/definitions/PackageName" "$ref": "#/definitions/PackageName"
}, },
"provides-extras": { "provides-extra": {
"type": "array", "type": "array",
"default": [], "default": [],
"items": { "items": {