Pass around index with associated metadata (#12406)

## Summary

This PR modifies the requirement source entities to store a (new)
container struct that wraps `IndexUrl`. This will allow us to store
user-defined metadata alongside `IndexUrl`, and propagate that metadata
throughout resolution.

Specifically, I need to store the "kind" of the index (Simple API vs.
`--find-links`), but I also ran into this problem when I tried to add
support for overriding `Cache-Control` headers on a per-index basis: at
present, we have no way to passing around metadata alongside an
`IndexUrl`.
This commit is contained in:
Charlie Marsh 2025-03-24 10:15:49 -04:00 committed by GitHub
parent c3442e822e
commit 1865e0a6ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 213 additions and 80 deletions

View file

@ -3,7 +3,17 @@ use url::Url;
/// When to use authentication. /// When to use authentication.
#[derive( #[derive(
Copy, Clone, Debug, Default, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize, Copy,
Clone,
Debug,
Default,
Hash,
Eq,
PartialEq,
Ord,
PartialOrd,
serde::Serialize,
serde::Deserialize,
)] )]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]

View file

@ -13,14 +13,14 @@ use reqwest_middleware::ClientWithMiddleware;
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
use tracing::{info_span, instrument, trace, warn, Instrument}; use tracing::{info_span, instrument, trace, warn, Instrument};
use url::Url; use url::Url;
use uv_auth::UrlAuthPolicies;
use uv_auth::UrlAuthPolicies;
use uv_cache::{Cache, CacheBucket, CacheEntry, WheelCache}; use uv_cache::{Cache, CacheBucket, CacheEntry, WheelCache};
use uv_configuration::KeyringProviderType; use uv_configuration::KeyringProviderType;
use uv_configuration::{IndexStrategy, TrustedHost}; use uv_configuration::{IndexStrategy, TrustedHost};
use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename}; use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename};
use uv_distribution_types::{ use uv_distribution_types::{
BuiltDist, File, FileLocation, Index, IndexCapabilities, IndexUrl, IndexUrls, Name, BuiltDist, File, FileLocation, IndexCapabilities, IndexMetadataRef, IndexUrl, IndexUrls, Name,
}; };
use uv_metadata::{read_metadata_async_seek, read_metadata_async_stream}; use uv_metadata::{read_metadata_async_seek, read_metadata_async_stream};
use uv_normalize::PackageName; use uv_normalize::PackageName;
@ -255,16 +255,17 @@ impl RegistryClient {
} }
/// Return the appropriate index URLs for the given [`PackageName`]. /// Return the appropriate index URLs for the given [`PackageName`].
fn index_urls_for(&self, package_name: &PackageName) -> impl Iterator<Item = &IndexUrl> { fn index_urls_for(&self, package_name: &PackageName) -> impl Iterator<Item = IndexMetadataRef> {
self.torch_backend self.torch_backend
.as_ref() .as_ref()
.and_then(|torch_backend| { .and_then(|torch_backend| {
torch_backend torch_backend
.applies_to(package_name) .applies_to(package_name)
.then(|| torch_backend.index_urls()) .then(|| torch_backend.index_urls())
.map(|indexes| indexes.map(IndexMetadataRef::from))
}) })
.map(Either::Left) .map(Either::Left)
.unwrap_or_else(|| Either::Right(self.index_urls.indexes().map(Index::url))) .unwrap_or_else(|| Either::Right(self.index_urls.indexes().map(IndexMetadataRef::from)))
} }
/// Return the appropriate [`IndexStrategy`] for the given [`PackageName`]. /// Return the appropriate [`IndexStrategy`] for the given [`PackageName`].
@ -288,10 +289,10 @@ impl RegistryClient {
pub async fn simple<'index>( pub async fn simple<'index>(
&'index self, &'index self,
package_name: &PackageName, package_name: &PackageName,
index: Option<&'index IndexUrl>, index: Option<IndexMetadataRef<'index>>,
capabilities: &IndexCapabilities, capabilities: &IndexCapabilities,
download_concurrency: &Semaphore, download_concurrency: &Semaphore,
) -> Result<Vec<(&'index IndexUrl, OwnedArchive<SimpleMetadata>)>, Error> { ) -> Result<Vec<(IndexMetadataRef<'index>, OwnedArchive<SimpleMetadata>)>, Error> {
// If `--no-index` is specified, avoid fetching regardless of whether the index is implicit, // If `--no-index` is specified, avoid fetching regardless of whether the index is implicit,
// explicit, etc. // explicit, etc.
if self.index_urls.no_index() { if self.index_urls.no_index() {
@ -312,7 +313,7 @@ impl RegistryClient {
for index in indexes { for index in indexes {
let _permit = download_concurrency.acquire().await; let _permit = download_concurrency.acquire().await;
if let Some(metadata) = self if let Some(metadata) = self
.simple_single_index(package_name, index, capabilities) .simple_single_index(package_name, index.url(), capabilities)
.await? .await?
{ {
results.push((index, metadata)); results.push((index, metadata));
@ -327,7 +328,7 @@ impl RegistryClient {
.map(|index| async move { .map(|index| async move {
let _permit = download_concurrency.acquire().await; let _permit = download_concurrency.acquire().await;
let metadata = self let metadata = self
.simple_single_index(package_name, index, capabilities) .simple_single_index(package_name, index.url(), capabilities)
.await?; .await?;
Ok((index, metadata)) Ok((index, metadata))
}) })
@ -369,9 +370,9 @@ impl RegistryClient {
capabilities: &IndexCapabilities, capabilities: &IndexCapabilities,
) -> Result<Option<OwnedArchive<SimpleMetadata>>, Error> { ) -> Result<Option<OwnedArchive<SimpleMetadata>>, Error> {
// Format the URL for PyPI. // Format the URL for PyPI.
let mut url: Url = index.clone().into(); let mut url = index.url().clone();
url.path_segments_mut() url.path_segments_mut()
.map_err(|()| ErrorKind::CannotBeABase(index.clone().into()))? .map_err(|()| ErrorKind::CannotBeABase(index.url().clone()))?
.pop_if_empty() .pop_if_empty()
.push(package_name.as_ref()) .push(package_name.as_ref())
// The URL *must* end in a trailing slash for proper relative path behavior // The URL *must* end in a trailing slash for proper relative path behavior

View file

@ -10,7 +10,9 @@ use crate::index_name::{IndexName, IndexNameError};
use crate::origin::Origin; use crate::origin::Origin;
use crate::{IndexUrl, IndexUrlError}; use crate::{IndexUrl, IndexUrlError};
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(
Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, serde::Deserialize,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Index { pub struct Index {
@ -200,6 +202,20 @@ impl Index {
} }
} }
impl From<IndexUrl> for Index {
fn from(value: IndexUrl) -> Self {
Self {
name: None,
url: value,
explicit: false,
default: false,
origin: None,
publish_url: None,
authenticate: AuthPolicy::default(),
}
}
}
impl FromStr for Index { impl FromStr for Index {
type Err = IndexSourceError; type Err = IndexSourceError;
@ -235,6 +251,76 @@ impl FromStr for Index {
} }
} }
/// An [`IndexUrl`] along with the metadata necessary to query the index.
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct IndexMetadata {
pub url: IndexUrl,
// /// The type of the index.
// ///
// /// Indexes can either be PEP 503-compliant (i.e., a registry implementing the Simple API) or
// /// structured as a flat list of distributions (e.g., `--find-links`). In both cases, indexes
// /// can point to either local or remote resources.
// #[serde(default)]
// pub r#type: IndexKind,
}
impl IndexMetadata {
/// Return a reference to the [`IndexMetadata`].
pub fn as_ref(&self) -> IndexMetadataRef<'_> {
let Self { url } = self;
IndexMetadataRef { url }
}
/// Consume the [`IndexMetadata`] and return the [`IndexUrl`].
pub fn into_url(self) -> IndexUrl {
self.url
}
}
/// A reference to an [`IndexMetadata`].
#[derive(Debug, Copy, Clone)]
pub struct IndexMetadataRef<'a> {
pub url: &'a IndexUrl,
}
impl IndexMetadata {
/// Return the [`IndexUrl`] of the index.
pub fn url(&self) -> &IndexUrl {
&self.url
}
}
impl IndexMetadataRef<'_> {
/// Return the [`IndexUrl`] of the index.
pub fn url(&self) -> &IndexUrl {
self.url
}
}
impl<'a> From<&'a IndexMetadata> for IndexMetadataRef<'a> {
fn from(value: &'a IndexMetadata) -> Self {
Self { url: &value.url }
}
}
impl<'a> From<&'a IndexUrl> for IndexMetadataRef<'a> {
fn from(value: &'a IndexUrl) -> Self {
Self { url: value }
}
}
impl<'a> From<&'a Index> for IndexMetadataRef<'a> {
fn from(value: &'a Index) -> Self {
Self { url: &value.url }
}
}
impl From<IndexUrl> for IndexMetadata {
fn from(value: IndexUrl) -> Self {
Self { url: value }
}
}
/// An error that can occur when parsing an [`Index`]. /// An error that can occur when parsing an [`Index`].
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum IndexSourceError { pub enum IndexSourceError {

View file

@ -9,7 +9,7 @@ use uv_small_str::SmallString;
/// The normalized name of an index. /// The normalized name of an index.
/// ///
/// Index names may contain letters, digits, hyphens, underscores, and periods, and must be ASCII. /// Index names may contain letters, digits, hyphens, underscores, and periods, and must be ASCII.
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize)] #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, serde::Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct IndexName(SmallString); pub struct IndexName(SmallString);

View file

@ -1,5 +1,5 @@
/// The origin of a piece of configuration. /// The origin of a piece of configuration.
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub enum Origin { pub enum Origin {
/// The setting was provided via the CLI. /// The setting was provided via the CLI.
Cli, Cli,

View file

@ -14,6 +14,9 @@ use uv_pep440::VersionSpecifiers;
use uv_pep508::{ use uv_pep508::{
marker, MarkerEnvironment, MarkerTree, RequirementOrigin, VerbatimUrl, VersionOrUrl, marker, MarkerEnvironment, MarkerTree, RequirementOrigin, VerbatimUrl, VersionOrUrl,
}; };
use crate::{IndexMetadata, IndexUrl};
use uv_pypi_types::{ use uv_pypi_types::{
ConflictItem, Hashes, ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ConflictItem, Hashes, ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl,
ParsedUrl, ParsedUrlError, VerbatimParsedUrl, ParsedUrl, ParsedUrlError, VerbatimParsedUrl,
@ -320,7 +323,7 @@ impl Display for Requirement {
} => { } => {
write!(f, "{specifier}")?; write!(f, "{specifier}")?;
if let Some(index) = index { if let Some(index) = index {
write!(f, " (index: {index})")?; write!(f, " (index: {})", index.url)?;
} }
} }
RequirementSource::Url { url, .. } => { RequirementSource::Url { url, .. } => {
@ -368,7 +371,7 @@ pub enum RequirementSource {
Registry { Registry {
specifier: VersionSpecifiers, specifier: VersionSpecifiers,
/// Choose a version from the index at the given URL. /// Choose a version from the index at the given URL.
index: Option<Url>, index: Option<IndexMetadata>,
/// The conflict item associated with the source, if any. /// The conflict item associated with the source, if any.
conflict: Option<ConflictItem>, conflict: Option<ConflictItem>,
}, },
@ -600,7 +603,7 @@ impl Display for RequirementSource {
} => { } => {
write!(f, "{specifier}")?; write!(f, "{specifier}")?;
if let Some(index) = index { if let Some(index) = index {
write!(f, " (index: {index})")?; write!(f, " (index: {})", index.url)?;
} }
} }
Self::Url { url, .. } => { Self::Url { url, .. } => {
@ -662,12 +665,13 @@ impl From<RequirementSource> for RequirementSourceWire {
match value { match value {
RequirementSource::Registry { RequirementSource::Registry {
specifier, specifier,
mut index, index,
conflict, conflict,
} => { } => {
if let Some(index) = index.as_mut() { let index = index.map(|index| index.url.into_url()).map(|mut index| {
redact_credentials(index); redact_credentials(&mut index);
} index
});
Self::Registry { Self::Registry {
specifier, specifier,
index, index,
@ -775,7 +779,8 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
conflict, conflict,
} => Ok(Self::Registry { } => Ok(Self::Registry {
specifier, specifier,
index, index: index
.map(|index| IndexMetadata::from(IndexUrl::from(VerbatimUrl::from_url(index)))),
conflict, conflict,
}), }),
RequirementSourceWire::Git { git } => { RequirementSourceWire::Git { git } => {

View file

@ -3,7 +3,9 @@ use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep508::MarkerTree; use uv_pep508::MarkerTree;
use uv_pypi_types::{HashDigest, HashDigests}; use uv_pypi_types::{HashDigest, HashDigests};
use crate::{BuiltDist, Diagnostic, Dist, Name, RequirementSource, ResolvedDist, SourceDist}; use crate::{
BuiltDist, Diagnostic, Dist, IndexMetadata, Name, RequirementSource, ResolvedDist, SourceDist,
};
/// A set of packages pinned at specific versions. /// A set of packages pinned at specific versions.
/// ///
@ -229,7 +231,7 @@ impl From<&ResolvedDist> for RequirementSource {
wheel.filename.version.clone(), wheel.filename.version.clone(),
), ),
), ),
index: Some(wheel.index.url().clone()), index: Some(IndexMetadata::from(wheel.index.clone())),
conflict: None, conflict: None,
} }
} }
@ -252,7 +254,7 @@ impl From<&ResolvedDist> for RequirementSource {
specifier: uv_pep440::VersionSpecifiers::from( specifier: uv_pep440::VersionSpecifiers::from(
uv_pep440::VersionSpecifier::equals_version(sdist.version.clone()), uv_pep440::VersionSpecifier::equals_version(sdist.version.clone()),
), ),
index: Some(sdist.index.url().clone()), index: Some(IndexMetadata::from(sdist.index.clone())),
conflict: None, conflict: None,
}, },
Dist::Source(SourceDist::DirectUrl(sdist)) => { Dist::Source(SourceDist::DirectUrl(sdist)) => {

View file

@ -8,7 +8,7 @@ use url::Url;
use uv_distribution_filename::DistExtension; use uv_distribution_filename::DistExtension;
use uv_distribution_types::{ use uv_distribution_types::{
Index, IndexLocations, IndexName, Origin, Requirement, RequirementSource, Index, IndexLocations, IndexMetadata, IndexName, Origin, Requirement, RequirementSource,
}; };
use uv_git_types::{GitReference, GitUrl, GitUrlParseError}; use uv_git_types::{GitReference, GitUrl, GitUrlParseError};
use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_normalize::{ExtraName, GroupName, PackageName};
@ -222,7 +222,9 @@ impl LoweredRequirement {
.find(|Index { name, .. }| { .find(|Index { name, .. }| {
name.as_ref().is_some_and(|name| *name == index) name.as_ref().is_some_and(|name| *name == index)
}) })
.map(|Index { url: index, .. }| index.clone()) .map(|index| IndexMetadata {
url: index.url.clone(),
})
else { else {
return Err(LoweringError::MissingIndex( return Err(LoweringError::MissingIndex(
requirement.name.clone(), requirement.name.clone(),
@ -238,7 +240,7 @@ impl LoweredRequirement {
}) })
} }
}); });
let source = registry_source(&requirement, index.into_url(), conflict); let source = registry_source(&requirement, index, conflict);
(source, marker) (source, marker)
} }
Source::Workspace { Source::Workspace {
@ -445,7 +447,9 @@ impl LoweredRequirement {
.find(|Index { name, .. }| { .find(|Index { name, .. }| {
name.as_ref().is_some_and(|name| *name == index) name.as_ref().is_some_and(|name| *name == index)
}) })
.map(|Index { url: index, .. }| index.clone()) .map(|index| IndexMetadata {
url: index.url.clone(),
})
else { else {
return Err(LoweringError::MissingIndex( return Err(LoweringError::MissingIndex(
requirement.name.clone(), requirement.name.clone(),
@ -453,7 +457,7 @@ impl LoweredRequirement {
)); ));
}; };
let conflict = None; let conflict = None;
let source = registry_source(&requirement, index.into_url(), conflict); let source = registry_source(&requirement, index, conflict);
(source, marker) (source, marker)
} }
Source::Workspace { .. } => { Source::Workspace { .. } => {
@ -627,7 +631,7 @@ fn url_source(
/// Convert a registry source into a [`RequirementSource`]. /// Convert a registry source into a [`RequirementSource`].
fn registry_source( fn registry_source(
requirement: &uv_pep508::Requirement<VerbatimParsedUrl>, requirement: &uv_pep508::Requirement<VerbatimParsedUrl>,
index: Url, index: IndexMetadata,
conflict: Option<ConflictItem>, conflict: Option<ConflictItem>,
) -> RequirementSource { ) -> RequirementSource {
match &requirement.version_or_url { match &requirement.version_or_url {

View file

@ -479,7 +479,7 @@ pub async fn check_url(
let response = match registry_client let response = match registry_client
.simple( .simple(
filename.name(), filename.name(),
Some(index_url), Some(index_url.into()),
index_capabilities, index_capabilities,
download_concurrency, download_concurrency,
) )

View file

@ -1,5 +1,5 @@
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use uv_distribution_types::IndexUrl; use uv_distribution_types::IndexMetadata;
use uv_normalize::PackageName; use uv_normalize::PackageName;
use crate::resolver::ResolverEnvironment; use crate::resolver::ResolverEnvironment;
@ -7,24 +7,24 @@ use crate::ResolveError;
/// See [`crate::resolver::ForkState`]. /// See [`crate::resolver::ForkState`].
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub(crate) struct ForkIndexes(FxHashMap<PackageName, IndexUrl>); pub(crate) struct ForkIndexes(FxHashMap<PackageName, IndexMetadata>);
impl ForkIndexes { impl ForkIndexes {
/// Get the [`IndexUrl`] previously used for a package in this fork. /// Get the [`Index`] previously used for a package in this fork.
pub(crate) fn get(&self, package_name: &PackageName) -> Option<&IndexUrl> { pub(crate) fn get(&self, package_name: &PackageName) -> Option<&IndexMetadata> {
self.0.get(package_name) self.0.get(package_name)
} }
/// Check that this is the only [`IndexUrl`] used for this package in this fork. /// Check that this is the only [`Index`] used for this package in this fork.
pub(crate) fn insert( pub(crate) fn insert(
&mut self, &mut self,
package_name: &PackageName, package_name: &PackageName,
index: &IndexUrl, index: &IndexMetadata,
env: &ResolverEnvironment, env: &ResolverEnvironment,
) -> Result<(), ResolveError> { ) -> Result<(), ResolveError> {
if let Some(previous) = self.0.insert(package_name.clone(), index.clone()) { if let Some(previous) = self.0.insert(package_name.clone(), index.clone()) {
if &previous != index { if &previous != index {
let mut conflicts = vec![previous.to_string(), index.to_string()]; let mut conflicts = vec![previous.url.to_string(), index.url.to_string()];
conflicts.sort(); conflicts.sort();
return Err(ResolveError::ConflictingIndexesForEnvironment { return Err(ResolveError::ConflictingIndexesForEnvironment {
package_name: package_name.clone(), package_name: package_name.clone(),

View file

@ -27,9 +27,9 @@ use uv_distribution_filename::{
use uv_distribution_types::{ use uv_distribution_types::{
redact_credentials, BuiltDist, DependencyMetadata, DirectUrlBuiltDist, DirectUrlSourceDist, redact_credentials, BuiltDist, DependencyMetadata, DirectUrlBuiltDist, DirectUrlSourceDist,
DirectorySourceDist, Dist, DistributionMetadata, FileLocation, GitSourceDist, IndexLocations, DirectorySourceDist, Dist, DistributionMetadata, FileLocation, GitSourceDist, IndexLocations,
IndexUrl, Name, PathBuiltDist, PathSourceDist, RegistryBuiltDist, RegistryBuiltWheel, IndexMetadata, IndexUrl, Name, PathBuiltDist, PathSourceDist, RegistryBuiltDist,
RegistrySourceDist, RemoteSource, Requirement, RequirementSource, ResolvedDist, StaticMetadata, RegistryBuiltWheel, RegistrySourceDist, RemoteSource, Requirement, RequirementSource,
ToUrlError, UrlString, ResolvedDist, StaticMetadata, ToUrlError, UrlString,
}; };
use uv_fs::{relative_to, PortablePath, PortablePathBuf}; use uv_fs::{relative_to, PortablePath, PortablePathBuf};
use uv_git::{RepositoryReference, ResolvedRepositoryReference}; use uv_git::{RepositoryReference, ResolvedRepositoryReference};
@ -4563,12 +4563,17 @@ fn normalize_requirement(
} }
RequirementSource::Registry { RequirementSource::Registry {
specifier, specifier,
mut index, index,
conflict, conflict,
} => { } => {
if let Some(index) = index.as_mut() { // Round-trip the index to remove anything apart from the URL.
redact_credentials(index); let index = index
} .map(|index| index.url.into_url())
.map(|mut index| {
redact_credentials(&mut index);
index
})
.map(|index| IndexMetadata::from(IndexUrl::from(VerbatimUrl::from_url(index))));
Ok(Requirement { Ok(Requirement {
name: requirement.name, name: requirement.name,
extras: requirement.extras, extras: requirement.extras,

View file

@ -11,7 +11,7 @@ use rustc_hash::FxHashMap;
use uv_configuration::{IndexStrategy, NoBinary, NoBuild}; use uv_configuration::{IndexStrategy, NoBinary, NoBuild};
use uv_distribution_types::{ use uv_distribution_types::{
IncompatibleDist, IncompatibleSource, IncompatibleWheel, Index, IndexCapabilities, IncompatibleDist, IncompatibleSource, IncompatibleWheel, Index, IndexCapabilities,
IndexLocations, IndexUrl, IndexLocations, IndexMetadata, IndexUrl,
}; };
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::{Version, VersionSpecifiers}; use uv_pep440::{Version, VersionSpecifiers};
@ -727,7 +727,7 @@ impl PubGrubReportFormatter<'_> {
env: &ResolverEnvironment, env: &ResolverEnvironment,
tags: Option<&Tags>, tags: Option<&Tags>,
) -> Option<PubGrubHint> { ) -> Option<PubGrubHint> {
let response = if let Some(url) = fork_indexes.get(name) { let response = if let Some(url) = fork_indexes.get(name).map(IndexMetadata::url) {
index.explicit().get(&(name.clone(), url.clone())) index.explicit().get(&(name.clone(), url.clone()))
} else { } else {
index.implicit().get(name) index.implicit().get(name)

View file

@ -13,7 +13,9 @@ use crate::resolver::Request;
use crate::{ use crate::{
InMemoryIndex, PythonRequirement, ResolveError, ResolverEnvironment, VersionsResponse, InMemoryIndex, PythonRequirement, ResolveError, ResolverEnvironment, VersionsResponse,
}; };
use uv_distribution_types::{CompatibleDist, DistributionMetadata, IndexCapabilities, IndexUrl}; use uv_distribution_types::{
CompatibleDist, DistributionMetadata, IndexCapabilities, IndexMetadata,
};
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::Version; use uv_pep440::Version;
use uv_pep508::MarkerTree; use uv_pep508::MarkerTree;
@ -81,7 +83,7 @@ impl BatchPrefetcher {
pub(crate) fn prefetch_batches( pub(crate) fn prefetch_batches(
&mut self, &mut self,
next: &PubGrubPackage, next: &PubGrubPackage,
index: Option<&IndexUrl>, index: Option<&IndexMetadata>,
version: &Version, version: &Version,
current_range: &Range<Version>, current_range: &Range<Version>,
unchangeable_constraints: Option<&Term<Range<Version>>>, unchangeable_constraints: Option<&Term<Range<Version>>>,
@ -110,7 +112,7 @@ impl BatchPrefetcher {
self.prefetch_runner self.prefetch_runner
.index .index
.explicit() .explicit()
.wait_blocking(&(name.clone(), index.clone())) .wait_blocking(&(name.clone(), index.url().clone()))
.ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))? .ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?
} else { } else {
self.prefetch_runner self.prefetch_runner

View file

@ -1,6 +1,5 @@
use uv_distribution_types::{IndexUrl, RequirementSource}; use uv_distribution_types::{IndexMetadata, RequirementSource};
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep508::VerbatimUrl;
use uv_pypi_types::ConflictItem; use uv_pypi_types::ConflictItem;
use crate::resolver::ForkMap; use crate::resolver::ForkMap;
@ -24,7 +23,7 @@ pub(crate) struct Indexes(ForkMap<Entry>);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct Entry { struct Entry {
index: IndexUrl, index: IndexMetadata,
conflict: Option<ConflictItem>, conflict: Option<ConflictItem>,
} }
@ -46,7 +45,9 @@ impl Indexes {
else { else {
continue; continue;
}; };
let index = IndexUrl::from(VerbatimUrl::from_url(index.clone())); let index = IndexMetadata {
url: index.url.clone(),
};
let conflict = conflict.clone(); let conflict = conflict.clone();
indexes.add(&requirement, Entry { index, conflict }); indexes.add(&requirement, Entry { index, conflict });
} }
@ -60,7 +61,7 @@ impl Indexes {
} }
/// Return the explicit index used for a package in the given fork. /// Return the explicit index used for a package in the given fork.
pub(crate) fn get(&self, name: &PackageName, env: &ResolverEnvironment) -> Vec<&IndexUrl> { pub(crate) fn get(&self, name: &PackageName, env: &ResolverEnvironment) -> Vec<&IndexMetadata> {
let entries = self.0.get(name, env); let entries = self.0.get(name, env);
entries entries
.iter() .iter()

View file

@ -25,8 +25,8 @@ use uv_distribution::DistributionDatabase;
use uv_distribution_types::{ use uv_distribution_types::{
BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, DistributionMetadata, BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, DistributionMetadata,
IncompatibleDist, IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, IncompatibleDist, IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations,
IndexUrl, InstalledDist, PythonRequirementKind, RemoteSource, Requirement, ResolvedDist, IndexMetadata, IndexUrl, InstalledDist, PythonRequirementKind, RemoteSource, Requirement,
ResolvedDistRef, SourceDist, VersionOrUrlRef, ResolvedDist, ResolvedDistRef, SourceDist, VersionOrUrlRef,
}; };
use uv_git::GitResolver; use uv_git::GitResolver;
use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_normalize::{ExtraName, GroupName, PackageName};
@ -494,7 +494,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
let decision = self.choose_version( let decision = self.choose_version(
next_package, next_package,
next_id, next_id,
index, index.map(IndexMetadata::url),
term_intersection.unwrap_positive(), term_intersection.unwrap_positive(),
&mut state.pins, &mut state.pins,
&preferences, &preferences,
@ -925,7 +925,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&self, &self,
package: &PubGrubPackage, package: &PubGrubPackage,
url: Option<&VerbatimParsedUrl>, url: Option<&VerbatimParsedUrl>,
index: Option<&IndexUrl>, index: Option<&IndexMetadata>,
request_sink: &Sender<Request>, request_sink: &Sender<Request>,
) -> Result<(), ResolveError> { ) -> Result<(), ResolveError> {
// Ignore unresolved URL packages, i.e., packages that use a direct URL in some forks. // Ignore unresolved URL packages, i.e., packages that use a direct URL in some forks.
@ -945,7 +945,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&self, &self,
package: &PubGrubPackage, package: &PubGrubPackage,
url: Option<&VerbatimParsedUrl>, url: Option<&VerbatimParsedUrl>,
index: Option<&IndexUrl>, index: Option<&IndexMetadata>,
request_sink: &Sender<Request>, request_sink: &Sender<Request>,
) -> Result<(), ResolveError> { ) -> Result<(), ResolveError> {
// Only request real packages. // Only request real packages.
@ -969,7 +969,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
if self if self
.index .index
.explicit() .explicit()
.register((name.clone(), index.clone())) .register((name.clone(), index.url().clone()))
{ {
request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?; request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
} }
@ -2249,7 +2249,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
Ok(Some(Response::Package( Ok(Some(Response::Package(
package_name, package_name,
index, index.map(IndexMetadata::into_url),
package_versions, package_versions,
))) )))
} }
@ -2449,7 +2449,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
continue; continue;
} }
let versions_response = if let Some(index) = fork_indexes.get(name) { let versions_response = if let Some(index) = fork_indexes.get(name) {
self.index.explicit().get(&(name.clone(), index.clone())) self.index
.explicit()
.get(&(name.clone(), index.url().clone()))
} else { } else {
self.index.implicit().get(name) self.index.implicit().get(name)
}; };
@ -3166,7 +3168,7 @@ impl ResolutionDependencyEdge {
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub(crate) enum Request { pub(crate) enum Request {
/// A request to fetch the metadata for a package. /// A request to fetch the metadata for a package.
Package(PackageName, Option<IndexUrl>), Package(PackageName, Option<IndexMetadata>),
/// A request to fetch the metadata for a built or source distribution. /// A request to fetch the metadata for a built or source distribution.
Dist(Dist), Dist(Dist),
/// A request to fetch the metadata from an already-installed distribution. /// A request to fetch the metadata from an already-installed distribution.

View file

@ -3,7 +3,9 @@ use std::sync::Arc;
use uv_configuration::BuildOptions; use uv_configuration::BuildOptions;
use uv_distribution::{ArchiveMetadata, DistributionDatabase, Reporter}; use uv_distribution::{ArchiveMetadata, DistributionDatabase, Reporter};
use uv_distribution_types::{Dist, IndexCapabilities, IndexUrl, InstalledDist, RequestedDist}; use uv_distribution_types::{
Dist, IndexCapabilities, IndexMetadata, IndexMetadataRef, InstalledDist, RequestedDist,
};
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::{Version, VersionSpecifiers}; use uv_pep440::{Version, VersionSpecifiers};
use uv_platform_tags::Tags; use uv_platform_tags::Tags;
@ -78,7 +80,7 @@ pub trait ResolverProvider {
fn get_package_versions<'io>( fn get_package_versions<'io>(
&'io self, &'io self,
package_name: &'io PackageName, package_name: &'io PackageName,
index: Option<&'io IndexUrl>, index: Option<&'io IndexMetadata>,
) -> impl Future<Output = PackageVersionsResult> + 'io; ) -> impl Future<Output = PackageVersionsResult> + 'io;
/// Get the metadata for a distribution. /// Get the metadata for a distribution.
@ -150,13 +152,18 @@ impl<Context: BuildContext> ResolverProvider for DefaultResolverProvider<'_, Con
async fn get_package_versions<'io>( async fn get_package_versions<'io>(
&'io self, &'io self,
package_name: &'io PackageName, package_name: &'io PackageName,
index: Option<&'io IndexUrl>, index: Option<&'io IndexMetadata>,
) -> PackageVersionsResult { ) -> PackageVersionsResult {
let result = self let result = self
.fetcher .fetcher
.client() .client()
.manual(|client, semaphore| { .manual(|client, semaphore| {
client.simple(package_name, index, self.capabilities, semaphore) client.simple(
package_name,
index.map(IndexMetadataRef::from),
self.capabilities,
semaphore,
)
}) })
.await; .await;
@ -171,7 +178,7 @@ impl<Context: BuildContext> ResolverProvider for DefaultResolverProvider<'_, Con
VersionMap::from_metadata( VersionMap::from_metadata(
metadata, metadata,
package_name, package_name,
index, index.url(),
self.tags.as_ref(), self.tags.as_ref(),
&self.requires_python, &self.requires_python,
&self.allowed_yanks, &self.allowed_yanks,

View file

@ -2,7 +2,7 @@ use tokio::sync::Semaphore;
use tracing::debug; use tracing::debug;
use uv_client::{RegistryClient, VersionFiles}; use uv_client::{RegistryClient, VersionFiles};
use uv_distribution_filename::DistFilename; use uv_distribution_filename::DistFilename;
use uv_distribution_types::{IndexCapabilities, IndexUrl}; use uv_distribution_types::{IndexCapabilities, IndexMetadataRef, IndexUrl};
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_platform_tags::Tags; use uv_platform_tags::Tags;
use uv_resolver::{ExcludeNewer, PrereleaseMode, RequiresPython}; use uv_resolver::{ExcludeNewer, PrereleaseMode, RequiresPython};
@ -34,7 +34,12 @@ impl LatestClient<'_> {
let archives = match self let archives = match self
.client .client
.simple(package, index, self.capabilities, download_concurrency) .simple(
package,
index.map(IndexMetadataRef::from),
self.capabilities,
download_concurrency,
)
.await .await
{ {
Ok(archives) => archives, Ok(archives) => archives,

View file

@ -1,17 +1,15 @@
use crate::commands::reporters::PublishReporter;
use crate::commands::{human_readable_bytes, ExitStatus};
use crate::printer::Printer;
use crate::settings::NetworkSettings;
use anyhow::{bail, Context, Result};
use console::Term;
use owo_colors::OwoColorize;
use std::fmt::Write; use std::fmt::Write;
use std::iter; use std::iter;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use anyhow::{bail, Context, Result};
use console::Term;
use owo_colors::OwoColorize;
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
use tracing::{debug, info}; use tracing::{debug, info};
use url::Url; use url::Url;
use uv_cache::Cache; use uv_cache::Cache;
use uv_client::{AuthIntegration, BaseClient, BaseClientBuilder, RegistryClientBuilder}; use uv_client::{AuthIntegration, BaseClient, BaseClientBuilder, RegistryClientBuilder};
use uv_configuration::{KeyringProviderType, TrustedPublishing}; use uv_configuration::{KeyringProviderType, TrustedPublishing};
@ -21,6 +19,11 @@ use uv_publish::{
}; };
use uv_warnings::warn_user_once; use uv_warnings::warn_user_once;
use crate::commands::reporters::PublishReporter;
use crate::commands::{human_readable_bytes, ExitStatus};
use crate::printer::Printer;
use crate::settings::NetworkSettings;
pub(crate) async fn publish( pub(crate) async fn publish(
paths: Vec<String>, paths: Vec<String>,
publish_url: Url, publish_url: Url,

View file

@ -15331,7 +15331,7 @@ fn lock_explicit_default_index() -> Result<()> {
DEBUG No workspace root found, using project root DEBUG No workspace root found, using project root
DEBUG Ignoring existing lockfile due to mismatched requirements for: `project==0.1.0` DEBUG Ignoring existing lockfile due to mismatched requirements for: `project==0.1.0`
Requested: {Requirement { name: PackageName("anyio"), extras: [], groups: [], marker: true, source: Registry { specifier: VersionSpecifiers([]), index: None, conflict: None }, origin: None }} Requested: {Requirement { name: PackageName("anyio"), extras: [], groups: [], marker: true, source: Registry { specifier: VersionSpecifiers([]), index: None, conflict: None }, origin: None }}
Existing: {Requirement { name: PackageName("iniconfig"), extras: [], groups: [], marker: true, source: Registry { specifier: VersionSpecifiers([VersionSpecifier { operator: Equal, version: "2.0.0" }]), index: Some(Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("test.pypi.org")), port: None, path: "/simple", query: None, fragment: None }), conflict: None }, origin: None }} Existing: {Requirement { name: PackageName("iniconfig"), extras: [], groups: [], marker: true, source: Registry { specifier: VersionSpecifiers([VersionSpecifier { operator: Equal, version: "2.0.0" }]), index: Some(IndexMetadata { url: Url(VerbatimUrl { url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("test.pypi.org")), port: None, path: "/simple", query: None, fragment: None }, given: None }) }), conflict: None }, origin: None }}
DEBUG Solving with installed Python version: 3.12.[X] DEBUG Solving with installed Python version: 3.12.[X]
DEBUG Solving with target Python version: >=3.12 DEBUG Solving with target Python version: >=3.12
DEBUG Adding direct dependency: project* DEBUG Adding direct dependency: project*