diff --git a/crates/uv-resolver/src/lock.rs b/crates/uv-resolver/src/lock.rs index 63d0b7b5a..7ca42569f 100644 --- a/crates/uv-resolver/src/lock.rs +++ b/crates/uv-resolver/src/lock.rs @@ -453,6 +453,15 @@ impl Lock { doc.insert("requires-python", value(requires_python.to_string())); } + // Count the number of distributions for each package name. When + // there's only one distribution for a particular package name (the + // overwhelmingly common case), we can omit some data (like source and + // version) on dependency edges since it is strictly redundant. + let mut dist_count_by_name: FxHashMap = FxHashMap::default(); + for dist in &self.distributions { + *dist_count_by_name.entry(dist.id.name.clone()).or_default() += 1; + } + let mut distributions = ArrayOfTables::new(); for dist in &self.distributions { let mut table = Table::new(); @@ -469,7 +478,7 @@ impl Lock { let deps = dist .dependencies .iter() - .map(Dependency::to_toml) + .map(|dep| dep.to_toml(&dist_count_by_name)) .collect::(); table.insert("dependencies", Item::ArrayOfTables(deps)); } @@ -479,7 +488,7 @@ impl Lock { for (extra, deps) in &dist.optional_dependencies { let deps = deps .iter() - .map(Dependency::to_toml) + .map(|dep| dep.to_toml(&dist_count_by_name)) .collect::(); optional_deps.insert(extra.as_ref(), Item::ArrayOfTables(deps)); } @@ -491,7 +500,7 @@ impl Lock { for (extra, deps) in &dist.dev_dependencies { let deps = deps .iter() - .map(Dependency::to_toml) + .map(|dep| dep.to_toml(&dist_count_by_name)) .collect::(); dev_dependencies.insert(extra.as_ref(), Item::ArrayOfTables(deps)); } @@ -532,10 +541,27 @@ impl TryFrom for Lock { type Error = LockError; fn try_from(wire: LockWire) -> Result { + // Count the number of distributions for each package name. When + // there's only one distribution for a particular package name (the + // overwhelmingly common case), we can omit some data (like source and + // version) on dependency edges since it is strictly redundant. + let mut unambiguous_dist_ids: FxHashMap = FxHashMap::default(); + let mut ambiguous = FxHashSet::default(); + for dist in &wire.distributions { + if ambiguous.contains(&dist.id.name) { + continue; + } + if unambiguous_dist_ids.remove(&dist.id.name).is_some() { + ambiguous.insert(dist.id.name.clone()); + continue; + } + unambiguous_dist_ids.insert(dist.id.name.clone(), dist.id.clone()); + } + let distributions = wire .distributions .into_iter() - .map(DistributionWire::unwire) + .map(|dist| dist.unwire(&unambiguous_dist_ids)) .collect::, _>>()?; Lock::new(wire.version, distributions, wire.requires_python) } @@ -844,9 +870,14 @@ struct DistributionWire { } impl DistributionWire { - fn unwire(self) -> Result { + fn unwire( + self, + unambiguous_dist_ids: &FxHashMap, + ) -> Result { let unwire_deps = |deps: Vec| -> Result, LockError> { - deps.into_iter().map(DependencyWire::unwire).collect() + deps.into_iter() + .map(|dep| dep.unwire(unambiguous_dist_ids)) + .collect() }; Ok(Distribution { id: self.id, @@ -922,16 +953,38 @@ impl std::fmt::Display for DistributionId { #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, serde::Deserialize)] struct DistributionIdForDependency { name: PackageName, - version: Version, - source: Source, + version: Option, + source: Option, } impl DistributionIdForDependency { - fn unwire(self) -> Result { + fn unwire( + self, + unambiguous_dist_ids: &FxHashMap, + ) -> Result { + let unambiguous_dist_id = unambiguous_dist_ids.get(&self.name); + let version = self.version.map(Ok::<_, LockError>).unwrap_or_else(|| { + let Some(dist_id) = unambiguous_dist_id else { + return Err(LockErrorKind::MissingDependencyVersion { + name: self.name.clone(), + } + .into()); + }; + Ok(dist_id.version.clone()) + })?; + let source = self.source.map(Ok::<_, LockError>).unwrap_or_else(|| { + let Some(dist_id) = unambiguous_dist_id else { + return Err(LockErrorKind::MissingDependencySource { + name: self.name.clone(), + } + .into()); + }; + Ok(dist_id.source.clone()) + })?; Ok(DistributionId { name: self.name, - version: self.version, - source: self.source, + version, + source, }) } } @@ -940,8 +993,8 @@ impl From for DistributionIdForDependency { fn from(id: DistributionId) -> DistributionIdForDependency { DistributionIdForDependency { name: id.name, - version: id.version, - source: id.source, + version: Some(id.version), + source: Some(id.source), } } } @@ -1762,11 +1815,17 @@ impl Dependency { } /// Returns the TOML representation of this dependency. - fn to_toml(&self) -> Table { + fn to_toml(&self, dist_count_by_name: &FxHashMap) -> Table { + let count = dist_count_by_name + .get(&self.distribution_id.name) + .copied() + .expect("all dependencies have a corresponding distribution"); let mut table = Table::new(); table.insert("name", value(self.distribution_id.name.to_string())); - table.insert("version", value(self.distribution_id.version.to_string())); - table.insert("source", value(self.distribution_id.source.to_string())); + if count > 1 { + table.insert("version", value(self.distribution_id.version.to_string())); + table.insert("source", value(self.distribution_id.source.to_string())); + } if let Some(ref extra) = self.extra { table.insert("extra", value(extra.to_string())); } @@ -1813,9 +1872,12 @@ struct DependencyWire { } impl DependencyWire { - fn unwire(self) -> Result { + fn unwire( + self, + unambiguous_dist_ids: &FxHashMap, + ) -> Result { Ok(Dependency { - distribution_id: self.distribution_id.unwire()?, + distribution_id: self.distribution_id.unwire(unambiguous_dist_ids)?, extra: self.extra, marker: self.marker, }) @@ -2034,6 +2096,26 @@ enum LockErrorKind { #[source] err: VerbatimUrlError, }, + /// An error that occurs when an ambiguous `distribution.dependency` is + /// missing a `version` field. + #[error( + "dependency {name} has missing `version` \ + field but has more than one matching distribution" + )] + MissingDependencyVersion { + /// The name of the dependency that is missing a `version` field. + name: PackageName, + }, + /// An error that occurs when an ambiguous `distribution.dependency` is + /// missing a `source` field. + #[error( + "dependency {name} has missing `source` \ + field but has more than one matching distribution" + )] + MissingDependencySource { + /// The name of the dependency that is missing a `source` field. + name: PackageName, + }, } /// An error that occurs when a source string could not be parsed. @@ -2092,6 +2174,172 @@ impl std::fmt::Display for HashParseError { mod tests { use super::*; + #[test] + fn missing_dependency_source_unambiguous() { + let data = r#" +version = 1 + +[[distribution]] +name = "a" +version = "0.1.0" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution]] +name = "b" +version = "0.1.0" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution.dependencies]] +name = "a" +version = "0.1.0" +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); + } + + #[test] + fn missing_dependency_version_unambiguous() { + let data = r#" +version = 1 + +[[distribution]] +name = "a" +version = "0.1.0" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution]] +name = "b" +version = "0.1.0" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution.dependencies]] +name = "a" +source = "registry+https://pypi.org/simple" +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); + } + + #[test] + fn missing_dependency_source_version_unambiguous() { + let data = r#" +version = 1 + +[[distribution]] +name = "a" +version = "0.1.0" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution]] +name = "b" +version = "0.1.0" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution.dependencies]] +name = "a" +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); + } + + #[test] + fn missing_dependency_source_ambiguous() { + let data = r#" +version = 1 + +[[distribution]] +name = "a" +version = "0.1.0" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution]] +name = "a" +version = "0.1.1" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution]] +name = "b" +version = "0.1.0" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution.dependencies]] +name = "a" +version = "0.1.0" +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); + } + + #[test] + fn missing_dependency_version_ambiguous() { + let data = r#" +version = 1 + +[[distribution]] +name = "a" +version = "0.1.0" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution]] +name = "a" +version = "0.1.1" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution]] +name = "b" +version = "0.1.0" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution.dependencies]] +name = "a" +source = "registry+https://pypi.org/simple" +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); + } + + #[test] + fn missing_dependency_source_version_ambiguous() { + let data = r#" +version = 1 + +[[distribution]] +name = "a" +version = "0.1.0" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution]] +name = "a" +version = "0.1.1" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution]] +name = "b" +version = "0.1.0" +source = "registry+https://pypi.org/simple" +sdist = { url = "https://example.com", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 0 } + +[[distribution.dependencies]] +name = "a" +"#; + let result: Result = toml::from_str(data); + insta::assert_debug_snapshot!(result); + } + #[test] fn hash_required_present() { let data = r#" diff --git a/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_source_ambiguous.snap b/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_source_ambiguous.snap new file mode 100644 index 000000000..e7b0acc3d --- /dev/null +++ b/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_source_ambiguous.snap @@ -0,0 +1,16 @@ +--- +source: crates/uv-resolver/src/lock.rs +expression: result +--- +Err( + Error { + inner: Error { + inner: TomlError { + message: "dependency a has missing `source` field but has more than one matching distribution", + raw: None, + keys: [], + span: None, + }, + }, + }, +) diff --git a/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap b/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap new file mode 100644 index 000000000..fe09635a4 --- /dev/null +++ b/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap @@ -0,0 +1,210 @@ +--- +source: crates/uv-resolver/src/lock.rs +expression: result +--- +Ok( + Lock { + version: 1, + distributions: [ + Distribution { + id: DistributionId { + name: PackageName( + "a", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }, + sdist: Some( + Url { + url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "example.com", + ), + ), + port: None, + path: "/", + query: None, + fragment: None, + }, + metadata: SourceDistMetadata { + hash: Some( + Hash( + HashDigest { + algorithm: Sha256, + digest: "37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + }, + ), + ), + size: Some( + 0, + ), + }, + }, + ), + wheels: [], + dependencies: [], + optional_dependencies: {}, + dev_dependencies: {}, + }, + Distribution { + id: DistributionId { + name: PackageName( + "b", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }, + sdist: Some( + Url { + url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "example.com", + ), + ), + port: None, + path: "/", + query: None, + fragment: None, + }, + metadata: SourceDistMetadata { + hash: Some( + Hash( + HashDigest { + algorithm: Sha256, + digest: "37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + }, + ), + ), + size: Some( + 0, + ), + }, + }, + ), + wheels: [], + dependencies: [ + Dependency { + distribution_id: DistributionId { + name: PackageName( + "a", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }, + extra: None, + marker: None, + }, + ], + optional_dependencies: {}, + dev_dependencies: {}, + }, + ], + requires_python: None, + by_id: { + DistributionId { + name: PackageName( + "a", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }: 0, + DistributionId { + name: PackageName( + "b", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }: 1, + }, + }, +) diff --git a/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_ambiguous.snap b/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_ambiguous.snap new file mode 100644 index 000000000..7ce965a3e --- /dev/null +++ b/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_ambiguous.snap @@ -0,0 +1,16 @@ +--- +source: crates/uv-resolver/src/lock.rs +expression: result +--- +Err( + Error { + inner: Error { + inner: TomlError { + message: "dependency a has missing `version` field but has more than one matching distribution", + raw: None, + keys: [], + span: None, + }, + }, + }, +) diff --git a/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap b/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap new file mode 100644 index 000000000..fe09635a4 --- /dev/null +++ b/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap @@ -0,0 +1,210 @@ +--- +source: crates/uv-resolver/src/lock.rs +expression: result +--- +Ok( + Lock { + version: 1, + distributions: [ + Distribution { + id: DistributionId { + name: PackageName( + "a", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }, + sdist: Some( + Url { + url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "example.com", + ), + ), + port: None, + path: "/", + query: None, + fragment: None, + }, + metadata: SourceDistMetadata { + hash: Some( + Hash( + HashDigest { + algorithm: Sha256, + digest: "37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + }, + ), + ), + size: Some( + 0, + ), + }, + }, + ), + wheels: [], + dependencies: [], + optional_dependencies: {}, + dev_dependencies: {}, + }, + Distribution { + id: DistributionId { + name: PackageName( + "b", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }, + sdist: Some( + Url { + url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "example.com", + ), + ), + port: None, + path: "/", + query: None, + fragment: None, + }, + metadata: SourceDistMetadata { + hash: Some( + Hash( + HashDigest { + algorithm: Sha256, + digest: "37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + }, + ), + ), + size: Some( + 0, + ), + }, + }, + ), + wheels: [], + dependencies: [ + Dependency { + distribution_id: DistributionId { + name: PackageName( + "a", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }, + extra: None, + marker: None, + }, + ], + optional_dependencies: {}, + dev_dependencies: {}, + }, + ], + requires_python: None, + by_id: { + DistributionId { + name: PackageName( + "a", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }: 0, + DistributionId { + name: PackageName( + "b", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }: 1, + }, + }, +) diff --git a/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_version_ambiguous.snap b/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_version_ambiguous.snap new file mode 100644 index 000000000..7ce965a3e --- /dev/null +++ b/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_version_ambiguous.snap @@ -0,0 +1,16 @@ +--- +source: crates/uv-resolver/src/lock.rs +expression: result +--- +Err( + Error { + inner: Error { + inner: TomlError { + message: "dependency a has missing `version` field but has more than one matching distribution", + raw: None, + keys: [], + span: None, + }, + }, + }, +) diff --git a/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap b/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap new file mode 100644 index 000000000..fe09635a4 --- /dev/null +++ b/crates/uv-resolver/src/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap @@ -0,0 +1,210 @@ +--- +source: crates/uv-resolver/src/lock.rs +expression: result +--- +Ok( + Lock { + version: 1, + distributions: [ + Distribution { + id: DistributionId { + name: PackageName( + "a", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }, + sdist: Some( + Url { + url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "example.com", + ), + ), + port: None, + path: "/", + query: None, + fragment: None, + }, + metadata: SourceDistMetadata { + hash: Some( + Hash( + HashDigest { + algorithm: Sha256, + digest: "37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + }, + ), + ), + size: Some( + 0, + ), + }, + }, + ), + wheels: [], + dependencies: [], + optional_dependencies: {}, + dev_dependencies: {}, + }, + Distribution { + id: DistributionId { + name: PackageName( + "b", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }, + sdist: Some( + Url { + url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "example.com", + ), + ), + port: None, + path: "/", + query: None, + fragment: None, + }, + metadata: SourceDistMetadata { + hash: Some( + Hash( + HashDigest { + algorithm: Sha256, + digest: "37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + }, + ), + ), + size: Some( + 0, + ), + }, + }, + ), + wheels: [], + dependencies: [ + Dependency { + distribution_id: DistributionId { + name: PackageName( + "a", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }, + extra: None, + marker: None, + }, + ], + optional_dependencies: {}, + dev_dependencies: {}, + }, + ], + requires_python: None, + by_id: { + DistributionId { + name: PackageName( + "a", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }: 0, + DistributionId { + name: PackageName( + "b", + ), + version: "0.1.0", + source: Registry( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + ), + }: 1, + }, + }, +)