uv_distribution_types/
dependency_metadata.rs1use rustc_hash::FxHashMap;
2use serde::{Deserialize, Serialize};
3use tracing::{debug, warn};
4use uv_normalize::{ExtraName, PackageName};
5use uv_pep440::{Version, VersionSpecifiers};
6use uv_pep508::Requirement;
7use uv_pypi_types::{ResolutionMetadata, VerbatimParsedUrl};
8
9#[derive(Debug, Clone, Default)]
11pub struct DependencyMetadata(FxHashMap<PackageName, Vec<StaticMetadata>>);
12
13impl DependencyMetadata {
14 pub fn from_entries(entries: impl IntoIterator<Item = StaticMetadata>) -> Self {
16 let mut map = Self::default();
17 for entry in entries {
18 map.0.entry(entry.name.clone()).or_default().push(entry);
19 }
20 map
21 }
22
23 pub fn get(
25 &self,
26 package: &PackageName,
27 version: Option<&Version>,
28 ) -> Option<ResolutionMetadata> {
29 let versions = self.0.get(package)?;
30
31 if let Some(version) = version {
32 let metadata = if let Some(metadata) = versions
34 .iter()
35 .find(|entry| entry.version.as_ref() == Some(version))
36 {
37 debug!("Found dependency metadata entry for `{package}=={version}`");
38 metadata
39 } else if let Some(metadata) = versions.iter().find(|entry| entry.version.is_none()) {
40 debug!("Found global metadata entry for `{package}`");
41 metadata
42 } else {
43 warn!("No dependency metadata entry found for `{package}=={version}`");
44 return None;
45 };
46
47 Some(ResolutionMetadata {
48 name: metadata.name.clone(),
49 version: version.clone(),
50 requires_dist: metadata.requires_dist.clone(),
51 requires_python: metadata.requires_python.clone(),
52 provides_extra: metadata.provides_extra.clone(),
53 dynamic: false,
54 })
55 } else {
56 let [metadata] = versions.as_slice() else {
59 warn!("Multiple dependency metadata entries found for `{package}`");
60 return None;
61 };
62 let Some(version) = metadata.version.clone() else {
63 warn!("No version found in dependency metadata entry for `{package}`");
64 return None;
65 };
66 debug!("Found dependency metadata entry for `{package}` (assuming: `{version}`)");
67
68 Some(ResolutionMetadata {
69 name: metadata.name.clone(),
70 version,
71 requires_dist: metadata.requires_dist.clone(),
72 requires_python: metadata.requires_python.clone(),
73 provides_extra: metadata.provides_extra.clone(),
74 dynamic: false,
75 })
76 }
77 }
78
79 pub fn values(&self) -> impl Iterator<Item = &StaticMetadata> {
81 self.0.values().flatten()
82 }
83}
84
85#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
88#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
89#[serde(rename_all = "kebab-case", deny_unknown_fields)]
90pub struct StaticMetadata {
91 pub name: PackageName,
93 #[cfg_attr(
94 feature = "schemars",
95 schemars(
96 with = "Option<String>",
97 description = "PEP 440-style package version, e.g., `1.2.3`"
98 )
99 )]
100 pub version: Option<Version>,
101 #[serde(default)]
103 pub requires_dist: Box<[Requirement<VerbatimParsedUrl>]>,
104 #[cfg_attr(
105 feature = "schemars",
106 schemars(
107 with = "Option<String>",
108 description = "PEP 508-style Python requirement, e.g., `>=3.10`"
109 )
110 )]
111 pub requires_python: Option<VersionSpecifiers>,
112 #[serde(default, alias = "provides-extras")]
113 pub provides_extra: Box<[ExtraName]>,
114}