mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-25 21:37:51 +00:00
Support --find-links-style "flat" indexes in [[tool.uv.index]] (#12407)
Some checks are pending
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
Some checks are pending
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
## Summary This PR extends `[[tool.uv.index]]` to support `--find-links`-style "flat" indexes, so that users can point to such indexes without using `--find-links` _and_ get access to the full functionality of `[[tool.uv.index]]` (e.g., they can now pin packages to `--find-links`-style indexes). Note that, at present, `--find-links` indexes actually have some quirky behavior, in that we combine them into a single entity and then merge the discovered distributions into each Simple API-style index. The motivation here, IIRC, was to match pip's behavior quite closely. I'm interested in _removing_ that behavior, but it'd be breaking (and may also be inconvenient for some use-cases). So, the behavior for indexes passed in via `--find-links` remains completely unchanged. However, `[[tool.uv.index]]` entries with `format = "flat"` are now treated identically to those defined with `format = "simple"` (the default), in that we stop after we find the first-matching index, etc. Closes https://github.com/astral-sh/uv/issues/11634.
This commit is contained in:
parent
f2a2d982b8
commit
bd9c365b92
19 changed files with 826 additions and 122 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -4876,6 +4876,7 @@ dependencies = [
|
|||
"reqwest-retry",
|
||||
"rkyv",
|
||||
"rmp-serde",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sys-info",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ reqwest-middleware = { workspace = true }
|
|||
reqwest-retry = { workspace = true }
|
||||
rkyv = { workspace = true }
|
||||
rmp-serde = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sys-info = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ use url::Url;
|
|||
use uv_distribution_filename::{WheelFilename, WheelFilenameError};
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::html;
|
||||
use crate::middleware::OfflineError;
|
||||
use crate::{html, FlatIndexError};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
|
|
@ -155,6 +155,9 @@ pub enum ErrorKind {
|
|||
#[error(transparent)]
|
||||
JoinRelativeUrl(#[from] uv_pypi_types::JoinRelativeError),
|
||||
|
||||
#[error(transparent)]
|
||||
Flat(#[from] FlatIndexError),
|
||||
|
||||
#[error("Expected a file URL, but received: {0}")]
|
||||
NonFileUrl(Url),
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ pub use base_client::{
|
|||
};
|
||||
pub use cached_client::{CacheControl, CachedClient, CachedClientError, DataWithCachePolicy};
|
||||
pub use error::{Error, ErrorKind, WrappedReqwestError};
|
||||
pub use flat_index::{FlatIndexClient, FlatIndexEntries, FlatIndexError};
|
||||
pub use flat_index::{FlatIndexClient, FlatIndexEntries, FlatIndexEntry, FlatIndexError};
|
||||
pub use linehaul::LineHaul;
|
||||
pub use registry_client::{
|
||||
Connectivity, RegistryClient, RegistryClientBuilder, SimpleMetadata, SimpleMetadatum,
|
||||
VersionFiles,
|
||||
Connectivity, MetadataFormat, RegistryClient, RegistryClientBuilder, SimpleMetadata,
|
||||
SimpleMetadatum, VersionFiles,
|
||||
};
|
||||
pub use rkyvutil::{Deserializer, OwnedArchive, Serializer, Validator};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use std::collections::BTreeMap;
|
|||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_http_range_reader::AsyncHttpRangeReader;
|
||||
|
|
@ -10,7 +11,8 @@ use http::HeaderMap;
|
|||
use itertools::Either;
|
||||
use reqwest::{Proxy, Response, StatusCode};
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use tokio::sync::Semaphore;
|
||||
use rustc_hash::FxHashMap;
|
||||
use tokio::sync::{Mutex, Semaphore};
|
||||
use tracing::{info_span, instrument, trace, warn, Instrument};
|
||||
use url::Url;
|
||||
|
||||
|
|
@ -20,7 +22,8 @@ use uv_configuration::KeyringProviderType;
|
|||
use uv_configuration::{IndexStrategy, TrustedHost};
|
||||
use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename};
|
||||
use uv_distribution_types::{
|
||||
BuiltDist, File, FileLocation, IndexCapabilities, IndexMetadataRef, IndexUrl, IndexUrls, Name,
|
||||
BuiltDist, File, FileLocation, IndexCapabilities, IndexFormat, IndexMetadataRef, IndexUrl,
|
||||
IndexUrls, Name,
|
||||
};
|
||||
use uv_metadata::{read_metadata_async_seek, read_metadata_async_stream};
|
||||
use uv_normalize::PackageName;
|
||||
|
|
@ -33,10 +36,14 @@ use uv_torch::TorchStrategy;
|
|||
|
||||
use crate::base_client::{BaseClientBuilder, ExtraMiddleware};
|
||||
use crate::cached_client::CacheControl;
|
||||
use crate::flat_index::FlatIndexEntry;
|
||||
use crate::html::SimpleHtml;
|
||||
use crate::remote_metadata::wheel_metadata_from_remote_zip;
|
||||
use crate::rkyvutil::OwnedArchive;
|
||||
use crate::{BaseClient, CachedClient, CachedClientError, Error, ErrorKind};
|
||||
use crate::{
|
||||
BaseClient, CachedClient, CachedClientError, Error, ErrorKind, FlatIndexClient,
|
||||
FlatIndexEntries,
|
||||
};
|
||||
|
||||
/// A builder for an [`RegistryClient`].
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -169,6 +176,7 @@ impl<'a> RegistryClientBuilder<'a> {
|
|||
connectivity,
|
||||
client,
|
||||
timeout,
|
||||
flat_indexes: Arc::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +199,7 @@ impl<'a> RegistryClientBuilder<'a> {
|
|||
connectivity,
|
||||
client,
|
||||
timeout,
|
||||
flat_indexes: Arc::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -226,6 +235,17 @@ pub struct RegistryClient {
|
|||
connectivity: Connectivity,
|
||||
/// Configured client timeout, in seconds.
|
||||
timeout: Duration,
|
||||
/// The flat index entries for each `--find-links`-style index URL.
|
||||
flat_indexes: Arc<Mutex<FlatIndexCache>>,
|
||||
}
|
||||
|
||||
/// The format of the package metadata returned by querying an index.
|
||||
#[derive(Debug)]
|
||||
pub enum MetadataFormat {
|
||||
/// The metadata adheres to the Simple Repository API format.
|
||||
Simple(OwnedArchive<SimpleMetadata>),
|
||||
/// The metadata consists of a list of distributions from a "flat" index.
|
||||
Flat(Vec<FlatIndexEntry>),
|
||||
}
|
||||
|
||||
impl RegistryClient {
|
||||
|
|
@ -280,19 +300,21 @@ impl RegistryClient {
|
|||
.unwrap_or(self.index_strategy)
|
||||
}
|
||||
|
||||
/// Fetch a package from the `PyPI` simple API.
|
||||
/// Fetch package metadata from an index.
|
||||
///
|
||||
/// "simple" here refers to [PEP 503 – Simple Repository API](https://peps.python.org/pep-0503/)
|
||||
/// Supports both the "Simple" API and `--find-links`-style flat indexes.
|
||||
///
|
||||
/// "Simple" here refers to [PEP 503 – Simple Repository API](https://peps.python.org/pep-0503/)
|
||||
/// and [PEP 691 – JSON-based Simple API for Python Package Indexes](https://peps.python.org/pep-0691/),
|
||||
/// which the pypi json api approximately implements.
|
||||
#[instrument("simple_api", skip_all, fields(package = % package_name))]
|
||||
pub async fn simple<'index>(
|
||||
/// which the PyPI JSON API implements.
|
||||
#[instrument(skip_all, fields(package = % package_name))]
|
||||
pub async fn package_metadata<'index>(
|
||||
&'index self,
|
||||
package_name: &PackageName,
|
||||
index: Option<IndexMetadataRef<'index>>,
|
||||
capabilities: &IndexCapabilities,
|
||||
download_concurrency: &Semaphore,
|
||||
) -> Result<Vec<(IndexMetadataRef<'index>, OwnedArchive<SimpleMetadata>)>, Error> {
|
||||
) -> Result<Vec<(&'index IndexUrl, MetadataFormat)>, Error> {
|
||||
// If `--no-index` is specified, avoid fetching regardless of whether the index is implicit,
|
||||
// explicit, etc.
|
||||
if self.index_urls.no_index() {
|
||||
|
|
@ -312,14 +334,25 @@ impl RegistryClient {
|
|||
IndexStrategy::FirstIndex => {
|
||||
for index in indexes {
|
||||
let _permit = download_concurrency.acquire().await;
|
||||
match index.format {
|
||||
IndexFormat::Simple => {
|
||||
if let Some(metadata) = self
|
||||
.simple_single_index(package_name, index.url(), capabilities)
|
||||
.simple_single_index(package_name, index.url, capabilities)
|
||||
.await?
|
||||
{
|
||||
results.push((index, metadata));
|
||||
results.push((index.url, MetadataFormat::Simple(metadata)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
IndexFormat::Flat => {
|
||||
let entries = self.flat_single_index(package_name, index.url).await?;
|
||||
if !entries.is_empty() {
|
||||
results.push((index.url, MetadataFormat::Flat(entries)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, fetch concurrently.
|
||||
|
|
@ -327,10 +360,19 @@ impl RegistryClient {
|
|||
results = futures::stream::iter(indexes)
|
||||
.map(|index| async move {
|
||||
let _permit = download_concurrency.acquire().await;
|
||||
match index.format {
|
||||
IndexFormat::Simple => {
|
||||
let metadata = self
|
||||
.simple_single_index(package_name, index.url(), capabilities)
|
||||
.simple_single_index(package_name, index.url, capabilities)
|
||||
.await?;
|
||||
Ok((index, metadata))
|
||||
Ok((index.url, metadata.map(MetadataFormat::Simple)))
|
||||
}
|
||||
IndexFormat::Flat => {
|
||||
let entries =
|
||||
self.flat_single_index(package_name, index.url).await?;
|
||||
Ok((index.url, Some(MetadataFormat::Flat(entries))))
|
||||
}
|
||||
}
|
||||
})
|
||||
.buffered(8)
|
||||
.filter_map(|result: Result<_, Error>| async move {
|
||||
|
|
@ -357,6 +399,46 @@ impl RegistryClient {
|
|||
Ok(results)
|
||||
}
|
||||
|
||||
/// Fetch the [`FlatIndexEntry`] entries for a given package from a single `--find-links` index.
|
||||
async fn flat_single_index(
|
||||
&self,
|
||||
package_name: &PackageName,
|
||||
index: &IndexUrl,
|
||||
) -> Result<Vec<FlatIndexEntry>, Error> {
|
||||
// Store the flat index entries in a cache, to avoid redundant fetches. A flat index will
|
||||
// typically contain entries for multiple packages; as such, it's more efficient to cache
|
||||
// the entire index rather than re-fetching it for each package.
|
||||
let mut cache = self.flat_indexes.lock().await;
|
||||
if let Some(entries) = cache.get(index) {
|
||||
return Ok(entries.get(package_name).cloned().unwrap_or_default());
|
||||
}
|
||||
|
||||
let client = FlatIndexClient::new(self.cached_client(), self.connectivity, &self.cache);
|
||||
|
||||
// Fetch the entries for the index.
|
||||
let FlatIndexEntries { entries, .. } =
|
||||
client.fetch_index(index).await.map_err(ErrorKind::Flat)?;
|
||||
|
||||
// Index by package name.
|
||||
let mut entries_by_package: FxHashMap<PackageName, Vec<FlatIndexEntry>> =
|
||||
FxHashMap::default();
|
||||
for entry in entries {
|
||||
entries_by_package
|
||||
.entry(entry.filename.name().clone())
|
||||
.or_default()
|
||||
.push(entry);
|
||||
}
|
||||
let package_entries = entries_by_package
|
||||
.get(package_name)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
// Write to the cache.
|
||||
cache.insert(index.clone(), entries_by_package);
|
||||
|
||||
Ok(package_entries)
|
||||
}
|
||||
|
||||
/// Fetch the [`SimpleMetadata`] from a single index for a given package.
|
||||
///
|
||||
/// The index can either be a PEP 503-compatible remote repository, or a local directory laid
|
||||
|
|
@ -883,6 +965,27 @@ impl RegistryClient {
|
|||
}
|
||||
}
|
||||
|
||||
/// A map from [`IndexUrl`] to [`FlatIndexEntry`] entries found at the given URL, indexed by
|
||||
/// [`PackageName`].
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct FlatIndexCache(FxHashMap<IndexUrl, FxHashMap<PackageName, Vec<FlatIndexEntry>>>);
|
||||
|
||||
impl FlatIndexCache {
|
||||
/// Get the entries for a given index URL.
|
||||
fn get(&self, index: &IndexUrl) -> Option<&FxHashMap<PackageName, Vec<FlatIndexEntry>>> {
|
||||
self.0.get(index)
|
||||
}
|
||||
|
||||
/// Insert the entries for a given index URL.
|
||||
fn insert(
|
||||
&mut self,
|
||||
index: IndexUrl,
|
||||
entries: FxHashMap<PackageName, Vec<FlatIndexEntry>>,
|
||||
) -> Option<FxHashMap<PackageName, Vec<FlatIndexEntry>>> {
|
||||
self.0.insert(index, entries)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub struct VersionFiles {
|
||||
|
|
|
|||
|
|
@ -64,13 +64,13 @@ pub struct Index {
|
|||
/// The origin of the index (e.g., a CLI flag, a user-level configuration file, etc.).
|
||||
#[serde(skip)]
|
||||
pub origin: Option<Origin>,
|
||||
// /// 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,
|
||||
/// The format used by the index.
|
||||
///
|
||||
/// Indexes can either be PEP 503-compliant (i.e., a PyPI-style 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 format: IndexFormat,
|
||||
/// The URL of the upload endpoint.
|
||||
///
|
||||
/// When using `uv publish --index <name>`, this URL is used for publishing.
|
||||
|
|
@ -96,17 +96,28 @@ pub struct Index {
|
|||
pub authenticate: AuthPolicy,
|
||||
}
|
||||
|
||||
// #[derive(
|
||||
// Default, Debug, Copy, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize,
|
||||
// )]
|
||||
// #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
// pub enum IndexKind {
|
||||
// /// A PEP 503 and/or PEP 691-compliant index.
|
||||
// #[default]
|
||||
// Simple,
|
||||
// /// An index containing a list of links to distributions (e.g., `--find-links`).
|
||||
// Flat,
|
||||
// }
|
||||
#[derive(
|
||||
Default,
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Hash,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum IndexFormat {
|
||||
/// A PyPI-style index implementing the Simple Repository API.
|
||||
#[default]
|
||||
Simple,
|
||||
/// A `--find-links`-style index containing a flat list of wheels and source distributions.
|
||||
Flat,
|
||||
}
|
||||
|
||||
impl Index {
|
||||
/// Initialize an [`Index`] from a pip-style `--index-url`.
|
||||
|
|
@ -117,6 +128,7 @@ impl Index {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: IndexFormat::Simple,
|
||||
publish_url: None,
|
||||
authenticate: AuthPolicy::default(),
|
||||
}
|
||||
|
|
@ -130,6 +142,7 @@ impl Index {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: IndexFormat::Simple,
|
||||
publish_url: None,
|
||||
authenticate: AuthPolicy::default(),
|
||||
}
|
||||
|
|
@ -143,6 +156,7 @@ impl Index {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: IndexFormat::Flat,
|
||||
publish_url: None,
|
||||
authenticate: AuthPolicy::default(),
|
||||
}
|
||||
|
|
@ -210,6 +224,7 @@ impl From<IndexUrl> for Index {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: IndexFormat::Simple,
|
||||
publish_url: None,
|
||||
authenticate: AuthPolicy::default(),
|
||||
}
|
||||
|
|
@ -231,6 +246,7 @@ impl FromStr for Index {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: IndexFormat::Simple,
|
||||
publish_url: None,
|
||||
authenticate: AuthPolicy::default(),
|
||||
});
|
||||
|
|
@ -245,6 +261,7 @@ impl FromStr for Index {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: IndexFormat::Simple,
|
||||
publish_url: None,
|
||||
authenticate: AuthPolicy::default(),
|
||||
})
|
||||
|
|
@ -254,21 +271,17 @@ 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 {
|
||||
/// The URL of the index.
|
||||
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,
|
||||
/// The format used by the index.
|
||||
pub format: IndexFormat,
|
||||
}
|
||||
|
||||
impl IndexMetadata {
|
||||
/// Return a reference to the [`IndexMetadata`].
|
||||
pub fn as_ref(&self) -> IndexMetadataRef<'_> {
|
||||
let Self { url } = self;
|
||||
IndexMetadataRef { url }
|
||||
let Self { url, format: kind } = self;
|
||||
IndexMetadataRef { url, format: *kind }
|
||||
}
|
||||
|
||||
/// Consume the [`IndexMetadata`] and return the [`IndexUrl`].
|
||||
|
|
@ -280,7 +293,10 @@ impl IndexMetadata {
|
|||
/// A reference to an [`IndexMetadata`].
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct IndexMetadataRef<'a> {
|
||||
/// The URL of the index.
|
||||
pub url: &'a IndexUrl,
|
||||
/// The format used by the index.
|
||||
pub format: IndexFormat,
|
||||
}
|
||||
|
||||
impl IndexMetadata {
|
||||
|
|
@ -297,27 +313,39 @@ impl IndexMetadataRef<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
Self {
|
||||
url: &value.url,
|
||||
format: value.format,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a IndexMetadata> for IndexMetadataRef<'a> {
|
||||
fn from(value: &'a IndexMetadata) -> Self {
|
||||
Self {
|
||||
url: &value.url,
|
||||
format: value.format,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IndexUrl> for IndexMetadata {
|
||||
fn from(value: IndexUrl) -> Self {
|
||||
Self { url: value }
|
||||
Self {
|
||||
url: value,
|
||||
format: IndexFormat::Simple,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a IndexUrl> for IndexMetadataRef<'a> {
|
||||
fn from(value: &'a IndexUrl) -> Self {
|
||||
Self {
|
||||
url: value,
|
||||
format: IndexFormat::Simple,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ impl schemars::JsonSchema for IndexUrl {
|
|||
schemars::schema::SchemaObject {
|
||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||
metadata: Some(Box::new(schemars::schema::Metadata {
|
||||
description: Some("The URL of an index to use for fetching packages (e.g., `https://pypi.org/simple`).".to_string()),
|
||||
description: Some("The URL of an index to use for fetching packages (e.g., `https://pypi.org/simple`), or a local path.".to_string()),
|
||||
..schemars::schema::Metadata::default()
|
||||
})),
|
||||
..schemars::schema::SchemaObject::default()
|
||||
|
|
|
|||
|
|
@ -222,9 +222,14 @@ impl LoweredRequirement {
|
|||
.find(|Index { name, .. }| {
|
||||
name.as_ref().is_some_and(|name| *name == index)
|
||||
})
|
||||
.map(|index| IndexMetadata {
|
||||
url: index.url.clone(),
|
||||
})
|
||||
.map(
|
||||
|Index {
|
||||
url, format: kind, ..
|
||||
}| IndexMetadata {
|
||||
url: url.clone(),
|
||||
format: *kind,
|
||||
},
|
||||
)
|
||||
else {
|
||||
return Err(LoweringError::MissingIndex(
|
||||
requirement.name.clone(),
|
||||
|
|
@ -447,9 +452,14 @@ impl LoweredRequirement {
|
|||
.find(|Index { name, .. }| {
|
||||
name.as_ref().is_some_and(|name| *name == index)
|
||||
})
|
||||
.map(|index| IndexMetadata {
|
||||
url: index.url.clone(),
|
||||
})
|
||||
.map(
|
||||
|Index {
|
||||
url, format: kind, ..
|
||||
}| IndexMetadata {
|
||||
url: url.clone(),
|
||||
format: *kind,
|
||||
},
|
||||
)
|
||||
else {
|
||||
return Err(LoweringError::MissingIndex(
|
||||
requirement.name.clone(),
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ use trusted_publishing::TrustedPublishingToken;
|
|||
use url::Url;
|
||||
use uv_cache::{Cache, Refresh};
|
||||
use uv_client::{
|
||||
BaseClient, OwnedArchive, RegistryClientBuilder, UvRetryableStrategy, DEFAULT_RETRIES,
|
||||
BaseClient, MetadataFormat, OwnedArchive, RegistryClientBuilder, UvRetryableStrategy,
|
||||
DEFAULT_RETRIES,
|
||||
};
|
||||
use uv_configuration::{KeyringProviderType, TrustedPublishing};
|
||||
use uv_distribution_filename::{DistFilename, SourceDistExtension, SourceDistFilename};
|
||||
|
|
@ -477,7 +478,7 @@ pub async fn check_url(
|
|||
|
||||
debug!("Checking for {filename} in the registry");
|
||||
let response = match registry_client
|
||||
.simple(
|
||||
.package_metadata(
|
||||
filename.name(),
|
||||
Some(index_url.into()),
|
||||
index_capabilities,
|
||||
|
|
@ -499,7 +500,7 @@ pub async fn check_url(
|
|||
};
|
||||
}
|
||||
};
|
||||
let [(_, simple_metadata)] = response.as_slice() else {
|
||||
let [(_, MetadataFormat::Simple(simple_metadata))] = response.as_slice() else {
|
||||
unreachable!("We queried a single index, we must get a single response");
|
||||
};
|
||||
let simple_metadata = OwnedArchive::deserialize(simple_metadata);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::collections::BTreeMap;
|
|||
use rustc_hash::FxHashMap;
|
||||
use tracing::instrument;
|
||||
|
||||
use uv_client::FlatIndexEntries;
|
||||
use uv_client::{FlatIndexEntries, FlatIndexEntry};
|
||||
use uv_configuration::BuildOptions;
|
||||
use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename};
|
||||
use uv_distribution_types::{
|
||||
|
|
@ -39,11 +39,10 @@ impl FlatIndex {
|
|||
build_options: &BuildOptions,
|
||||
) -> Self {
|
||||
// Collect compatible distributions.
|
||||
let mut index = FxHashMap::default();
|
||||
let mut index = FxHashMap::<PackageName, FlatDistributions>::default();
|
||||
for entry in entries.entries {
|
||||
let distributions = index.entry(entry.filename.name().clone()).or_default();
|
||||
Self::add_file(
|
||||
distributions,
|
||||
distributions.add_file(
|
||||
entry.file,
|
||||
entry.filename,
|
||||
tags,
|
||||
|
|
@ -59,8 +58,59 @@ impl FlatIndex {
|
|||
Self { index, offline }
|
||||
}
|
||||
|
||||
/// Get the [`FlatDistributions`] for the given package name.
|
||||
pub fn get(&self, package_name: &PackageName) -> Option<&FlatDistributions> {
|
||||
self.index.get(package_name)
|
||||
}
|
||||
|
||||
/// Whether any `--find-links` entries could not be resolved due to a lack of network
|
||||
/// connectivity.
|
||||
pub fn offline(&self) -> bool {
|
||||
self.offline
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of [`PrioritizedDist`] from a `--find-links` entry for a single package, indexed
|
||||
/// by [`Version`].
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FlatDistributions(BTreeMap<Version, PrioritizedDist>);
|
||||
|
||||
impl FlatDistributions {
|
||||
/// Collect all files from a `--find-links` target into a [`FlatIndex`].
|
||||
#[instrument(skip_all)]
|
||||
pub fn from_entries(
|
||||
entries: Vec<FlatIndexEntry>,
|
||||
tags: Option<&Tags>,
|
||||
hasher: &HashStrategy,
|
||||
build_options: &BuildOptions,
|
||||
) -> Self {
|
||||
let mut distributions = Self::default();
|
||||
for entry in entries {
|
||||
distributions.add_file(
|
||||
entry.file,
|
||||
entry.filename,
|
||||
tags,
|
||||
hasher,
|
||||
build_options,
|
||||
entry.index,
|
||||
);
|
||||
}
|
||||
distributions
|
||||
}
|
||||
|
||||
/// Returns an [`Iterator`] over the distributions.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&Version, &PrioritizedDist)> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
/// Removes the [`PrioritizedDist`] for the given version.
|
||||
pub fn remove(&mut self, version: &Version) -> Option<PrioritizedDist> {
|
||||
self.0.remove(version)
|
||||
}
|
||||
|
||||
/// Add the given [`File`] to the [`FlatDistributions`] for the given package.
|
||||
fn add_file(
|
||||
distributions: &mut FlatDistributions,
|
||||
&mut self,
|
||||
file: File,
|
||||
filename: DistFilename,
|
||||
tags: Option<&Tags>,
|
||||
|
|
@ -86,7 +136,7 @@ impl FlatIndex {
|
|||
file: Box::new(file),
|
||||
index,
|
||||
};
|
||||
match distributions.0.entry(version) {
|
||||
match self.0.entry(version) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().insert_built(dist, vec![], compatibility);
|
||||
}
|
||||
|
|
@ -110,7 +160,7 @@ impl FlatIndex {
|
|||
index,
|
||||
wheels: vec![],
|
||||
};
|
||||
match distributions.0.entry(filename.version) {
|
||||
match self.0.entry(filename.version) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().insert_source(dist, vec![], compatibility);
|
||||
}
|
||||
|
|
@ -194,31 +244,6 @@ impl FlatIndex {
|
|||
|
||||
WheelCompatibility::Compatible(hash, priority, build_tag)
|
||||
}
|
||||
|
||||
/// Get the [`FlatDistributions`] for the given package name.
|
||||
pub fn get(&self, package_name: &PackageName) -> Option<&FlatDistributions> {
|
||||
self.index.get(package_name)
|
||||
}
|
||||
|
||||
/// Returns `true` if there are any offline `--find-links` entries.
|
||||
pub fn offline(&self) -> bool {
|
||||
self.offline
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of [`PrioritizedDist`] from a `--find-links` entry for a single package, indexed
|
||||
/// by [`Version`].
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FlatDistributions(BTreeMap<Version, PrioritizedDist>);
|
||||
|
||||
impl FlatDistributions {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&Version, &PrioritizedDist)> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, version: &Version) -> Option<PrioritizedDist> {
|
||||
self.0.remove(version)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for FlatDistributions {
|
||||
|
|
|
|||
|
|
@ -45,9 +45,7 @@ impl Indexes {
|
|||
else {
|
||||
continue;
|
||||
};
|
||||
let index = IndexMetadata {
|
||||
url: index.url.clone(),
|
||||
};
|
||||
let index = index.clone();
|
||||
let conflict = conflict.clone();
|
||||
indexes.add(&requirement, Entry { index, conflict });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
use uv_client::MetadataFormat;
|
||||
use uv_configuration::BuildOptions;
|
||||
use uv_distribution::{ArchiveMetadata, DistributionDatabase, Reporter};
|
||||
use uv_distribution_types::{
|
||||
|
|
@ -158,7 +158,7 @@ impl<Context: BuildContext> ResolverProvider for DefaultResolverProvider<'_, Con
|
|||
.fetcher
|
||||
.client()
|
||||
.manual(|client, semaphore| {
|
||||
client.simple(
|
||||
client.package_metadata(
|
||||
package_name,
|
||||
index.map(IndexMetadataRef::from),
|
||||
self.capabilities,
|
||||
|
|
@ -174,11 +174,11 @@ impl<Context: BuildContext> ResolverProvider for DefaultResolverProvider<'_, Con
|
|||
Ok(results) => Ok(VersionsResponse::Found(
|
||||
results
|
||||
.into_iter()
|
||||
.map(|(index, metadata)| {
|
||||
VersionMap::from_metadata(
|
||||
.map(|(index, metadata)| match metadata {
|
||||
MetadataFormat::Simple(metadata) => VersionMap::from_simple_metadata(
|
||||
metadata,
|
||||
package_name,
|
||||
index.url(),
|
||||
index,
|
||||
self.tags.as_ref(),
|
||||
&self.requires_python,
|
||||
&self.allowed_yanks,
|
||||
|
|
@ -188,7 +188,13 @@ impl<Context: BuildContext> ResolverProvider for DefaultResolverProvider<'_, Con
|
|||
.and_then(|flat_index| flat_index.get(package_name))
|
||||
.cloned(),
|
||||
self.build_options,
|
||||
)
|
||||
),
|
||||
MetadataFormat::Flat(metadata) => VersionMap::from_flat_metadata(
|
||||
metadata,
|
||||
self.tags.as_ref(),
|
||||
&self.hasher,
|
||||
self.build_options,
|
||||
),
|
||||
})
|
||||
.collect(),
|
||||
)),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::sync::OnceLock;
|
|||
use pubgrub::Ranges;
|
||||
use tracing::instrument;
|
||||
|
||||
use uv_client::{OwnedArchive, SimpleMetadata, VersionFiles};
|
||||
use uv_client::{FlatIndexEntry, OwnedArchive, SimpleMetadata, VersionFiles};
|
||||
use uv_configuration::BuildOptions;
|
||||
use uv_distribution_filename::{DistFilename, WheelFilename};
|
||||
use uv_distribution_types::{
|
||||
|
|
@ -41,7 +41,7 @@ impl VersionMap {
|
|||
///
|
||||
/// PEP 592: <https://peps.python.org/pep-0592/#warehouse-pypi-implementation-notes>
|
||||
#[instrument(skip_all, fields(package_name))]
|
||||
pub(crate) fn from_metadata(
|
||||
pub(crate) fn from_simple_metadata(
|
||||
simple_metadata: OwnedArchive<SimpleMetadata>,
|
||||
package_name: &PackageName,
|
||||
index: &IndexUrl,
|
||||
|
|
@ -116,6 +116,30 @@ impl VersionMap {
|
|||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all, fields(package_name))]
|
||||
pub(crate) fn from_flat_metadata(
|
||||
flat_metadata: Vec<FlatIndexEntry>,
|
||||
tags: Option<&Tags>,
|
||||
hasher: &HashStrategy,
|
||||
build_options: &BuildOptions,
|
||||
) -> Self {
|
||||
let mut stable = false;
|
||||
let mut local = false;
|
||||
let mut map = BTreeMap::new();
|
||||
|
||||
for (version, prioritized_dist) in
|
||||
FlatDistributions::from_entries(flat_metadata, tags, hasher, build_options)
|
||||
{
|
||||
stable |= version.is_stable();
|
||||
local |= version.is_local();
|
||||
map.insert(version, prioritized_dist);
|
||||
}
|
||||
|
||||
Self {
|
||||
inner: VersionMapInner::Eager(VersionMapEager { map, stable, local }),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`DistFile`] for the given version, if any.
|
||||
pub(crate) fn get(&self, version: &Version) -> Option<&PrioritizedDist> {
|
||||
match self.inner {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use tokio::sync::Semaphore;
|
||||
use tracing::debug;
|
||||
use uv_client::{RegistryClient, VersionFiles};
|
||||
|
||||
use uv_client::{MetadataFormat, RegistryClient, VersionFiles};
|
||||
use uv_distribution_filename::DistFilename;
|
||||
use uv_distribution_types::{IndexCapabilities, IndexMetadataRef, IndexUrl};
|
||||
use uv_normalize::PackageName;
|
||||
|
|
@ -34,7 +35,7 @@ impl LatestClient<'_> {
|
|||
|
||||
let archives = match self
|
||||
.client
|
||||
.simple(
|
||||
.package_metadata(
|
||||
package,
|
||||
index.map(IndexMetadataRef::from),
|
||||
self.capabilities,
|
||||
|
|
@ -55,6 +56,10 @@ impl LatestClient<'_> {
|
|||
|
||||
let mut latest: Option<DistFilename> = None;
|
||||
for (_, archive) in archives {
|
||||
let MetadataFormat::Simple(archive) = archive else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for datum in archive.iter().rev() {
|
||||
// Find the first compatible distribution.
|
||||
let files = rkyv::deserialize::<VersionFiles, rkyv::rancor::Error>(&datum.files)
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ pub(crate) async fn tree(
|
|||
.packages()
|
||||
.iter()
|
||||
.filter_map(|package| {
|
||||
// TODO(charlie): We would need to know the format here.
|
||||
let index = match package.index(target.install_path()) {
|
||||
Ok(Some(index)) => index,
|
||||
Ok(None) => return None,
|
||||
|
|
@ -232,6 +233,7 @@ pub(crate) async fn tree(
|
|||
let download_concurrency = &download_concurrency;
|
||||
let mut fetches = futures::stream::iter(packages)
|
||||
.map(|(package, index)| async move {
|
||||
// This probably already doesn't work for `--find-links`?
|
||||
let Some(filename) = client
|
||||
.find_latest(package.name(), Some(&index), download_concurrency)
|
||||
.await?
|
||||
|
|
|
|||
|
|
@ -9551,7 +9551,7 @@ fn lock_find_links_local_wheel() -> Result<()> {
|
|||
|
||||
/// Prefer an explicit index over any `--find-links` entries.
|
||||
#[test]
|
||||
fn lock_find_links_explicit_index() -> Result<()> {
|
||||
fn lock_find_links_ignore_explicit_index() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Populate the `--find-links` entries.
|
||||
|
|
@ -9669,6 +9669,123 @@ fn lock_find_links_explicit_index() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure that `[[tool.uv.index]]` entries with `format = "flat"` can use relative paths in the
|
||||
/// `url` field.
|
||||
#[test]
|
||||
fn lock_find_links_relative_url() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Populate the `--find-links` entries.
|
||||
fs_err::create_dir_all(context.temp_dir.join("links"))?;
|
||||
|
||||
for entry in fs_err::read_dir(context.workspace_root.join("scripts/links"))? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path
|
||||
.file_name()
|
||||
.and_then(|file_name| file_name.to_str())
|
||||
.is_some_and(|file_name| file_name.starts_with("tqdm-"))
|
||||
{
|
||||
let dest = context
|
||||
.temp_dir
|
||||
.join("links")
|
||||
.join(path.file_name().unwrap());
|
||||
fs_err::copy(&path, &dest)?;
|
||||
}
|
||||
}
|
||||
|
||||
let workspace = context.temp_dir.child("workspace");
|
||||
|
||||
let pyproject_toml = workspace.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["tqdm"]
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "local"
|
||||
format = "flat"
|
||||
url = "./links"
|
||||
explicit = true
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().current_dir(&workspace), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Resolved 3 packages in [TIME]
|
||||
");
|
||||
|
||||
let lock = fs_err::read_to_string(workspace.join("uv.lock")).unwrap();
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "tqdm" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "tqdm" }]
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "4.66.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ea/85/3ce0f9f7d3f596e7ea57f4e5ce8c18cb44e4a9daa58ddb46ee0d13d6bff8/tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531", size = 169462 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/14/e75e52d521442e2fcc9f1df3c5e456aead034203d4797867980de558ab34/tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9", size = 78296 },
|
||||
]
|
||||
"#
|
||||
);
|
||||
});
|
||||
|
||||
// Re-run with `--locked`.
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--locked").current_dir(&workspace), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Resolved 3 packages in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lock a local source distribution via `--find-links`.
|
||||
#[test]
|
||||
fn lock_find_links_local_sdist() -> Result<()> {
|
||||
|
|
@ -9963,6 +10080,311 @@ fn lock_find_links_http_sdist() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Use an explicit `--find-links` index.
|
||||
#[test]
|
||||
fn lock_find_links_explicit_index() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Populate the `--find-links` entries.
|
||||
fs_err::create_dir_all(context.temp_dir.join("links"))?;
|
||||
|
||||
for entry in fs_err::read_dir(context.workspace_root.join("scripts/links"))? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path
|
||||
.file_name()
|
||||
.and_then(|file_name| file_name.to_str())
|
||||
.is_some_and(|file_name| file_name.starts_with("tqdm-"))
|
||||
{
|
||||
let dest = context
|
||||
.temp_dir
|
||||
.join("links")
|
||||
.join(path.file_name().unwrap());
|
||||
fs_err::copy(&path, &dest)?;
|
||||
}
|
||||
}
|
||||
|
||||
let workspace = context.temp_dir.child("workspace");
|
||||
|
||||
let pyproject_toml = workspace.child("pyproject.toml");
|
||||
pyproject_toml.write_str(&formatdoc! { r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["tqdm"]
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "local"
|
||||
format = "flat"
|
||||
url = "{}"
|
||||
explicit = true
|
||||
|
||||
[tool.uv.sources]
|
||||
tqdm = {{ index = "local" }}
|
||||
"#,
|
||||
Url::from_file_path(context.temp_dir.join("links/")).unwrap()
|
||||
})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().current_dir(&workspace), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
let lock = fs_err::read_to_string(workspace.join("uv.lock")).unwrap();
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
|
||||
[[package]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "tqdm" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "tqdm", index = "file://[TEMP_DIR]/links" }]
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "1000.0.0"
|
||||
source = { registry = "../links" }
|
||||
wheels = [
|
||||
{ path = "tqdm-1000.0.0-py3-none-any.whl" },
|
||||
]
|
||||
"#
|
||||
);
|
||||
});
|
||||
|
||||
// Re-run with `--locked`.
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--locked").current_dir(&workspace), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Use the same index priority rules, interchangeably, for `--find-links` and Simple API indexes.
|
||||
#[test]
|
||||
fn lock_find_links_higher_priority_index() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Populate the `--find-links` entries.
|
||||
fs_err::create_dir_all(context.temp_dir.join("links"))?;
|
||||
|
||||
for entry in fs_err::read_dir(context.workspace_root.join("scripts/links"))? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path
|
||||
.file_name()
|
||||
.and_then(|file_name| file_name.to_str())
|
||||
.is_some_and(|file_name| file_name.starts_with("tqdm-"))
|
||||
{
|
||||
let dest = context
|
||||
.temp_dir
|
||||
.join("links")
|
||||
.join(path.file_name().unwrap());
|
||||
fs_err::copy(&path, &dest)?;
|
||||
}
|
||||
}
|
||||
|
||||
let workspace = context.temp_dir.child("workspace");
|
||||
|
||||
let pyproject_toml = workspace.child("pyproject.toml");
|
||||
pyproject_toml.write_str(&formatdoc! { r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["tqdm"]
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "local"
|
||||
format = "flat"
|
||||
url = "{}"
|
||||
"#,
|
||||
Url::from_file_path(context.temp_dir.join("links/")).unwrap()
|
||||
})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().current_dir(&workspace), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
let lock = fs_err::read_to_string(workspace.join("uv.lock")).unwrap();
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
|
||||
[[package]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "tqdm" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "tqdm" }]
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "1000.0.0"
|
||||
source = { registry = "../links" }
|
||||
wheels = [
|
||||
{ path = "tqdm-1000.0.0-py3-none-any.whl" },
|
||||
]
|
||||
"#
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Use the same index priority rules, interchangeably, for `--find-links` and Simple API indexes.
|
||||
#[test]
|
||||
fn lock_find_links_lower_priority_index() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Populate the `--find-links` entries.
|
||||
fs_err::create_dir_all(context.temp_dir.join("links"))?;
|
||||
|
||||
for entry in fs_err::read_dir(context.workspace_root.join("scripts/links"))? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path
|
||||
.file_name()
|
||||
.and_then(|file_name| file_name.to_str())
|
||||
.is_some_and(|file_name| file_name.starts_with("tqdm-"))
|
||||
{
|
||||
let dest = context
|
||||
.temp_dir
|
||||
.join("links")
|
||||
.join(path.file_name().unwrap());
|
||||
fs_err::copy(&path, &dest)?;
|
||||
}
|
||||
}
|
||||
|
||||
let workspace = context.temp_dir.child("workspace");
|
||||
|
||||
let pyproject_toml = workspace.child("pyproject.toml");
|
||||
pyproject_toml.write_str(&formatdoc! { r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["tqdm"]
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "local"
|
||||
format = "flat"
|
||||
url = "{}"
|
||||
"#,
|
||||
Url::from_file_path(context.temp_dir.join("links/")).unwrap()
|
||||
})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().current_dir(&workspace), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Resolved 3 packages in [TIME]
|
||||
");
|
||||
|
||||
let lock = fs_err::read_to_string(workspace.join("uv.lock")).unwrap();
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "tqdm" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "tqdm" }]
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "4.66.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ea/85/3ce0f9f7d3f596e7ea57f4e5ce8c18cb44e4a9daa58ddb46ee0d13d6bff8/tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531", size = 169462 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/14/e75e52d521442e2fcc9f1df3c5e456aead034203d4797867980de558ab34/tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9", size = 78296 },
|
||||
]
|
||||
"#
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lock against a local directory laid out as a PEP 503-compatible index.
|
||||
#[test]
|
||||
fn lock_local_index() -> Result<()> {
|
||||
|
|
@ -15331,7 +15753,7 @@ fn lock_explicit_default_index() -> Result<()> {
|
|||
DEBUG No workspace root found, using project root
|
||||
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 }}
|
||||
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 }}
|
||||
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 }), format: Simple }), conflict: None }, origin: None }}
|
||||
DEBUG Solving with installed Python version: 3.12.[X]
|
||||
DEBUG Solving with target Python version: >=3.12
|
||||
DEBUG Adding direct dependency: project*
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -293,6 +294,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -453,6 +455,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -645,6 +648,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -945,6 +949,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -1129,6 +1134,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -1159,6 +1165,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -1322,6 +1329,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
|||
origin: Some(
|
||||
Cli,
|
||||
),
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -1352,6 +1360,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -1382,6 +1391,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -1567,6 +1577,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: Flat,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -1894,6 +1905,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -1924,6 +1936,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -2083,6 +2096,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -2113,6 +2127,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -3184,6 +3199,7 @@ fn resolve_both() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -3468,6 +3484,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -4174,6 +4191,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
origin: Some(
|
||||
Cli,
|
||||
),
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -4204,6 +4222,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -4365,6 +4384,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
origin: Some(
|
||||
Cli,
|
||||
),
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -4395,6 +4415,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: false,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -4562,6 +4583,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
origin: Some(
|
||||
Cli,
|
||||
),
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -4592,6 +4614,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -4754,6 +4777,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
origin: Some(
|
||||
Cli,
|
||||
),
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -4784,6 +4808,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -4953,6 +4978,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
origin: Some(
|
||||
Cli,
|
||||
),
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -4983,6 +5009,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -5145,6 +5172,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
origin: Some(
|
||||
Cli,
|
||||
),
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
@ -5175,6 +5203,7 @@ fn index_priority() -> anyhow::Result<()> {
|
|||
explicit: false,
|
||||
default: true,
|
||||
origin: None,
|
||||
format: Simple,
|
||||
publish_url: None,
|
||||
authenticate: Auto,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -223,6 +223,25 @@ authenticate = "never"
|
|||
When `authenticate` is set to `never`, uv will never search for credentials for the given index and
|
||||
will error if credentials are provided directly.
|
||||
|
||||
## "Flat" indexes
|
||||
|
||||
By default, `[[tool.uv.index]]` entries are assumed to be PyPI-style registries that implement the
|
||||
[PEP 503](https://peps.python.org/pep-0503/) Simple Repository API. However, uv also supports "flat"
|
||||
indexes, which are local directories or HTML pages that contain flat lists of wheels and source
|
||||
distributions. In pip, such indexes are specified using the `--find-links` option.
|
||||
|
||||
To define a flat index in your `pyproject.toml`, use the `kind = "flat"` option:
|
||||
|
||||
```toml
|
||||
[[tool.uv.index]]
|
||||
name = "example"
|
||||
url = "/path/to/directory"
|
||||
kind = "flat"
|
||||
```
|
||||
|
||||
Flat indexes support the same feature set as Simple Repository API indexes (e.g.,
|
||||
`explicit = true`); you can also pin a package to a flat index using `tool.uv.sources`.
|
||||
|
||||
## `--index-url` and `--extra-index-url`
|
||||
|
||||
In addition to the `[[tool.uv.index]]` configuration option, uv supports pip-style `--index-url` and
|
||||
|
|
|
|||
29
uv.schema.json
generated
29
uv.schema.json
generated
|
|
@ -796,6 +796,15 @@
|
|||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"format": {
|
||||
"description": "The format used by the index.\n\nIndexes can either be PEP 503-compliant (i.e., a PyPI-style 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.",
|
||||
"default": "simple",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/IndexFormat"
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the index.\n\nIndex names can be used to reference indexes elsewhere in the configuration. For example, you can pin a package to a specific index by name:\n\n```toml [[tool.uv.index]] name = \"pytorch\" url = \"https://download.pytorch.org/whl/cu121\"\n\n[tool.uv.sources] torch = { index = \"pytorch\" } ```",
|
||||
"anyOf": [
|
||||
|
|
@ -825,6 +834,24 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"IndexFormat": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A PyPI-style index implementing the Simple Repository API.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"simple"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "A `--find-links`-style index containing a flat list of wheels and source distributions.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"flat"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"IndexName": {
|
||||
"description": "The normalized name of an index.\n\nIndex names may contain letters, digits, hyphens, underscores, and periods, and must be ASCII.",
|
||||
"type": "string"
|
||||
|
|
@ -855,7 +882,7 @@
|
|||
]
|
||||
},
|
||||
"IndexUrl": {
|
||||
"description": "The URL of an index to use for fetching packages (e.g., `https://pypi.org/simple`).",
|
||||
"description": "The URL of an index to use for fetching packages (e.g., `https://pypi.org/simple`), or a local path.",
|
||||
"type": "string"
|
||||
},
|
||||
"KeyringProviderType": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue