Add support for named and explicit indexes (#7481)

## Summary

This PR adds a first-class API for defining registry indexes, beyond our
existing `--index-url` and `--extra-index-url` setup.

Specifically, you now define indexes like so in a `uv.toml` or
`pyproject.toml` file:

```toml
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
```

You can also provide indexes via `--index` and `UV_INDEX`, and override
the default index with `--default-index` and `UV_DEFAULT_INDEX`.

### Index priority

Indexes are prioritized in the order in which they're defined, such that
the first-defined index has highest priority.

Indexes are also inherited from parent configuration (e.g., the
user-level `uv.toml`), but are placed after any indexes in the current
project, matching our semantics for other array-based configuration
values.

You can mix `--index` and `--default-index` with the legacy
`--index-url` and `--extra-index-url` settings; the latter two are
merely treated as unnamed `[[tool.uv.index]]` entries.

### Index pinning

If an index includes a name (which is optional), it can then be
referenced via `tool.uv.sources`:

```toml
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"

[tool.uv.sources]
torch = { index = "pytorch" }
```

If an index is marked as `explicit = true`, it can _only_ be used via
such references, and will never be searched implicitly:

```toml
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
explicit = true

[tool.uv.sources]
torch = { index = "pytorch" }
```

Indexes defined outside of the current project (e.g., in the user-level
`uv.toml`) can _not_ be explicitly selected.

(As of now, we only support using a single index for a given
`tool.uv.sources` definition.)

### Default index

By default, we include PyPI as the default index. This remains true even
if the user defines a `[[tool.uv.index]]` -- PyPI is still used as a
fallback. You can mark an index as `default = true` to (1) disable the
use of PyPI, and (2) bump it to the bottom of the prioritized list, such
that it's used only if a package does not exist on a prior index:

```toml
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
default = true
```

### Name reuse

If a name is reused, the higher-priority index with that name is used,
while the lower-priority indexes are ignored entirely.

For example, given:

```toml
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"

[[tool.uv.index]]
name = "pytorch"
url = "https://test.pypi.org/simple"
```

The `https://test.pypi.org/simple` index would be ignored entirely,
since it's lower-priority than `https://download.pytorch.org/whl/cu121`
but shares the same name.

Closes #171.

## Future work

- Users should be able to provide authentication for named indexes via
environment variables.
- `uv add` should automatically write `--index` entries to the
`pyproject.toml` file.
- Users should be able to provide multiple indexes for a given package,
stratified by platform:
```toml
[tool.uv.sources]
torch = [
  { index = "cpu", markers = "sys_platform == 'darwin'" },
  { index = "gpu", markers = "sys_platform != 'darwin'" },
]
```
- Users should be able to specify a proxy URL for a given index, to
avoid writing user-specific URLs to a lockfile:
```toml
[[tool.uv.index]]
name = "test"
url = "https://private.org/simple"
proxy = "http://<omitted>/pypi/simple"
```
This commit is contained in:
Charlie Marsh 2024-10-15 15:24:23 -07:00 committed by GitHub
parent 34be3af84f
commit 5b391770df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 3526 additions and 658 deletions

2
Cargo.lock generated
View file

@ -5121,6 +5121,7 @@ dependencies = [
"serde",
"thiserror",
"toml",
"uv-distribution-types",
"uv-pep440",
"uv-pep508",
"uv-pypi-types",
@ -5285,6 +5286,7 @@ dependencies = [
"toml_edit",
"tracing",
"url",
"uv-distribution-types",
"uv-fs",
"uv-git",
"uv-macros",

View file

@ -7,13 +7,14 @@ use anyhow::{anyhow, Result};
use clap::builder::styling::{AnsiColor, Effects, Style};
use clap::builder::Styles;
use clap::{Args, Parser, Subcommand};
use url::Url;
use uv_cache::CacheArgs;
use uv_configuration::{
ConfigSettingEntry, ExportFormat, IndexStrategy, KeyringProviderType, PackageNameSpecifier,
TargetTriple, TrustedHost, TrustedPublishing, VersionControlSystem,
};
use uv_distribution_types::{FlatIndexLocation, IndexUrl};
use uv_distribution_types::{FlatIndexLocation, Index, IndexUrl};
use uv_normalize::{ExtraName, PackageName};
use uv_pep508::Requirement;
use uv_pypi_types::VerbatimParsedUrl;
@ -793,6 +794,36 @@ fn parse_flat_index(input: &str) -> Result<Maybe<FlatIndexLocation>, String> {
}
}
/// Parse a string into an [`Index`], mapping the empty string to `None`.
fn parse_index_source(input: &str) -> Result<Maybe<Index>, String> {
if input.is_empty() {
Ok(Maybe::None)
} else {
match Index::from_str(input) {
Ok(index) => Ok(Maybe::Some(Index {
default: false,
..index
})),
Err(err) => Err(err.to_string()),
}
}
}
/// Parse a string into an [`Index`], mapping the empty string to `None`.
fn parse_default_index_source(input: &str) -> Result<Maybe<Index>, String> {
if input.is_empty() {
Ok(Maybe::None)
} else {
match Index::from_str(input) {
Ok(index) => Ok(Maybe::Some(Index {
default: true,
..index
})),
Err(err) => Err(err.to_string()),
}
}
}
/// Parse a string into an [`Url`], mapping the empty string to `None`.
fn parse_insecure_host(input: &str) -> Result<Maybe<TrustedHost>, String> {
if input.is_empty() {
@ -2282,8 +2313,8 @@ pub struct VenvArgs {
///
/// By default, uv will stop at the first index on which a given package is available, and
/// limit resolutions to those present on that first index (`first-match`). This prevents
/// "dependency confusion" attacks, whereby an attack can upload a malicious package under the
/// same name to a secondary.
/// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the
/// same name to an alternate index.
#[arg(long, value_enum, env = EnvVars::UV_INDEX_STRATEGY)]
pub index_strategy: Option<IndexStrategy>,
@ -3808,7 +3839,28 @@ pub struct GenerateShellCompletionArgs {
#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub struct IndexArgs {
/// The URL of the Python package index (by default: <https://pypi.org/simple>).
/// The URLs to use when resolving dependencies, in addition to the default index.
///
/// Accepts either a repository compliant with PEP 503 (the simple repository API), or a local
/// directory laid out in the same format.
///
/// All indexes provided via this flag take priority over the index specified by
/// `--default-index` (which defaults to PyPI). When multiple `--index` flags are
/// provided, earlier values take priority.
#[arg(long, env = "UV_INDEX", value_delimiter = ' ', value_parser = parse_index_source, help_heading = "Index options")]
pub index: Option<Vec<Maybe<Index>>>,
/// The URL of the default package index (by default: <https://pypi.org/simple>).
///
/// Accepts either a repository compliant with PEP 503 (the simple repository API), or a local
/// directory laid out in the same format.
///
/// The index given by this flag is given lower priority than all other indexes specified via
/// the `--index` flag.
#[arg(long, env = "UV_DEFAULT_INDEX", value_parser = parse_default_index_source, help_heading = "Index options")]
pub default_index: Option<Maybe<Index>>,
/// (Deprecated: use `--default-index` instead) The URL of the Python package index (by default: <https://pypi.org/simple>).
///
/// Accepts either a repository compliant with PEP 503 (the simple repository API), or a local
/// directory laid out in the same format.
@ -3818,7 +3870,7 @@ pub struct IndexArgs {
#[arg(long, short, env = EnvVars::UV_INDEX_URL, value_parser = parse_index_url, help_heading = "Index options")]
pub index_url: Option<Maybe<IndexUrl>>,
/// Extra URLs of package indexes to use, in addition to `--index-url`.
/// (Deprecated: use `--index` instead) Extra URLs of package indexes to use, in addition to `--index-url`.
///
/// Accepts either a repository compliant with PEP 503 (the simple repository API), or a local
/// directory laid out in the same format.
@ -3955,8 +4007,8 @@ pub struct InstallerArgs {
///
/// By default, uv will stop at the first index on which a given package is available, and
/// limit resolutions to those present on that first index (`first-match`). This prevents
/// "dependency confusion" attacks, whereby an attack can upload a malicious package under the
/// same name to a secondary.
/// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the
/// same name to an alternate index.
#[arg(
long,
value_enum,
@ -4117,8 +4169,8 @@ pub struct ResolverArgs {
///
/// By default, uv will stop at the first index on which a given package is available, and
/// limit resolutions to those present on that first index (`first-match`). This prevents
/// "dependency confusion" attacks, whereby an attack can upload a malicious package under the
/// same name to a secondary.
/// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the
/// same name to an alternate index.
#[arg(
long,
value_enum,
@ -4309,8 +4361,8 @@ pub struct ResolverInstallerArgs {
///
/// By default, uv will stop at the first index on which a given package is available, and
/// limit resolutions to those present on that first index (`first-match`). This prevents
/// "dependency confusion" attacks, whereby an attack can upload a malicious package under the
/// same name to a secondary.
/// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the
/// same name to an alternate index.
#[arg(
long,
value_enum,

View file

@ -1,7 +1,7 @@
use uv_cache::Refresh;
use uv_configuration::ConfigSettings;
use uv_resolver::PrereleaseMode;
use uv_settings::{PipOptions, ResolverInstallerOptions, ResolverOptions};
use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions};
use crate::{
BuildOptionsArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs,
@ -186,6 +186,8 @@ impl From<ResolverInstallerArgs> for PipOptions {
impl From<IndexArgs> for PipOptions {
fn from(args: IndexArgs) -> Self {
let IndexArgs {
default_index,
index,
index_url,
extra_index_url,
no_index,
@ -193,6 +195,12 @@ impl From<IndexArgs> for PipOptions {
} = args;
Self {
index: default_index
.and_then(Maybe::into_option)
.map(|default_index| vec![default_index])
.combine(
index.map(|index| index.into_iter().filter_map(Maybe::into_option).collect()),
),
index_url: index_url.and_then(Maybe::into_option),
extra_index_url: extra_index_url.map(|extra_index_url| {
extra_index_url
@ -247,6 +255,15 @@ pub fn resolver_options(
} = build_args;
ResolverOptions {
index: index_args
.default_index
.and_then(Maybe::into_option)
.map(|default_index| vec![default_index])
.combine(
index_args
.index
.map(|index| index.into_iter().filter_map(Maybe::into_option).collect()),
),
index_url: index_args.index_url.and_then(Maybe::into_option),
extra_index_url: index_args.extra_index_url.map(|extra_index_url| {
extra_index_url
@ -335,7 +352,16 @@ pub fn resolver_installer_options(
no_binary_package,
} = build_args;
let default_index = index_args
.default_index
.and_then(Maybe::into_option)
.map(|default_index| vec![default_index]);
let index = index_args
.index
.map(|index| index.into_iter().filter_map(Maybe::into_option).collect());
ResolverInstallerOptions {
index: default_index.combine(index),
index_url: index_args.index_url.and_then(Maybe::into_option),
extra_index_url: index_args.extra_index_url.map(|extra_index_url| {
extra_index_url

View file

@ -1,6 +1,7 @@
use async_http_range_reader::AsyncHttpRangeReader;
use futures::{FutureExt, TryStreamExt};
use http::HeaderMap;
use itertools::Either;
use reqwest::{Client, Response, StatusCode};
use reqwest_middleware::ClientWithMiddleware;
use std::collections::BTreeMap;
@ -16,7 +17,7 @@ use uv_configuration::KeyringProviderType;
use uv_configuration::{IndexStrategy, TrustedHost};
use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename};
use uv_distribution_types::{
BuiltDist, File, FileLocation, IndexCapabilities, IndexUrl, IndexUrls, Name,
BuiltDist, File, FileLocation, Index, IndexCapabilities, IndexUrl, IndexUrls, Name,
};
use uv_metadata::{read_metadata_async_seek, read_metadata_async_stream};
use uv_normalize::PackageName;
@ -204,8 +205,15 @@ impl RegistryClient {
pub async fn simple(
&self,
package_name: &PackageName,
index: Option<&IndexUrl>,
) -> Result<Vec<(IndexUrl, OwnedArchive<SimpleMetadata>)>, Error> {
let mut it = self.index_urls.indexes().peekable();
let indexes = if let Some(index) = index {
Either::Left(std::iter::once(index))
} else {
Either::Right(self.index_urls.indexes().map(Index::url))
};
let mut it = indexes.peekable();
if it.peek().is_none() {
return Err(ErrorKind::NoIndex(package_name.to_string()).into());
}

View file

@ -0,0 +1,146 @@
use crate::{IndexUrl, IndexUrlError};
use std::str::FromStr;
use thiserror::Error;
use url::Url;
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Index {
/// The name of the index.
///
/// Index names can be used to reference indexes elsewhere in the configuration. For example,
/// you can pin a package to a specific index by name:
///
/// ```toml
/// [[tool.uv.index]]
/// name = "pytorch"
/// url = "https://download.pytorch.org/whl/cu121"
///
/// [tool.uv.sources]
/// torch = { index = "pytorch" }
/// ```
pub name: Option<String>,
/// The URL of the index.
///
/// Expects to receive a URL (e.g., `https://pypi.org/simple`) or a local path.
pub url: IndexUrl,
/// Mark the index as explicit.
///
/// Explicit indexes will _only_ be used when explicitly requested via a `[tool.uv.sources]`
/// definition, as in:
///
/// ```toml
/// [[tool.uv.index]]
/// name = "pytorch"
/// url = "https://download.pytorch.org/whl/cu121"
/// explicit = true
///
/// [tool.uv.sources]
/// torch = { index = "pytorch" }
/// ```
#[serde(default)]
pub explicit: bool,
/// Mark the index as the default index.
///
/// By default, uv uses PyPI as the default index, such that even if additional indexes are
/// defined via `[[tool.uv.index]]`, PyPI will still be used as a fallback for packages that
/// aren't found elsewhere. To disable the PyPI default, set `default = true` on at least one
/// other index.
///
/// Marking an index as default will move it to the front of the list of indexes, such that it
/// is given the highest priority when resolving packages.
#[serde(default)]
pub default: bool,
// /// 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,
}
// #[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,
// }
impl Index {
/// Initialize an [`Index`] from a pip-style `--index-url`.
pub fn from_index_url(url: IndexUrl) -> Self {
Self {
url,
name: None,
explicit: false,
default: true,
}
}
/// Initialize an [`Index`] from a pip-style `--extra-index-url`.
pub fn from_extra_index_url(url: IndexUrl) -> Self {
Self {
url,
name: None,
explicit: false,
default: false,
}
}
/// Return the [`IndexUrl`] of the index.
pub fn url(&self) -> &IndexUrl {
&self.url
}
/// Return the raw [`URL`] of the index.
pub fn raw_url(&self) -> &Url {
self.url.url()
}
}
impl FromStr for Index {
type Err = IndexSourceError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// Determine whether the source is prefixed with a name, as in `name=https://pypi.org/simple`.
if let Some((name, url)) = s.split_once('=') {
if name.is_empty() {
return Err(IndexSourceError::EmptyName);
}
if name.chars().all(char::is_alphanumeric) {
let url = IndexUrl::from_str(url)?;
return Ok(Self {
name: Some(name.to_string()),
url,
explicit: false,
default: false,
});
}
}
// Otherwise, assume the source is a URL.
let url = IndexUrl::from_str(s)?;
Ok(Self {
name: None,
url,
explicit: false,
default: false,
})
}
}
/// An error that can occur when parsing an [`Index`].
#[derive(Error, Debug)]
pub enum IndexSourceError {
#[error(transparent)]
Url(#[from] IndexUrlError),
#[error("Index included a name, but the name was empty")]
EmptyName,
}

View file

@ -11,12 +11,13 @@ use url::{ParseError, Url};
use uv_pep508::{VerbatimUrl, VerbatimUrlError};
use crate::Verbatim;
use crate::{Index, Verbatim};
static PYPI_URL: LazyLock<Url> = LazyLock::new(|| Url::parse("https://pypi.org/simple").unwrap());
static DEFAULT_INDEX_URL: LazyLock<IndexUrl> =
LazyLock::new(|| IndexUrl::Pypi(VerbatimUrl::from_url(PYPI_URL.clone())));
static DEFAULT_INDEX_URL: LazyLock<Index> = LazyLock::new(|| {
Index::from_index_url(IndexUrl::Pypi(VerbatimUrl::from_url(PYPI_URL.clone())))
});
/// The URL of an index to use for fetching packages (e.g., PyPI).
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
@ -55,6 +56,15 @@ impl IndexUrl {
}
}
/// Convert the index URL into a [`Url`].
pub fn into_url(self) -> Url {
match self {
Self::Pypi(url) => url.into_url(),
Self::Url(url) => url.into_url(),
Self::Path(url) => url.into_url(),
}
}
/// Return the redacted URL for the index, omitting any sensitive credentials.
pub fn redacted(&self) -> Cow<'_, Url> {
let url = self.url();
@ -288,39 +298,21 @@ impl From<VerbatimUrl> for FlatIndexLocation {
/// The index locations to use for fetching packages. By default, uses the PyPI index.
///
/// From a pip perspective, this type merges `--index-url`, `--extra-index-url`, and `--find-links`.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
/// This type merges the legacy `--index-url`, `--extra-index-url`, and `--find-links` options,
/// along with the uv-specific `--index` and `--default-index`.
#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct IndexLocations {
index: Option<IndexUrl>,
extra_index: Vec<IndexUrl>,
indexes: Vec<Index>,
flat_index: Vec<FlatIndexLocation>,
no_index: bool,
}
impl Default for IndexLocations {
/// By default, use the `PyPI` index.
fn default() -> Self {
Self {
index: Some(DEFAULT_INDEX_URL.clone()),
extra_index: Vec::new(),
flat_index: Vec::new(),
no_index: false,
}
}
}
impl IndexLocations {
/// Determine the index URLs to use for fetching packages.
pub fn new(
index: Option<IndexUrl>,
extra_index: Vec<IndexUrl>,
flat_index: Vec<FlatIndexLocation>,
no_index: bool,
) -> Self {
pub fn new(indexes: Vec<Index>, flat_index: Vec<FlatIndexLocation>, no_index: bool) -> Self {
Self {
index,
extra_index,
indexes,
flat_index,
no_index,
}
@ -335,14 +327,12 @@ impl IndexLocations {
#[must_use]
pub fn combine(
self,
index: Option<IndexUrl>,
extra_index: Vec<IndexUrl>,
indexes: Vec<Index>,
flat_index: Vec<FlatIndexLocation>,
no_index: bool,
) -> Self {
Self {
index: self.index.or(index),
extra_index: self.extra_index.into_iter().chain(extra_index).collect(),
indexes: self.indexes.into_iter().chain(indexes).collect(),
flat_index: self.flat_index.into_iter().chain(flat_index).collect(),
no_index: self.no_index || no_index,
}
@ -351,47 +341,69 @@ impl IndexLocations {
/// Returns `true` if no index configuration is set, i.e., the [`IndexLocations`] matches the
/// default configuration.
pub fn is_none(&self) -> bool {
self.index.is_none()
&& self.extra_index.is_empty()
&& self.flat_index.is_empty()
&& !self.no_index
*self == Self::default()
}
}
impl<'a> IndexLocations {
/// Return the primary [`IndexUrl`] entry.
/// Return the default [`Index`] entry.
///
/// If `--no-index` is set, return `None`.
///
/// If no index is provided, use the `PyPI` index.
pub fn index(&'a self) -> Option<&'a IndexUrl> {
pub fn default_index(&'a self) -> Option<&'a Index> {
if self.no_index {
None
} else {
match self.index.as_ref() {
Some(index) => Some(index),
None => Some(&DEFAULT_INDEX_URL),
}
let mut seen = FxHashSet::default();
self.indexes
.iter()
.filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name)))
.find(|index| index.default && !index.explicit)
.or_else(|| Some(&DEFAULT_INDEX_URL))
}
}
/// Return an iterator over the extra [`IndexUrl`] entries.
pub fn extra_index(&'a self) -> impl Iterator<Item = &'a IndexUrl> + 'a {
/// Return an iterator over the implicit [`Index`] entries.
pub fn implicit_indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
if self.no_index {
Either::Left(std::iter::empty())
} else {
Either::Right(self.extra_index.iter())
let mut seen = FxHashSet::default();
Either::Right(
self.indexes
.iter()
.filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name)))
.filter(|index| !(index.default || index.explicit)),
)
}
}
/// Return an iterator over all [`IndexUrl`] entries in order.
/// Return an iterator over the explicit [`Index`] entries.
pub fn explicit_indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
if self.no_index {
Either::Left(std::iter::empty())
} else {
let mut seen = FxHashSet::default();
Either::Right(
self.indexes
.iter()
.filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name)))
.filter(|index| index.explicit),
)
}
}
/// Return an iterator over all [`Index`] entries in order.
///
/// Prioritizes the extra indexes over the main index.
/// Explicit indexes are excluded.
///
/// Prioritizes the extra indexes over the default index.
///
/// If `no_index` was enabled, then this always returns an empty
/// iterator.
pub fn indexes(&'a self) -> impl Iterator<Item = &'a IndexUrl> + 'a {
self.extra_index().chain(self.index())
pub fn indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
self.implicit_indexes().chain(self.default_index())
}
/// Return an iterator over the [`FlatIndexLocation`] entries.
@ -407,88 +419,89 @@ impl<'a> IndexLocations {
/// Clone the index locations into a [`IndexUrls`] instance.
pub fn index_urls(&'a self) -> IndexUrls {
IndexUrls {
index: self.index.clone(),
extra_index: self.extra_index.clone(),
indexes: self.indexes.clone(),
no_index: self.no_index,
}
}
/// Return an iterator over all [`Url`] entries.
pub fn urls(&'a self) -> impl Iterator<Item = &'a Url> + 'a {
self.indexes()
.map(IndexUrl::url)
.chain(self.flat_index.iter().filter_map(|index| match index {
FlatIndexLocation::Path(_) => None,
FlatIndexLocation::Url(url) => Some(url.raw()),
}))
/// Return an iterator over all allowed [`IndexUrl`] entries.
///
/// This includes both explicit and implicit indexes, as well as the default index.
///
/// If `no_index` was enabled, then this always returns an empty
/// iterator.
pub fn allowed_indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
self.explicit_indexes()
.chain(self.implicit_indexes())
.chain(self.default_index())
}
/// Return an iterator over all allowed [`Url`] entries.
///
/// This includes both explicit and implicit index URLs, as well as the default index.
///
/// If `no_index` was enabled, then this always returns an empty
/// iterator.
pub fn allowed_urls(&'a self) -> impl Iterator<Item = &'a Url> + 'a {
self.allowed_indexes()
.map(Index::raw_url)
.chain(self.flat_index().map(FlatIndexLocation::url))
}
}
/// The index URLs to use for fetching packages.
///
/// From a pip perspective, this type merges `--index-url` and `--extra-index-url`.
#[derive(Debug, Clone)]
/// This type merges the legacy `--index-url` and `--extra-index-url` options, along with the
/// uv-specific `--index` and `--default-index`.
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct IndexUrls {
index: Option<IndexUrl>,
extra_index: Vec<IndexUrl>,
indexes: Vec<Index>,
no_index: bool,
}
impl Default for IndexUrls {
/// By default, use the `PyPI` index.
fn default() -> Self {
Self {
index: Some(DEFAULT_INDEX_URL.clone()),
extra_index: Vec::new(),
no_index: false,
}
}
}
impl<'a> IndexUrls {
/// Return the fallback [`IndexUrl`] entry.
/// Return the default [`Index`] entry.
///
/// If `--no-index` is set, return `None`.
///
/// If no index is provided, use the `PyPI` index.
fn index(&'a self) -> Option<&'a IndexUrl> {
fn default_index(&'a self) -> Option<&'a Index> {
if self.no_index {
None
} else {
match self.index.as_ref() {
Some(index) => Some(index),
None => Some(&DEFAULT_INDEX_URL),
}
let mut seen = FxHashSet::default();
self.indexes
.iter()
.filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name)))
.find(|index| index.default && !index.explicit)
.or_else(|| Some(&DEFAULT_INDEX_URL))
}
}
/// Return an iterator over the extra [`IndexUrl`] entries.
fn extra_index(&'a self) -> impl Iterator<Item = &'a IndexUrl> + 'a {
/// Return an iterator over the implicit [`Index`] entries.
fn implicit_indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
if self.no_index {
Either::Left(std::iter::empty())
} else {
Either::Right(self.extra_index.iter())
let mut seen = FxHashSet::default();
Either::Right(
self.indexes
.iter()
.filter(move |index| index.name.as_ref().map_or(true, |name| seen.insert(name)))
.filter(|index| !(index.default || index.explicit)),
)
}
}
/// Return an iterator over all [`IndexUrl`] entries in order.
///
/// Prioritizes the extra indexes over the main index.
/// Prioritizes the `[tool.uv.index]` definitions over the `--extra-index-url` definitions
/// over the `--index-url` definition.
///
/// If `no_index` was enabled, then this always returns an empty
/// iterator.
pub fn indexes(&'a self) -> impl Iterator<Item = &'a IndexUrl> + 'a {
self.extra_index().chain(self.index())
}
}
impl From<IndexLocations> for IndexUrls {
fn from(locations: IndexLocations) -> Self {
Self {
index: locations.index,
extra_index: locations.extra_index,
no_index: locations.no_index,
}
pub fn indexes(&'a self) -> impl Iterator<Item = &'a Index> + 'a {
self.implicit_indexes().chain(self.default_index())
}
}

View file

@ -57,6 +57,7 @@ pub use crate::error::*;
pub use crate::file::*;
pub use crate::hash::*;
pub use crate::id::*;
pub use crate::index::*;
pub use crate::index_url::*;
pub use crate::installed::*;
pub use crate::prioritized_distribution::*;
@ -75,6 +76,7 @@ mod error;
mod file;
mod hash;
mod id;
mod index;
mod index_url;
mod installed;
mod prioritized_distribution;

View file

@ -190,7 +190,7 @@ impl From<&ResolvedDist> for Requirement {
wheels.best_wheel().filename.version.clone(),
),
),
index: None,
index: Some(wheels.best_wheel().index.url().clone()),
},
Dist::Built(BuiltDist::DirectUrl(wheel)) => {
let mut location = wheel.url.to_url();
@ -211,7 +211,7 @@ impl From<&ResolvedDist> for Requirement {
specifier: uv_pep440::VersionSpecifiers::from(
uv_pep440::VersionSpecifier::equals_version(sdist.version.clone()),
),
index: None,
index: Some(sdist.index.url().clone()),
},
Dist::Source(SourceDist::DirectUrl(sdist)) => {
let mut location = sdist.url.to_url();

View file

@ -3,7 +3,7 @@ use std::collections::hash_map::Entry;
use rustc_hash::FxHashMap;
use uv_cache::{Cache, CacheBucket, WheelCache};
use uv_distribution_types::{CachedRegistryDist, Hashed, IndexLocations, IndexUrl};
use uv_distribution_types::{CachedRegistryDist, Hashed, Index, IndexLocations, IndexUrl};
use uv_fs::{directories, files, symlinks};
use uv_normalize::PackageName;
use uv_platform_tags::Tags;
@ -17,6 +17,8 @@ use crate::source::{HttpRevisionPointer, LocalRevisionPointer, HTTP_REVISION, LO
pub struct IndexEntry {
/// The cached distribution.
pub dist: CachedRegistryDist,
/// The index from which the wheel was downloaded.
pub index: Index,
/// Whether the wheel was built from source (true), or downloaded from the registry directly (false).
pub built: bool,
}
@ -80,23 +82,22 @@ impl<'a> RegistryWheelIndex<'a> {
) -> Vec<IndexEntry> {
let mut entries = vec![];
// Collect into owned `IndexUrl`.
let flat_index_urls: Vec<IndexUrl> = index_locations
let flat_index_urls: Vec<Index> = index_locations
.flat_index()
.map(|flat_index| IndexUrl::from(flat_index.clone()))
.map(|flat_index| Index::from_extra_index_url(IndexUrl::from(flat_index.clone())))
.collect();
for index_url in index_locations.indexes().chain(flat_index_urls.iter()) {
for index in index_locations.indexes().chain(flat_index_urls.iter()) {
// Index all the wheels that were downloaded directly from the registry.
let wheel_dir = cache.shard(
CacheBucket::Wheels,
WheelCache::Index(index_url).wheel_dir(package.to_string()),
WheelCache::Index(index.url()).wheel_dir(package.to_string()),
);
// For registry wheels, the cache structure is: `<index>/<package-name>/<wheel>.http`
// or `<index>/<package-name>/<version>/<wheel>.rev`.
for file in files(&wheel_dir) {
match index_url {
match index.url() {
// Add files from remote registries.
IndexUrl::Pypi(_) | IndexUrl::Url(_) => {
if file
@ -116,6 +117,7 @@ impl<'a> RegistryWheelIndex<'a> {
) {
entries.push(IndexEntry {
dist: wheel.into_registry_dist(),
index: index.clone(),
built: false,
});
}
@ -142,6 +144,7 @@ impl<'a> RegistryWheelIndex<'a> {
) {
entries.push(IndexEntry {
dist: wheel.into_registry_dist(),
index: index.clone(),
built: false,
});
}
@ -156,7 +159,7 @@ impl<'a> RegistryWheelIndex<'a> {
// from the registry.
let cache_shard = cache.shard(
CacheBucket::SourceDistributions,
WheelCache::Index(index_url).wheel_dir(package.to_string()),
WheelCache::Index(index.url()).wheel_dir(package.to_string()),
);
// For registry wheels, the cache structure is: `<index>/<package-name>/<version>/`.
@ -165,7 +168,7 @@ impl<'a> RegistryWheelIndex<'a> {
let cache_shard = cache_shard.shard(shard);
// Read the revision from the cache.
let revision = match index_url {
let revision = match index.url() {
// Add files from remote registries.
IndexUrl::Pypi(_) | IndexUrl::Url(_) => {
let revision_entry = cache_shard.entry(HTTP_REVISION);
@ -197,6 +200,7 @@ impl<'a> RegistryWheelIndex<'a> {
) {
entries.push(IndexEntry {
dist: wheel.into_registry_dist(),
index: index.clone(),
built: true,
});
}

View file

@ -6,6 +6,7 @@ use thiserror::Error;
use url::Url;
use uv_distribution_filename::DistExtension;
use uv_distribution_types::Index;
use uv_git::GitReference;
use uv_normalize::PackageName;
use uv_pep440::VersionSpecifiers;
@ -33,6 +34,7 @@ impl LoweredRequirement {
project_name: &'data PackageName,
project_dir: &'data Path,
project_sources: &'data BTreeMap<PackageName, Sources>,
project_indexes: &'data [Index],
workspace: &'data Workspace,
lower_bound: LowerBound,
) -> impl Iterator<Item = Result<LoweredRequirement, LoweringError>> + 'data {
@ -151,7 +153,26 @@ impl LoweredRequirement {
(source, marker)
}
Source::Registry { index, marker } => {
let source = registry_source(&requirement, index)?;
// Identify the named index from either the project indexes or the workspace indexes,
// in that order.
let Some(index) = project_indexes
.iter()
.find(|Index { name, .. }| {
name.as_ref().is_some_and(|name| *name == index)
})
.or_else(|| {
workspace.indexes().iter().find(|Index { name, .. }| {
name.as_ref().is_some_and(|name| *name == index)
})
})
.map(|Index { url: index, .. }| index.clone())
else {
return Err(LoweringError::MissingIndex(
requirement.name.clone(),
index,
));
};
let source = registry_source(&requirement, index.into_url())?;
(source, marker)
}
Source::Workspace {
@ -238,6 +259,7 @@ impl LoweredRequirement {
requirement: uv_pep508::Requirement<VerbatimParsedUrl>,
dir: &'data Path,
sources: &'data BTreeMap<PackageName, Sources>,
indexes: &'data [Index],
) -> impl Iterator<Item = Result<LoweredRequirement, LoweringError>> + 'data {
let source = sources.get(&requirement.name).cloned();
@ -318,7 +340,19 @@ impl LoweredRequirement {
(source, marker)
}
Source::Registry { index, marker } => {
let source = registry_source(&requirement, index)?;
let Some(index) = indexes
.iter()
.find(|Index { name, .. }| {
name.as_ref().is_some_and(|name| *name == index)
})
.map(|Index { url: index, .. }| index.clone())
else {
return Err(LoweringError::MissingIndex(
requirement.name.clone(),
index,
));
};
let source = registry_source(&requirement, index.into_url())?;
(source, marker)
}
Source::Workspace { .. } => {
@ -358,6 +392,8 @@ pub enum LoweringError {
UndeclaredWorkspacePackage,
#[error("Can only specify one of: `rev`, `tag`, or `branch`")]
MoreThanOneGitRef,
#[error("Package `{0}` references an undeclared index: `{1}`")]
MissingIndex(PackageName, String),
#[error("Workspace members are not allowed in non-workspace contexts")]
WorkspaceMember,
#[error(transparent)]
@ -448,7 +484,7 @@ fn url_source(url: Url, subdirectory: Option<PathBuf>) -> Result<RequirementSour
/// Convert a registry source into a [`RequirementSource`].
fn registry_source(
requirement: &uv_pep508::Requirement<VerbatimParsedUrl>,
index: String,
index: Url,
) -> Result<RequirementSource, LoweringError> {
match &requirement.version_or_url {
None => {

View file

@ -59,6 +59,20 @@ impl RequiresDist {
source_strategy: SourceStrategy,
lower_bound: LowerBound,
) -> Result<Self, MetadataError> {
// Collect any `tool.uv.index` entries.
let empty = vec![];
let indexes = match source_strategy {
SourceStrategy::Enabled => project_workspace
.current_project()
.pyproject_toml()
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.index.as_deref())
.unwrap_or(&empty),
SourceStrategy::Disabled => &empty,
};
// Collect any `tool.uv.sources` and `tool.uv.dev_dependencies` from `pyproject.toml`.
let empty = BTreeMap::default();
let sources = match source_strategy {
@ -94,6 +108,7 @@ impl RequiresDist {
&metadata.name,
project_workspace.project_root(),
sources,
indexes,
project_workspace.workspace(),
lower_bound,
)
@ -127,6 +142,7 @@ impl RequiresDist {
&metadata.name,
project_workspace.project_root(),
sources,
indexes,
project_workspace.workspace(),
lower_bound,
)

View file

@ -121,6 +121,9 @@ impl<'a> Planner<'a> {
match installable {
Dist::Built(BuiltDist::Registry(wheel)) => {
if let Some(distribution) = registry_index.get(wheel.name()).find_map(|entry| {
if *entry.index.url() != wheel.best_wheel().index {
return None;
}
if entry.dist.filename.version != wheel.best_wheel().filename.version {
return None;
};
@ -231,6 +234,9 @@ impl<'a> Planner<'a> {
}
Dist::Source(SourceDist::Registry(sdist)) => {
if let Some(distribution) = registry_index.get(sdist.name()).find_map(|entry| {
if *entry.index.url() != sdist.index {
return None;
}
if entry.dist.filename.version != sdist.version {
return None;
};

View file

@ -134,6 +134,11 @@ impl VerbatimUrl {
self.url.clone()
}
/// Convert a [`VerbatimUrl`] into a [`Url`].
pub fn into_url(self) -> Url {
self.url
}
/// Return the underlying [`Path`], if the URL is a file URL.
pub fn as_path(&self) -> Result<PathBuf, VerbatimUrlError> {
self.url

View file

@ -318,8 +318,8 @@ pub enum RequirementSource {
/// The requirement has a version specifier, such as `foo >1,<2`.
Registry {
specifier: VersionSpecifiers,
/// Choose a version from the index with this name.
index: Option<String>,
/// Choose a version from the index at the given URL.
index: Option<Url>,
},
// TODO(konsti): Track and verify version specifier from `project.dependencies` matches the
// version in remote location.
@ -607,7 +607,7 @@ enum RequirementSourceWire {
Registry {
#[serde(skip_serializing_if = "VersionSpecifiers::is_empty", default)]
specifier: VersionSpecifiers,
index: Option<String>,
index: Option<Url>,
},
}

View file

@ -53,6 +53,9 @@ pub enum ResolveError {
fork_markers: MarkerTree,
},
#[error("Requirements contain conflicting indexes for package `{0}`: `{1}` vs. `{2}`")]
ConflictingIndexes(PackageName, String, String),
#[error("Package `{0}` attempted to resolve via URL: {1}. URL dependencies must be expressed as direct requirements or constraints. Consider adding `{0} @ {1}` to your dependencies or constraints file.")]
DisallowedUrl(PackageName, String),

View file

@ -1058,10 +1058,10 @@ impl Lock {
// Collect the set of available indexes (both `--index-url` and `--find-links` entries).
let remotes = indexes.map(|locations| {
locations
.indexes()
.filter_map(|index_url| match index_url {
.allowed_indexes()
.filter_map(|index| match index.url() {
IndexUrl::Pypi(_) | IndexUrl::Url(_) => {
Some(UrlString::from(index_url.redacted()))
Some(UrlString::from(index.url().redacted()))
}
IndexUrl::Path(_) => None,
})
@ -1080,11 +1080,11 @@ impl Lock {
let locals = indexes.map(|locations| {
locations
.indexes()
.filter_map(|index_url| match index_url {
.allowed_indexes()
.filter_map(|index| match index.url() {
IndexUrl::Pypi(_) | IndexUrl::Url(_) => None,
IndexUrl::Path(index_url) => {
let path = index_url.to_file_path().ok()?;
IndexUrl::Path(url) => {
let path = url.to_file_path().ok()?;
let path = relative_to(&path, workspace.install_path())
.or_else(|_| std::path::absolute(path))
.ok()?;

View file

@ -9,7 +9,7 @@ use pubgrub::{DerivationTree, Derived, External, Map, Range, ReportFormatter, Te
use rustc_hash::FxHashMap;
use uv_configuration::IndexStrategy;
use uv_distribution_types::{IndexLocations, IndexUrl};
use uv_distribution_types::{Index, IndexLocations, IndexUrl};
use uv_normalize::PackageName;
use uv_pep440::Version;
@ -708,6 +708,7 @@ impl PubGrubReportFormatter<'_> {
// indexes were not queried, and could contain a compatible version.
if let Some(next_index) = index_locations
.indexes()
.map(Index::url)
.skip_while(|url| *url != found_index)
.nth(1)
{

View file

@ -0,0 +1,67 @@
use crate::{DependencyMode, Manifest, ResolveError, ResolverMarkers};
use rustc_hash::FxHashMap;
use std::collections::hash_map::Entry;
use uv_distribution_types::IndexUrl;
use uv_normalize::PackageName;
use uv_pep508::VerbatimUrl;
use uv_pypi_types::RequirementSource;
/// A map of package names to their explicit index.
///
/// For example, given:
/// ```toml
/// [[tool.uv.index]]
/// name = "pytorch"
/// url = "https://download.pytorch.org/whl/cu121"
///
/// [tool.uv.sources]
/// torch = { index = "pytorch" }
/// ```
///
/// [`Indexes`] would contain a single entry mapping `torch` to `https://download.pytorch.org/whl/cu121`.
#[derive(Debug, Default, Clone)]
pub(crate) struct Indexes(FxHashMap<PackageName, IndexUrl>);
impl Indexes {
/// Determine the set of explicit, pinned indexes in the [`Manifest`].
pub(crate) fn from_manifest(
manifest: &Manifest,
markers: &ResolverMarkers,
dependencies: DependencyMode,
) -> Result<Self, ResolveError> {
let mut indexes = FxHashMap::<PackageName, IndexUrl>::default();
for requirement in manifest.requirements(markers, dependencies) {
let RequirementSource::Registry {
index: Some(index), ..
} = &requirement.source
else {
continue;
};
let index = IndexUrl::from(VerbatimUrl::from_url(index.clone()));
match indexes.entry(requirement.name.clone()) {
Entry::Occupied(entry) => {
let existing = entry.get();
if *existing != index {
return Err(ResolveError::ConflictingIndexes(
requirement.name.clone(),
existing.to_string(),
index.to_string(),
));
}
}
Entry::Vacant(entry) => {
entry.insert(index);
}
}
}
Ok(Self(indexes))
}
/// Return the explicit index for a given [`PackageName`].
pub(crate) fn get(&self, package_name: &PackageName) -> Option<&IndexUrl> {
self.0.get(package_name)
}
}

View file

@ -28,8 +28,9 @@ use uv_configuration::{Constraints, Overrides};
use uv_distribution::{ArchiveMetadata, DistributionDatabase};
use uv_distribution_types::{
BuiltDist, CompatibleDist, Dist, DistributionMetadata, IncompatibleDist, IncompatibleSource,
IncompatibleWheel, IndexCapabilities, IndexLocations, InstalledDist, PythonRequirementKind,
RemoteSource, ResolvedDist, ResolvedDistRef, SourceDist, VersionOrUrlRef,
IncompatibleWheel, IndexCapabilities, IndexLocations, IndexUrl, InstalledDist,
PythonRequirementKind, RemoteSource, ResolvedDist, ResolvedDistRef, SourceDist,
VersionOrUrlRef,
};
use uv_git::GitResolver;
use uv_normalize::{ExtraName, GroupName, PackageName};
@ -60,6 +61,7 @@ pub(crate) use crate::resolver::availability::{
use crate::resolver::batch_prefetch::BatchPrefetcher;
use crate::resolver::groups::Groups;
pub use crate::resolver::index::InMemoryIndex;
use crate::resolver::indexes::Indexes;
pub use crate::resolver::provider::{
DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
VersionsResponse, WheelMetadataResult,
@ -74,6 +76,7 @@ mod batch_prefetch;
mod fork_map;
mod groups;
mod index;
mod indexes;
mod locals;
mod provider;
mod reporter;
@ -100,6 +103,7 @@ struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
exclusions: Exclusions,
urls: Urls,
locals: Locals,
indexes: Indexes,
dependency_mode: DependencyMode,
hasher: HashStrategy,
markers: ResolverMarkers,
@ -204,6 +208,7 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
dependency_mode: options.dependency_mode,
urls: Urls::from_manifest(&manifest, &markers, git, options.dependency_mode)?,
locals: Locals::from_manifest(&manifest, &markers, options.dependency_mode),
indexes: Indexes::from_manifest(&manifest, &markers, options.dependency_mode)?,
groups: Groups::from_manifest(&manifest, &markers),
project: manifest.project,
workspace_members: manifest.workspace_members,
@ -377,7 +382,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
continue 'FORK;
};
state.next = highest_priority_pkg;
let url = state.next.name().and_then(|name| state.fork_urls.get(name));
let index = state.next.name().and_then(|name| self.indexes.get(name));
// Consider:
// ```toml
@ -390,7 +397,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
// since we weren't sure whether it might also be a URL requirement when
// transforming the requirements. For that case, we do another request here
// (idempotent due to caching).
self.request_package(&state.next, url, &request_sink)?;
self.request_package(&state.next, url, index, &request_sink)?;
prefetcher.version_tried(state.next.clone());
@ -529,7 +536,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
url: _,
} = dependency;
let url = package.name().and_then(|name| state.fork_urls.get(name));
self.visit_package(package, url, &request_sink)?;
let index = package.name().and_then(|name| self.indexes.get(name));
self.visit_package(package, url, index, &request_sink)?;
}
}
ForkedDependencies::Forked {
@ -697,7 +705,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
let url = package
.name()
.and_then(|name| forked_state.fork_urls.get(name));
self.visit_package(package, url, request_sink)?;
let index = package.name().and_then(|name| self.indexes.get(name));
self.visit_package(package, url, index, request_sink)?;
}
Ok(forked_state)
})
@ -720,6 +729,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&self,
package: &PubGrubPackage,
url: Option<&VerbatimParsedUrl>,
index: Option<&IndexUrl>,
request_sink: &Sender<Request>,
) -> Result<(), ResolveError> {
// Ignore unresolved URL packages.
@ -732,13 +742,14 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
return Ok(());
}
self.request_package(package, url, request_sink)
self.request_package(package, url, index, request_sink)
}
fn request_package(
&self,
package: &PubGrubPackage,
url: Option<&VerbatimParsedUrl>,
index: Option<&IndexUrl>,
request_sink: &Sender<Request>,
) -> Result<(), ResolveError> {
// Only request real package
@ -760,7 +771,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
} else {
// Emit a request to fetch the metadata for this package.
if self.index.packages().register(name.clone()) {
request_sink.blocking_send(Request::Package(name.clone()))?;
request_sink.blocking_send(Request::Package(name.clone(), index.cloned()))?;
}
}
Ok(())
@ -1709,9 +1720,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
) -> Result<Option<Response>, ResolveError> {
match request {
// Fetch package metadata from the registry.
Request::Package(package_name) => {
Request::Package(package_name, index) => {
let package_versions = provider
.get_package_versions(&package_name)
.get_package_versions(&package_name, index.as_ref())
.boxed_local()
.await
.map_err(ResolveError::Client)?;
@ -2505,7 +2516,7 @@ impl ResolutionPackage {
#[allow(clippy::large_enum_variant)]
pub(crate) enum Request {
/// A request to fetch the metadata for a package.
Package(PackageName),
Package(PackageName, Option<IndexUrl>),
/// A request to fetch the metadata for a built or source distribution.
Dist(Dist),
/// A request to fetch the metadata from an already-installed distribution.
@ -2554,7 +2565,7 @@ impl<'a> From<ResolvedDistRef<'a>> for Request {
impl Display for Request {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Package(package_name) => {
Self::Package(package_name, _) => {
write!(f, "Versions {package_name}")
}
Self::Dist(dist) => {

View file

@ -2,7 +2,7 @@ use std::future::Future;
use uv_configuration::BuildOptions;
use uv_distribution::{ArchiveMetadata, DistributionDatabase};
use uv_distribution_types::Dist;
use uv_distribution_types::{Dist, IndexUrl};
use uv_normalize::PackageName;
use uv_platform_tags::Tags;
use uv_types::{BuildContext, HashStrategy};
@ -49,6 +49,7 @@ pub trait ResolverProvider {
fn get_package_versions<'io>(
&'io self,
package_name: &'io PackageName,
index: Option<&'io IndexUrl>,
) -> impl Future<Output = PackageVersionsResult> + 'io;
/// Get the metadata for a distribution.
@ -111,11 +112,12 @@ impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a,
async fn get_package_versions<'io>(
&'io self,
package_name: &'io PackageName,
index: Option<&'io IndexUrl>,
) -> PackageVersionsResult {
let result = self
.fetcher
.client()
.managed(|client| client.simple(package_name))
.managed(|client| client.simple(package_name, index))
.await;
match result {

View file

@ -11,6 +11,7 @@ doctest = false
workspace = true
[dependencies]
uv-distribution-types = { workspace = true }
uv-pep440 = { workspace = true }
uv-pep508 = { workspace = true }
uv-pypi-types = { workspace = true }

View file

@ -1,13 +1,12 @@
use memchr::memmem::Finder;
use serde::Deserialize;
use std::collections::BTreeMap;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::LazyLock;
use memchr::memmem::Finder;
use serde::Deserialize;
use thiserror::Error;
use uv_distribution_types::Index;
use uv_pep440::VersionSpecifiers;
use uv_pep508::PackageName;
use uv_pypi_types::VerbatimParsedUrl;
@ -263,6 +262,7 @@ pub struct ToolUv {
#[serde(flatten)]
pub top_level: ResolverInstallerOptions,
pub sources: Option<BTreeMap<PackageName, Sources>>,
pub indexes: Option<Vec<Index>>,
}
#[derive(Debug, Error)]

View file

@ -7,7 +7,7 @@ use uv_configuration::{
ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple,
TrustedHost, TrustedPublishing,
};
use uv_distribution_types::{FlatIndexLocation, IndexUrl, StaticMetadata};
use uv_distribution_types::{FlatIndexLocation, Index, IndexUrl, StaticMetadata};
use uv_install_wheel::linker::LinkMode;
use uv_macros::{CombineOptions, OptionsMetadata};
use uv_normalize::{ExtraName, PackageName};
@ -230,6 +230,7 @@ pub struct GlobalOptions {
/// Settings relevant to all installer operations.
#[derive(Debug, Clone, Default, CombineOptions)]
pub struct InstallerOptions {
pub index: Option<Vec<Index>>,
pub index_url: Option<IndexUrl>,
pub extra_index_url: Option<Vec<IndexUrl>>,
pub no_index: Option<bool>,
@ -254,6 +255,7 @@ pub struct InstallerOptions {
/// Settings relevant to all resolver operations.
#[derive(Debug, Clone, Default, CombineOptions)]
pub struct ResolverOptions {
pub index: Option<Vec<Index>>,
pub index_url: Option<IndexUrl>,
pub extra_index_url: Option<Vec<IndexUrl>>,
pub no_index: Option<bool>,
@ -284,13 +286,52 @@ pub struct ResolverOptions {
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ResolverInstallerOptions {
/// The package indexes to use when resolving dependencies.
///
/// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
/// (the simple repository API), or a local directory laid out in the same format.
///
/// Indexes are considered in the order in which they're defined, such that the first-defined
/// index has the highest priority. Further, the indexes provided by this setting are given
/// higher priority than any indexes specified via [`index_url`](#index-url) or
/// [`extra_index_url`](#extra-index-url). uv will only consider the first index that contains
/// a given package, unless an alternative [index strategy](#index-strategy) is specified.
///
/// If an index is marked as `explicit = true`, it will be used exclusively for those
/// dependencies that select it explicitly via `[tool.uv.sources]`, as in:
///
/// ```toml
/// [[tool.uv.index]]
/// name = "pytorch"
/// url = "https://download.pytorch.org/whl/cu121"
/// explicit = true
///
/// [tool.uv.sources]
/// torch = { index = "pytorch" }
/// ```
///
/// If an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is
/// given the lowest priority when resolving packages. Additionally, marking an index as default will disable the
/// PyPI default index.
#[option(
default = "\"[]\"",
value_type = "dict",
example = r#"
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
"#
)]
pub index: Option<Vec<Index>>,
/// The URL of the Python package index (by default: <https://pypi.org/simple>).
///
/// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
/// (the simple repository API), or a local directory laid out in the same format.
///
/// The index provided by this setting is given lower priority than any indexes specified via
/// [`extra_index_url`](#extra-index-url).
/// [`extra_index_url`](#extra-index-url) or [`index`](#index).
///
/// (Deprecated: use `index` instead.)
#[option(
default = "\"https://pypi.org/simple\"",
value_type = "str",
@ -305,10 +346,13 @@ pub struct ResolverInstallerOptions {
/// (the simple repository API), or a local directory laid out in the same format.
///
/// All indexes provided via this flag take priority over the index specified by
/// [`index_url`](#index-url). When multiple indexes are provided, earlier values take priority.
/// [`index_url`](#index-url) or [`index`](#index) with `default = true`. When multiple indexes
/// are provided, earlier values take priority.
///
/// To control uv's resolution strategy when multiple indexes are present, see
/// [`index_strategy`](#index-strategy).
///
/// (Deprecated: use `index` instead.)
#[option(
default = "[]",
value_type = "list[str]",
@ -347,8 +391,8 @@ pub struct ResolverInstallerOptions {
///
/// By default, uv will stop at the first index on which a given package is available, and
/// limit resolutions to those present on that first index (`first-match`). This prevents
/// "dependency confusion" attacks, whereby an attack can upload a malicious package under the
/// same name to a secondary.
/// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the
/// same name to an alternate index.
#[option(
default = "\"first-index\"",
value_type = "str",
@ -693,6 +737,9 @@ pub struct PipOptions {
"#
)]
pub prefix: Option<PathBuf>,
#[serde(skip)]
#[cfg_attr(feature = "schemars", schemars(skip))]
pub index: Option<Vec<Index>>,
/// The URL of the Python package index (by default: <https://pypi.org/simple>).
///
/// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
@ -756,8 +803,8 @@ pub struct PipOptions {
///
/// By default, uv will stop at the first index on which a given package is available, and
/// limit resolutions to those present on that first index (`first-match`). This prevents
/// "dependency confusion" attacks, whereby an attack can upload a malicious package under the
/// same name to a secondary.
/// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the
/// same name to an alternate index.
#[option(
default = "\"first-index\"",
value_type = "str",
@ -1299,6 +1346,7 @@ pub struct PipOptions {
impl From<ResolverInstallerOptions> for ResolverOptions {
fn from(value: ResolverInstallerOptions) -> Self {
Self {
index: value.index,
index_url: value.index_url,
extra_index_url: value.extra_index_url,
no_index: value.no_index,
@ -1328,6 +1376,7 @@ impl From<ResolverInstallerOptions> for ResolverOptions {
impl From<ResolverInstallerOptions> for InstallerOptions {
fn from(value: ResolverInstallerOptions) -> Self {
Self {
index: value.index,
index_url: value.index_url,
extra_index_url: value.extra_index_url,
no_index: value.no_index,
@ -1361,6 +1410,7 @@ impl From<ResolverInstallerOptions> for InstallerOptions {
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ToolOptions {
pub index: Option<Vec<Index>>,
pub index_url: Option<IndexUrl>,
pub extra_index_url: Option<Vec<IndexUrl>>,
pub no_index: Option<bool>,
@ -1387,6 +1437,7 @@ pub struct ToolOptions {
impl From<ResolverInstallerOptions> for ToolOptions {
fn from(value: ResolverInstallerOptions) -> Self {
Self {
index: value.index,
index_url: value.index_url,
extra_index_url: value.extra_index_url,
no_index: value.no_index,
@ -1415,6 +1466,7 @@ impl From<ResolverInstallerOptions> for ToolOptions {
impl From<ToolOptions> for ResolverInstallerOptions {
fn from(value: ToolOptions) -> Self {
Self {
index: value.index,
index_url: value.index_url,
extra_index_url: value.extra_index_url,
no_index: value.no_index,
@ -1464,6 +1516,7 @@ pub struct OptionsWire {
// #[serde(flatten)]
// top_level: ResolverInstallerOptions,
index: Option<Vec<Index>>,
index_url: Option<IndexUrl>,
extra_index_url: Option<Vec<IndexUrl>>,
no_index: Option<bool>,
@ -1528,6 +1581,7 @@ impl From<OptionsWire> for Options {
concurrent_downloads,
concurrent_builds,
concurrent_installs,
index,
index_url,
extra_index_url,
no_index,
@ -1581,6 +1635,7 @@ impl From<OptionsWire> for Options {
concurrent_installs,
},
top_level: ResolverInstallerOptions {
index,
index_url,
extra_index_url,
no_index,

View file

@ -16,6 +16,7 @@ doctest = false
workspace = true
[dependencies]
uv-distribution-types = { workspace = true }
uv-fs = { workspace = true, features = ["tokio", "schemars"] }
uv-git = { workspace = true }
uv-macros = { workspace = true }

View file

@ -15,7 +15,7 @@ use std::str::FromStr;
use std::{collections::BTreeMap, mem};
use thiserror::Error;
use url::Url;
use uv_distribution_types::Index;
use uv_fs::{relative_to, PortablePathBuf};
use uv_git::GitReference;
use uv_macros::OptionsMetadata;
@ -154,9 +154,49 @@ pub struct ToolUv {
/// The sources to use (e.g., workspace members, Git repositories, local paths) when resolving
/// dependencies.
pub sources: Option<ToolUvSources>,
/// The indexes to use when resolving dependencies.
///
/// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
/// (the simple repository API), or a local directory laid out in the same format.
///
/// Indexes are considered in the order in which they're defined, such that the first-defined
/// index has the highest priority. Further, the indexes provided by this setting are given
/// higher priority than any indexes specified via [`index_url`](#index-url) or
/// [`extra_index_url`](#extra-index-url). uv will only consider the first index that contains
/// a given package, unless an alternative [index strategy](#index-strategy) is specified.
///
/// If an index is marked as `explicit = true`, it will be used exclusively for those
/// dependencies that select it explicitly via `[tool.uv.sources]`, as in:
///
/// ```toml
/// [[tool.uv.index]]
/// name = "pytorch"
/// url = "https://download.pytorch.org/whl/cu121"
/// explicit = true
///
/// [tool.uv.sources]
/// torch = { index = "pytorch" }
/// ```
///
/// If an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is
/// given the lowest priority when resolving packages. Additionally, marking an index as default will disable the
/// PyPI default index.
#[option(
default = "\"[]\"",
value_type = "dict",
example = r#"
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
"#
)]
pub index: Option<Vec<Index>>,
/// The workspace definition for the project, if any.
#[option_group]
pub workspace: Option<ToolUvWorkspace>,
/// Whether the project is managed by uv. If `false`, uv will ignore the project when
/// `uv run` is invoked.
#[option(
@ -167,6 +207,7 @@ pub struct ToolUv {
"#
)]
pub managed: Option<bool>,
/// Whether the project should be considered a Python package, or a non-package ("virtual")
/// project.
///
@ -185,6 +226,7 @@ pub struct ToolUv {
"#
)]
pub package: Option<bool>,
/// The project's development dependencies. Development dependencies will be installed by
/// default in `uv run` and `uv sync`, but will not appear in the project's published metadata.
#[cfg_attr(
@ -202,6 +244,7 @@ pub struct ToolUv {
"#
)]
pub dev_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
/// A list of supported environments against which to resolve dependencies.
///
/// By default, uv will resolve for all possible environments during a `uv lock` operation.
@ -226,6 +269,7 @@ pub struct ToolUv {
"#
)]
pub environments: Option<SupportedEnvironments>,
/// Overrides to apply when resolving the project's dependencies.
///
/// Overrides are used to force selection of a specific version of a package, regardless of the
@ -261,6 +305,7 @@ pub struct ToolUv {
"#
)]
pub override_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
/// Constraints to apply when resolving the project's dependencies.
///
/// Constraints are used to restrict the versions of dependencies that are selected during
@ -315,7 +360,7 @@ impl ToolUvSources {
impl<'de> serde::de::Deserialize<'de> for ToolUvSources {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
D: Deserializer<'de>,
{
struct SourcesVisitor;
@ -581,7 +626,6 @@ pub enum Source {
},
/// A dependency pinned to a specific index, e.g., `torch` after setting `torch` to `https://download.pytorch.org/whl/cu118`.
Registry {
// TODO(konstin): The string is more-or-less a placeholder
index: String,
#[serde(
skip_serializing_if = "uv_pep508::marker::ser::is_empty",

View file

@ -6,7 +6,7 @@ use rustc_hash::FxHashSet;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use tracing::{debug, trace, warn};
use uv_distribution_types::Index;
use uv_fs::{Simplified, CWD};
use uv_normalize::{GroupName, PackageName, DEV_DEPENDENCIES};
use uv_pep508::{MarkerTree, RequirementOrigin, VerbatimUrl};
@ -15,7 +15,7 @@ use uv_static::EnvVars;
use uv_warnings::{warn_user, warn_user_once};
use crate::pyproject::{
Project, PyProjectToml, PyprojectTomlError, Source, Sources, ToolUvSources, ToolUvWorkspace,
Project, PyProjectToml, PyprojectTomlError, Sources, ToolUvSources, ToolUvWorkspace,
};
#[derive(thiserror::Error, Debug)]
@ -80,6 +80,10 @@ pub struct Workspace {
///
/// This table is overridden by the project sources.
sources: BTreeMap<PackageName, Sources>,
/// The index table from the workspace `pyproject.toml`.
///
/// This table is overridden by the project indexes.
indexes: Vec<Index>,
/// The `pyproject.toml` of the workspace root.
pyproject_toml: PyProjectToml,
}
@ -489,20 +493,9 @@ impl Workspace {
&self.sources
}
/// Returns an iterator over all sources in the workspace.
pub fn iter_sources(&self) -> impl Iterator<Item = &Source> {
self.packages
.values()
.filter_map(|member| {
member.pyproject_toml().tool.as_ref().and_then(|tool| {
tool.uv
.as_ref()
.and_then(|uv| uv.sources.as_ref())
.map(ToolUvSources::inner)
.map(|sources| sources.values().flat_map(Sources::iter))
})
})
.flatten()
/// The index table from the workspace `pyproject.toml`.
pub fn indexes(&self) -> &[Index] {
&self.indexes
}
/// The `pyproject.toml` of the workspace.
@ -719,11 +712,18 @@ impl Workspace {
.and_then(|uv| uv.sources)
.map(ToolUvSources::into_inner)
.unwrap_or_default();
let workspace_indexes = workspace_pyproject_toml
.tool
.clone()
.and_then(|tool| tool.uv)
.and_then(|uv| uv.index)
.unwrap_or_default();
Ok(Workspace {
install_path: workspace_root,
packages: workspace_members,
sources: workspace_sources,
indexes: workspace_indexes,
pyproject_toml: workspace_pyproject_toml,
})
}
@ -1025,6 +1025,7 @@ impl ProjectWorkspace {
// There may be package sources, but we don't need to duplicate them into the
// workspace sources.
sources: BTreeMap::default(),
indexes: Vec::default(),
pyproject_toml: project_pyproject_toml.clone(),
},
});

View file

@ -43,7 +43,7 @@ async fn albatross_in_example() {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]/albatross-in-example/examples/bird-feeder",
"project_name": "bird-feeder",
@ -65,6 +65,7 @@ async fn albatross_in_example() {
}
},
"sources": {},
"indexes": [],
"pyproject_toml": {
"project": {
"name": "bird-feeder",
@ -79,7 +80,7 @@ async fn albatross_in_example() {
}
}
}
"#);
"###);
});
}
@ -94,7 +95,7 @@ async fn albatross_project_in_excluded() {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder",
"project_name": "bird-feeder",
@ -116,6 +117,7 @@ async fn albatross_project_in_excluded() {
}
},
"sources": {},
"indexes": [],
"pyproject_toml": {
"project": {
"name": "bird-feeder",
@ -130,7 +132,7 @@ async fn albatross_project_in_excluded() {
}
}
}
"#);
"###);
});
}
@ -144,7 +146,7 @@ async fn albatross_root_workspace() {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]/albatross-root-workspace",
"project_name": "albatross",
@ -200,6 +202,7 @@ async fn albatross_root_workspace() {
}
]
},
"indexes": [],
"pyproject_toml": {
"project": {
"name": "albatross",
@ -220,6 +223,7 @@ async fn albatross_root_workspace() {
}
]
},
"index": null,
"workspace": {
"members": [
"packages/*"
@ -237,7 +241,7 @@ async fn albatross_root_workspace() {
}
}
}
"#);
"###);
});
}
@ -252,7 +256,7 @@ async fn albatross_virtual_workspace() {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]/albatross-virtual-workspace/packages/albatross",
"project_name": "albatross",
@ -302,11 +306,13 @@ async fn albatross_virtual_workspace() {
}
},
"sources": {},
"indexes": [],
"pyproject_toml": {
"project": null,
"tool": {
"uv": {
"sources": null,
"index": null,
"workspace": {
"members": [
"packages/*"
@ -324,7 +330,7 @@ async fn albatross_virtual_workspace() {
}
}
}
"#);
"###);
});
}
@ -338,7 +344,7 @@ async fn albatross_just_project() {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]/albatross-just-project",
"project_name": "albatross",
@ -360,6 +366,7 @@ async fn albatross_just_project() {
}
},
"sources": {},
"indexes": [],
"pyproject_toml": {
"project": {
"name": "albatross",
@ -374,7 +381,7 @@ async fn albatross_just_project() {
}
}
}
"#);
"###);
});
}
#[tokio::test]
@ -456,7 +463,7 @@ async fn exclude_package() -> Result<()> {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]",
"project_name": "albatross",
@ -491,6 +498,7 @@ async fn exclude_package() -> Result<()> {
}
},
"sources": {},
"indexes": [],
"pyproject_toml": {
"project": {
"name": "albatross",
@ -504,6 +512,7 @@ async fn exclude_package() -> Result<()> {
"tool": {
"uv": {
"sources": null,
"index": null,
"workspace": {
"members": [
"packages/*"
@ -523,7 +532,7 @@ async fn exclude_package() -> Result<()> {
}
}
}
"#);
"###);
});
// Rewrite the members to both include and exclude `bird-feeder` by name.
@ -554,7 +563,7 @@ async fn exclude_package() -> Result<()> {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]",
"project_name": "albatross",
@ -589,6 +598,7 @@ async fn exclude_package() -> Result<()> {
}
},
"sources": {},
"indexes": [],
"pyproject_toml": {
"project": {
"name": "albatross",
@ -602,6 +612,7 @@ async fn exclude_package() -> Result<()> {
"tool": {
"uv": {
"sources": null,
"index": null,
"workspace": {
"members": [
"packages/seeds",
@ -622,7 +633,7 @@ async fn exclude_package() -> Result<()> {
}
}
}
"#);
"###);
});
// Rewrite the exclusion to use the top-level directory (`packages`).
@ -653,7 +664,7 @@ async fn exclude_package() -> Result<()> {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]",
"project_name": "albatross",
@ -701,6 +712,7 @@ async fn exclude_package() -> Result<()> {
}
},
"sources": {},
"indexes": [],
"pyproject_toml": {
"project": {
"name": "albatross",
@ -714,6 +726,7 @@ async fn exclude_package() -> Result<()> {
"tool": {
"uv": {
"sources": null,
"index": null,
"workspace": {
"members": [
"packages/seeds",
@ -734,7 +747,7 @@ async fn exclude_package() -> Result<()> {
}
}
}
"#);
"###);
});
// Rewrite the exclusion to use the top-level directory with a glob (`packages/*`).
@ -765,7 +778,7 @@ async fn exclude_package() -> Result<()> {
{
".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]"
},
@r#"
@r###"
{
"project_root": "[ROOT]",
"project_name": "albatross",
@ -787,6 +800,7 @@ async fn exclude_package() -> Result<()> {
}
},
"sources": {},
"indexes": [],
"pyproject_toml": {
"project": {
"name": "albatross",
@ -800,6 +814,7 @@ async fn exclude_package() -> Result<()> {
"tool": {
"uv": {
"sources": null,
"index": null,
"workspace": {
"members": [
"packages/seeds",
@ -820,7 +835,7 @@ async fn exclude_package() -> Result<()> {
}
}
}
"#);
"###);
});
Ok(())

View file

@ -400,7 +400,7 @@ async fn build_package(
.into_interpreter();
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
for url in index_locations.allowed_urls() {
store_credentials_from_url(url);
}

View file

@ -16,7 +16,7 @@ use uv_configuration::{
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::BuildDispatch;
use uv_distribution_types::{
DependencyMetadata, IndexCapabilities, IndexLocations, NameRequirementSpecification,
DependencyMetadata, Index, IndexCapabilities, IndexLocations, NameRequirementSpecification,
UnresolvedRequirementSpecification, Verbatim,
};
use uv_fs::Simplified;
@ -273,11 +273,18 @@ pub(crate) async fn pip_compile(
let dev = Vec::default();
// Incorporate any index locations from the provided sources.
let index_locations =
index_locations.combine(index_url, extra_index_urls, find_links, no_index);
let index_locations = index_locations.combine(
extra_index_urls
.into_iter()
.map(Index::from_extra_index_url)
.chain(index_url.map(Index::from_index_url))
.collect(),
find_links,
no_index,
);
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
for url in index_locations.allowed_urls() {
store_credentials_from_url(url);
}
@ -446,12 +453,12 @@ pub(crate) async fn pip_compile(
// If necessary, include the `--index-url` and `--extra-index-url` locations.
if include_index_url {
if let Some(index) = index_locations.index() {
writeln!(writer, "--index-url {}", index.verbatim())?;
if let Some(index) = index_locations.default_index() {
writeln!(writer, "--index-url {}", index.url().verbatim())?;
wrote_preamble = true;
}
for extra_index in index_locations.extra_index() {
writeln!(writer, "--extra-index-url {}", extra_index.verbatim())?;
for extra_index in index_locations.implicit_indexes() {
writeln!(writer, "--extra-index-url {}", extra_index.url().verbatim())?;
wrote_preamble = true;
}
}

View file

@ -14,7 +14,7 @@ use uv_configuration::{
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::BuildDispatch;
use uv_distribution_types::{
DependencyMetadata, IndexLocations, NameRequirementSpecification, Resolution,
DependencyMetadata, Index, IndexLocations, NameRequirementSpecification, Resolution,
UnresolvedRequirementSpecification,
};
use uv_fs::Simplified;
@ -275,11 +275,18 @@ pub(crate) async fn pip_install(
let dev = Vec::default();
// Incorporate any index locations from the provided sources.
let index_locations =
index_locations.combine(index_url, extra_index_urls, find_links, no_index);
let index_locations = index_locations.combine(
extra_index_urls
.into_iter()
.map(Index::from_extra_index_url)
.chain(index_url.map(Index::from_index_url))
.collect(),
find_links,
no_index,
);
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
for url in index_locations.allowed_urls() {
store_credentials_from_url(url);
}

View file

@ -13,7 +13,7 @@ use uv_configuration::{
};
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::BuildDispatch;
use uv_distribution_types::{DependencyMetadata, IndexLocations, Resolution};
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, Resolution};
use uv_fs::Simplified;
use uv_install_wheel::linker::LinkMode;
use uv_installer::SitePackages;
@ -211,11 +211,18 @@ pub(crate) async fn pip_sync(
};
// Incorporate any index locations from the provided sources.
let index_locations =
index_locations.combine(index_url, extra_index_urls, find_links, no_index);
let index_locations = index_locations.combine(
extra_index_urls
.into_iter()
.map(Index::from_extra_index_url)
.chain(index_url.map(Index::from_index_url))
.collect(),
find_links,
no_index,
);
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
for url in index_locations.allowed_urls() {
store_credentials_from_url(url);
}

View file

@ -246,7 +246,7 @@ pub(crate) async fn add(
resolution_environment(python_version, python_platform, target.interpreter())?;
// Add all authenticated sources to the cache.
for url in settings.index_locations.urls() {
for url in settings.index_locations.allowed_urls() {
store_credentials_from_url(url);
}

View file

@ -364,7 +364,7 @@ async fn do_lock(
PythonRequirement::from_requires_python(interpreter, requires_python.clone());
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
for url in index_locations.allowed_urls() {
store_credentials_from_url(url);
}

View file

@ -12,7 +12,7 @@ use uv_configuration::{Concurrency, Constraints, ExtrasSpecification, Reinstall,
use uv_dispatch::BuildDispatch;
use uv_distribution::DistributionDatabase;
use uv_distribution_types::{
Resolution, UnresolvedRequirement, UnresolvedRequirementSpecification,
Index, Resolution, UnresolvedRequirement, UnresolvedRequirementSpecification,
};
use uv_fs::Simplified;
use uv_git::ResolvedRepositoryReference;
@ -647,7 +647,7 @@ pub(crate) async fn resolve_names(
} = settings;
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
for url in index_locations.allowed_urls() {
store_credentials_from_url(url);
}
@ -794,7 +794,7 @@ pub(crate) async fn resolve_environment<'a>(
let python_requirement = PythonRequirement::from_interpreter(interpreter);
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
for url in index_locations.allowed_urls() {
store_credentials_from_url(url);
}
@ -952,7 +952,7 @@ pub(crate) async fn sync_environment(
let tags = venv.interpreter().tags()?;
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
for url in index_locations.allowed_urls() {
store_credentials_from_url(url);
}
@ -1140,7 +1140,7 @@ pub(crate) async fn update_environment(
}
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
for url in index_locations.allowed_urls() {
store_credentials_from_url(url);
}
@ -1349,7 +1349,7 @@ fn warn_on_requirements_txt_setting(
warn_user_once!("Ignoring `--no-index` from requirements file. Instead, use the `--no-index` command-line argument, or set `no-index` in a `uv.toml` or `pyproject.toml` file.");
} else {
if let Some(index_url) = index_url {
if settings.index_locations.index() != Some(index_url) {
if settings.index_locations.default_index().map(Index::url) != Some(index_url) {
warn_user_once!(
"Ignoring `--index-url` from requirements file: `{index_url}`. Instead, use the `--index-url` command-line argument, or set `index-url` in a `uv.toml` or `pyproject.toml` file."
);
@ -1358,8 +1358,8 @@ fn warn_on_requirements_txt_setting(
for extra_index_url in extra_index_urls {
if !settings
.index_locations
.extra_index()
.contains(extra_index_url)
.implicit_indexes()
.any(|index| index.url() == extra_index_url)
{
warn_user_once!(
"Ignoring `--extra-index-url` from requirements file: `{extra_index_url}`. Instead, use the `--extra-index-url` command-line argument, or set `extra-index-url` in a `uv.toml` or `pyproject.toml` file.`"

View file

@ -215,7 +215,19 @@ pub(crate) async fn run(
// Install the script requirements, if necessary. Otherwise, use an isolated environment.
if let Some(dependencies) = script.dependencies {
// // Collect any `tool.uv.sources` from the script.
// Collect any `tool.uv.index` from the script.
let empty = Vec::default();
let script_indexes = match settings.sources {
SourceStrategy::Enabled => script
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.indexes.as_deref())
.unwrap_or(&empty),
SourceStrategy::Disabled => &empty,
};
// Collect any `tool.uv.sources` from the script.
let empty = BTreeMap::default();
let script_sources = match settings.sources {
SourceStrategy::Enabled => script
@ -234,6 +246,7 @@ pub(crate) async fn run(
requirement,
script_dir.as_ref(),
script_sources,
script_indexes,
)
.map_ok(LoweredRequirement::into_inner)
})

View file

@ -276,7 +276,7 @@ pub(super) async fn do_sync(
let resolution = apply_editable_mode(resolution, editable);
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
for url in index_locations.allowed_urls() {
uv_auth::store_credentials_from_url(url);
}

View file

@ -229,7 +229,7 @@ async fn venv_impl(
let interpreter = python.into_interpreter();
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
for url in index_locations.allowed_urls() {
store_credentials_from_url(url);
}
@ -278,7 +278,7 @@ async fn venv_impl(
let interpreter = venv.interpreter();
// Add all authenticated sources to the cache.
for url in index_locations.urls() {
for url in index_locations.allowed_urls() {
store_credentials_from_url(url);
}

View file

@ -24,7 +24,7 @@ use uv_configuration::{
NoBinary, NoBuild, PreviewMode, Reinstall, SourceStrategy, TargetTriple, TrustedHost,
TrustedPublishing, Upgrade, VersionControlSystem,
};
use uv_distribution_types::{DependencyMetadata, IndexLocations};
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations};
use uv_install_wheel::linker::LinkMode;
use uv_normalize::PackageName;
use uv_pep508::{ExtraName, RequirementOrigin};
@ -1921,8 +1921,19 @@ impl From<ResolverOptions> for ResolverSettings {
fn from(value: ResolverOptions) -> Self {
Self {
index_locations: IndexLocations::new(
value.index_url,
value.extra_index_url.unwrap_or_default(),
value
.index
.into_iter()
.flatten()
.chain(
value
.extra_index_url
.into_iter()
.flatten()
.map(Index::from_extra_index_url),
)
.chain(value.index_url.into_iter().map(Index::from_index_url))
.collect(),
value.find_links.unwrap_or_default(),
value.no_index.unwrap_or_default(),
),
@ -2048,8 +2059,19 @@ impl From<ResolverInstallerOptions> for ResolverInstallerSettings {
fn from(value: ResolverInstallerOptions) -> Self {
Self {
index_locations: IndexLocations::new(
value.index_url,
value.extra_index_url.unwrap_or_default(),
value
.index
.into_iter()
.flatten()
.chain(
value
.extra_index_url
.into_iter()
.flatten()
.map(Index::from_extra_index_url),
)
.chain(value.index_url.into_iter().map(Index::from_index_url))
.collect(),
value.find_links.unwrap_or_default(),
value.no_index.unwrap_or_default(),
),
@ -2155,6 +2177,7 @@ impl PipSettings {
break_system_packages,
target,
prefix,
index,
index_url,
extra_index_url,
no_index,
@ -2206,6 +2229,7 @@ impl PipSettings {
} = pip.unwrap_or_default();
let ResolverInstallerOptions {
index: top_level_index,
index_url: top_level_index_url,
extra_index_url: top_level_extra_index_url,
no_index: top_level_no_index,
@ -2237,6 +2261,7 @@ impl PipSettings {
// preferring the latter.
//
// For example, prefer `tool.uv.pip.index-url` over `tool.uv.index-url`.
let index = index.combine(top_level_index);
let index_url = index_url.combine(top_level_index_url);
let extra_index_url = extra_index_url.combine(top_level_extra_index_url);
let no_index = no_index.combine(top_level_no_index);
@ -2262,10 +2287,25 @@ impl PipSettings {
Self {
index_locations: IndexLocations::new(
args.index_url.combine(index_url),
args.extra_index_url
.combine(extra_index_url)
.unwrap_or_default(),
args.index
.into_iter()
.flatten()
.chain(
args.extra_index_url
.into_iter()
.flatten()
.map(Index::from_extra_index_url),
)
.chain(args.index_url.into_iter().map(Index::from_index_url))
.chain(index.into_iter().flatten())
.chain(
extra_index_url
.into_iter()
.flatten()
.map(Index::from_extra_index_url),
)
.chain(index_url.into_iter().map(Index::from_index_url))
.collect(),
args.find_links.combine(find_links).unwrap_or_default(),
args.no_index.combine(no_index).unwrap_or_default(),
),

View file

@ -7751,7 +7751,7 @@ fn lock_local_index() -> Result<()> {
[tool.uv]
extra-index-url = ["{}"]
"#,
Url::from_directory_path(&root).unwrap().as_str()
Url::from_file_path(&root).unwrap().as_str()
})?;
uv_snapshot!(context.filters(), context.lock().env_remove(EnvVars::UV_EXCLUDE_NEWER), @r###"
@ -7765,7 +7765,7 @@ fn lock_local_index() -> Result<()> {
let lock = context.read("uv.lock");
let index = Url::from_directory_path(&root).unwrap().to_string();
let index = Url::from_file_path(&root).unwrap().to_string();
let filters = [(index.as_str(), "file://[TMP]")]
.into_iter()
.chain(context.filters())
@ -11883,6 +11883,591 @@ fn lock_trailing_slash() -> Result<()> {
Ok(())
}
#[test]
fn lock_explicit_index() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0", "iniconfig==2.0.0"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
iniconfig = { index = "test" }
[[tool.uv.index]]
name = "test"
url = "https://test.pypi.org/simple"
explicit = true
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 5 packages in [TIME]
"###);
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "anyio"
version = "3.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/68/fe/7ce1926952c8a403b35029e194555558514b365ad77d75125f521a2bec62/anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0", size = 80873 },
]
[[package]]
name = "idna"
version = "3.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567 },
]
[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://test.pypi.org/simple" }
sdist = { url = "https://test-files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
wheels = [
{ url = "https://test-files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
]
[[package]]
name = "project"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "anyio" },
{ name = "iniconfig" },
]
[package.metadata]
requires-dist = [
{ name = "anyio", specifier = "==3.7.0" },
{ name = "iniconfig", specifier = "==2.0.0", index = "https://test.pypi.org/simple" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
]
"###
);
});
Ok(())
}
#[test]
fn lock_named_index() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["typing-extensions"]
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
explicit = true
[[tool.uv.index]]
name = "heron"
url = "https://pypi-proxy.fly.dev/simple"
[[tool.uv.index]]
name = "test"
url = "https://test.pypi.org/simple"
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
"###);
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "project"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "typing-extensions" },
]
[package.metadata]
requires-dist = [{ name = "typing-extensions" }]
[[package]]
name = "typing-extensions"
version = "4.10.0"
source = { registry = "https://pypi-proxy.fly.dev/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/16/3a/0d26ce356c7465a19c9ea8814b960f8a36c3b0d07c323176620b7b483e44/typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb", size = 77558 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/de/dc04a3ea60b22624b51c703a84bbe0184abcd1d0b9bc8074b5d6b7ab90bb/typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475", size = 33926 },
]
"###
);
});
Ok(())
}
#[test]
fn lock_default_index() -> Result<()> {
let context = TestContext::new("3.12");
// If an index is included, PyPI will still be used as the default index.
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
"###);
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
]
[[package]]
name = "project"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "iniconfig" },
]
[package.metadata]
requires-dist = [{ name = "iniconfig" }]
"###
);
});
// Unless that index is marked as the default.
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
default = true
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
Because iniconfig was not found in the package registry and your project depends on iniconfig, we can conclude that your project's requirements are unsatisfiable.
"###);
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
]
[[package]]
name = "project"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "iniconfig" },
]
[package.metadata]
requires-dist = [{ name = "iniconfig" }]
"###
);
});
Ok(())
}
#[test]
fn lock_named_index_cli() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0", "jinja2"]
[tool.uv.sources]
jinja2 = { index = "pytorch" }
"#,
)?;
// The package references a non-existent index.
uv_snapshot!(context.filters(), context.lock(), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to build: `project @ file://[TEMP_DIR]/`
Caused by: Failed to parse entry for: `jinja2`
Caused by: Package `jinja2` references an undeclared index: `pytorch`
"###);
// This also isn't supported right now; you need to specify the index in the `pyproject.toml`.
uv_snapshot!(context.filters(), context.lock().arg("--index").arg("pytorch=https://download.pytorch.org/whl/cu121"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to build: `project @ file://[TEMP_DIR]/`
Caused by: Failed to parse entry for: `jinja2`
Caused by: Package `jinja2` references an undeclared index: `pytorch`
"###);
Ok(())
}
/// If a name is reused, the higher-priority index should "overwrite" the lower-priority index.
/// In other words, the lower-priority index should be ignored entirely during implicit resolution.
///
/// In this test, we should use PyPI (the default index) and ignore `https://example.com` entirely.
/// (Querying `https://example.com` would fail with a 500.)
#[test]
fn lock_repeat_named_index() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
[[tool.uv.index]]
name = "pytorch"
url = "https://example.com"
"#,
)?;
// Fall back to PyPI, since `iniconfig` doesn't exist on the PyTorch index.
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
"###);
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
]
[[package]]
name = "project"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "iniconfig" },
]
[package.metadata]
requires-dist = [{ name = "iniconfig" }]
"###
);
});
Ok(())
}
/// If a name is reused, the higher-priority index should "overwrite" the lower-priority index.
/// This includes names passed in via the CLI.
#[test]
fn lock_repeat_named_index_cli() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["jinja2==3.1.2"]
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
"#,
)?;
// Resolve to the PyTorch index.
uv_snapshot!(context.filters(), context.lock().env_remove("UV_EXCLUDE_NEWER"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
"###);
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"
[[package]]
name = "jinja2"
version = "3.1.2"
source = { registry = "https://download.pytorch.org/whl/cu121" }
dependencies = [
{ name = "markupsafe" },
]
wheels = [
{ url = "https://download.pytorch.org/whl/Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" },
]
[[package]]
name = "markupsafe"
version = "2.1.5"
source = { registry = "https://download.pytorch.org/whl/cu121" }
wheels = [
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1" },
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4" },
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee" },
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5" },
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b" },
{ url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb" },
]
[[package]]
name = "project"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "jinja2" },
]
[package.metadata]
requires-dist = [{ name = "jinja2", specifier = "==3.1.2" }]
"###
);
});
// Resolve to PyPI, since the PyTorch index is replaced by the Packse index, which doesn't
// include `jinja2`.
uv_snapshot!(context.filters(), context.lock().arg("--index").arg(format!("pytorch={}", packse_index_url())).env_remove("UV_EXCLUDE_NEWER"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
"###);
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"
[[package]]
name = "jinja2"
version = "3.1.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7a/ff/75c28576a1d900e87eb6335b063fab47a8ef3c8b4d88524c4bf78f670cce/Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", size = 268239 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", size = 133101 },
]
[[package]]
name = "markupsafe"
version = "2.1.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215 },
{ url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069 },
{ url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452 },
{ url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462 },
{ url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869 },
{ url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906 },
{ url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296 },
{ url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038 },
{ url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572 },
{ url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127 },
]
[[package]]
name = "project"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "jinja2" },
]
[package.metadata]
requires-dist = [{ name = "jinja2", specifier = "==3.1.2" }]
"###
);
});
Ok(())
}
/// Lock a project with `package = false`, making it a virtual project.
#[test]
fn lock_explicit_virtual_project() -> Result<()> {

View file

@ -191,7 +191,7 @@ fn invalid_pyproject_toml_option_unknown_field() -> Result<()> {
|
2 | unknown = "field"
| ^^^^^^^
unknown field `unknown`, expected one of `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `publish-url`, `trusted-publishing`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `workspace`, `sources`, `dev-dependencies`, `managed`, `package`
unknown field `unknown`, expected one of `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `publish-url`, `trusted-publishing`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `workspace`, `sources`, `dev-dependencies`, `managed`, `package`
Resolved in [TIME]
Audited in [TIME]

File diff suppressed because it is too large Load diff

View file

@ -3075,3 +3075,61 @@ fn sync_no_sources_missing_member() -> Result<()> {
Ok(())
}
#[test]
fn sync_explicit() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "root"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"idna>2",
]
[[tool.uv.index]]
name = "test"
url = "https://test.pypi.org/simple"
explicit = true
[tool.uv.sources]
idna = { index = "test" }
"#,
)?;
uv_snapshot!(context.filters(), context.sync(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ idna==2.7
"###);
// Clear the environment.
fs_err::remove_dir_all(&context.venv)?;
// The package should be drawn from the cache.
uv_snapshot!(context.filters(), context.sync(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ idna==2.7
"###);
Ok(())
}

View file

@ -70,6 +70,7 @@ standards-compliant `project.dependencies` table.
During development, a project may rely on a package that isn't available on PyPI. The following
additional sources are supported by uv:
- Index: A package resolved from a specific package index.
- Git: A Git repository.
- URL: A remote wheel or source distribution.
- Path: A local wheel, source distribution, or project directory.
@ -91,6 +92,29 @@ $ uv lock --no-sources
The use of `--no-sources` will also prevent uv from discovering any
[workspace members](#workspace-member) that could satisfy a given dependency.
### Index
To pin a Python package to a specific index, add a named index to the `pyproject.toml`:
```toml title="pyproject.toml"
[project]
dependencies = [
"torch",
]
[tool.uv.sources]
torch = { index = "pytorch" }
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cpu"
explicit = true
```
The `explicit` flag is optional and indicates that the index should _only_ be used for packages that
explicitly specify it in `tool.uv.sources`. If `explicit` is not set, other packages may be resolved
from the index, if not found elsewhere.
### Git
To add a Git dependency source, prefix a Git-compatible URL to clone with `git+`.

View file

@ -2,10 +2,16 @@
uv accepts the following command-line arguments as environment variables:
- `UV_INDEX`: Equivalent to the `--index` command-line argument. If set, uv will use this
space-separated list of URLs as additional indexes when searching for packages.
- `UV_DEFAULT_INDEX`: Equivalent to the `--default-index` command-line argument. If set, uv will use
this URL as the default index when searching for packages.
- `UV_INDEX_URL`: Equivalent to the `--index-url` command-line argument. If set, uv will use this
URL as the base index for searching for packages.
URL as the default index when searching for packages. (Deprecated: use `UV_DEFAULT_INDEX`
instead.)
- `UV_EXTRA_INDEX_URL`: Equivalent to the `--extra-index-url` command-line argument. If set, uv will
use this space-separated list of URLs as additional indexes when searching for packages.
(Deprecated: use `UV_INDEX` instead.)
- `UV_FIND_LINKS`: Equivalent to the `--find-links` command-line argument. If set, uv will use this
comma-separated list of additional locations to search for packages.
- `UV_CACHE_DIR`: Equivalent to the `--cache-dir` command-line argument. If set, uv will use this

View file

@ -5,6 +5,7 @@ Read about the various ways to configure uv:
- [Using configuration files](./files.md)
- [Using environment variables](./environment.md)
- [Configuring authentication](./authentication.md)
- [Configuring package indexes](./indexes.md)
Or, jump to the [settings reference](../reference/settings.md) which enumerates the available
configuration options.

View file

@ -0,0 +1,114 @@
# Package indexes
By default, uv uses the [Python Package Index (PyPI)](https://pypi.org) for dependency resolution
and package installation. However, uv can be configured to use other package indexes, including
private indexes, via the `[[tool.uv.index]]` configuration option (and `--index`, the analogous
command-line option).
## Defining an index
To include an additional index when resolving dependencies, add a `[[tool.uv.index]]` entry to your
`pyproject.toml`:
```toml
[[tool.uv.index]]
# Optional name for the index.
name = "pytorch"
# Required URL for the index.
url = "https://download.pytorch.org/whl/cpu"
```
Indexes are prioritized in the order in which theyre defined, such that the first index listed in
the configuration file is the first index consulted when resolving dependencies, with indexes
provided via the command line taking precedence over those in the configuration file.
By default, uv includes the Python Package Index (PyPI) as the "default" index, i.e., the index used
when a package is not found on any other index. To exclude PyPI from the list of indexes, set
`default = true` on another index entry (or use the `--default-index` command-line option):
```toml
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cpu"
default = true
```
The default index is always treated as lowest priority, regardless of its position in the list of
indexes.
## Pinning a package to an index
A package can be pinned to a specific index by specifying the index in its `tool.uv.sources` entry.
For example, to ensure that `torch` is _always_ installed from the `pytorch` index, add the
following to your `pyproject.toml`:
```toml
[tool.uv.sources]
torch = { index = "pytorch" }
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cpu"
```
An index can be marked as `explicit = true` to prevent packages from being installed from that index
unless explicitly pinned to it. For example, to ensure that `torch` is installed from the `pytorch`
index, but all other packages are installed from PyPI, add the following to your `pyproject.toml`:
```toml
[tool.uv.sources]
torch = { index = "pytorch" }
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cpu"
explicit = true
```
Named indexes referenced via `tool.uv.sources` must be defined within the project's `pyproject.toml`
file; indexes provided via the command-line, environment variables, or user-level configuration will
not be recognized.
## Searching across multiple indexes
By default, uv will stop at the first index on which a given package is available, and limit
resolutions to those present on that first index (`first-match`).
For example, if an internal index is specified via `[[tool.uv.index]]`, uv's behavior is such that
if a package exists on that internal index, it will _always_ be installed from that internal index,
and never from PyPI. The intent is to prevent "dependency confusion" attacks, in which an attacker
publishes a malicious package on PyPI with the same name as an internal package, thus causing the
malicious package to be installed instead of the internal package. See, for example,
[the `torchtriton` attack](https://pytorch.org/blog/compromised-nightly-dependency/) from
December 2022.
Users can opt in to alternate index behaviors via the`--index-strategy` command-line option, or the
`UV_INDEX_STRATEGY` environment variable, which supports the following values:
- `first-match` (default): Search for each package across all indexes, limiting the candidate
versions to those present in the first index that contains the package.
- `unsafe-first-match`: Search for each package across all indexes, but prefer the first index with
a compatible version, even if newer versions are available on other indexes.
- `unsafe-best-match`: Search for each package across all indexes, and select the best version from
the combined set of candidate versions.
While `unsafe-best-match` is the closest to pip's behavior, it exposes users to the risk of
"dependency confusion" attacks.
## `--index-url` and `--extra-index-url`
In addition to the `[[tool.uv.index]]` configuration option, uv supports pip-style `--index-url` and
`--extra-index-url` command-line options for compatibility, where `--index-url` defines the default
index and `--extra-index-url` defines additional indexes.
These options can be used in conjunction with the `[[tool.uv.index]]` configuration option, and use
the same prioritization rules:
- The default index is always treated as lowest priority, whether defined via the legacy
`--index-url` argument, the recommended `--default-index` argument, or a `[[tool.uv.index]]` entry
with `default = true`.
- Indexes are consulted in the order in which theyre defined, either via the legacy
`--extra-index-url` argument, the recommended `--index` argument, or `[[tool.uv.index]]` entries.
In effect, `--index-url` and `--extra-index-url` can be thought of as unnamed `[[tool.uv.index]]`
entries, with `default = true` enabled for the former.

View file

@ -148,10 +148,9 @@ supports the following values:
While `unsafe-best-match` is the closest to `pip`'s behavior, it exposes users to the risk of
"dependency confusion" attacks.
In the future, uv will support pinning packages to dedicated indexes (see:
[#171](https://github.com/astral-sh/uv/issues/171)). Additionally,
[PEP 708](https://peps.python.org/pep-0708/) is a provisional standard that aims to address the
"dependency confusion" issue across package registries and installers.
uv also supports pinning packages to dedicated indexes (see:
[_Indexes_](../configuration/indexes.md#pinning-a-package-to-an-index)), such that a given package
is _always_ installed from a specific index.
## PEP 517 build isolation

View file

@ -116,6 +116,13 @@ uv run [OPTIONS] [COMMAND]
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--config-setting</code>, <code>-C</code> <i>config-setting</i></dt><dd><p>Settings to pass to the PEP 517 build backend, specified as <code>KEY=VALUE</code> pairs</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
@ -135,7 +142,7 @@ uv run [OPTIONS] [COMMAND]
<p>This option is only available when running in a project.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -155,9 +162,16 @@ uv run [OPTIONS] [COMMAND]
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -169,7 +183,7 @@ uv run [OPTIONS] [COMMAND]
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -672,6 +686,13 @@ uv add [OPTIONS] <PACKAGES|--requirements <REQUIREMENTS>>
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--config-setting</code>, <code>-C</code> <i>config-setting</i></dt><dd><p>Settings to pass to the PEP 517 build backend, specified as <code>KEY=VALUE</code> pairs</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--dev</code></dt><dd><p>Add the requirements as development dependencies</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
@ -693,7 +714,7 @@ uv add [OPTIONS] <PACKAGES|--requirements <REQUIREMENTS>>
<p>To add this dependency to an optional group in the current project instead, see <code>--optional</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -713,9 +734,16 @@ uv add [OPTIONS] <PACKAGES|--requirements <REQUIREMENTS>>
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -727,7 +755,7 @@ uv add [OPTIONS] <PACKAGES|--requirements <REQUIREMENTS>>
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -998,6 +1026,13 @@ uv remove [OPTIONS] <PACKAGES>...
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--config-setting</code>, <code>-C</code> <i>config-setting</i></dt><dd><p>Settings to pass to the PEP 517 build backend, specified as <code>KEY=VALUE</code> pairs</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--dev</code></dt><dd><p>Remove the packages from the development dependencies</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
@ -1011,7 +1046,7 @@ uv remove [OPTIONS] <PACKAGES>...
<p>Accepts both RFC 3339 timestamps (e.g., <code>2006-12-02T02:07:43Z</code>) and local dates in the same format (e.g., <code>2006-12-02</code>) in your system&#8217;s configured time zone.</p>
<p>May also be set with the <code>UV_EXCLUDE_NEWER</code> environment variable.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -1031,9 +1066,16 @@ uv remove [OPTIONS] <PACKAGES>...
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -1045,7 +1087,7 @@ uv remove [OPTIONS] <PACKAGES>...
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -1300,6 +1342,13 @@ uv sync [OPTIONS]
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--config-setting</code>, <code>-C</code> <i>config-setting</i></dt><dd><p>Settings to pass to the PEP 517 build backend, specified as <code>KEY=VALUE</code> pairs</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
@ -1317,7 +1366,7 @@ uv sync [OPTIONS]
<p>Note that all optional dependencies are always included in the resolution; this option only affects the selection of packages to install.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -1337,9 +1386,16 @@ uv sync [OPTIONS]
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -1351,7 +1407,7 @@ uv sync [OPTIONS]
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -1610,6 +1666,13 @@ uv lock [OPTIONS]
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--config-setting</code>, <code>-C</code> <i>config-setting</i></dt><dd><p>Settings to pass to the PEP 517 build backend, specified as <code>KEY=VALUE</code> pairs</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
@ -1621,7 +1684,7 @@ uv lock [OPTIONS]
<p>Accepts both RFC 3339 timestamps (e.g., <code>2006-12-02T02:07:43Z</code>) and local dates in the same format (e.g., <code>2006-12-02</code>) in your system&#8217;s configured time zone.</p>
<p>May also be set with the <code>UV_EXCLUDE_NEWER</code> environment variable.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -1639,9 +1702,16 @@ uv lock [OPTIONS]
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -1653,7 +1723,7 @@ uv lock [OPTIONS]
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -1886,6 +1956,13 @@ uv export [OPTIONS]
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--config-setting</code>, <code>-C</code> <i>config-setting</i></dt><dd><p>Settings to pass to the PEP 517 build backend, specified as <code>KEY=VALUE</code> pairs</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
@ -1901,7 +1978,7 @@ uv export [OPTIONS]
<p>May be provided more than once.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -1931,9 +2008,16 @@ uv export [OPTIONS]
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -1945,7 +2029,7 @@ uv export [OPTIONS]
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -2198,6 +2282,13 @@ uv tree [OPTIONS]
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--config-setting</code>, <code>-C</code> <i>config-setting</i></dt><dd><p>Settings to pass to the PEP 517 build backend, specified as <code>KEY=VALUE</code> pairs</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--depth</code>, <code>-d</code> <i>depth</i></dt><dd><p>Maximum display depth of the dependency tree</p>
<p>[default: 255]</p>
@ -2212,7 +2303,7 @@ uv tree [OPTIONS]
<p>Accepts both RFC 3339 timestamps (e.g., <code>2006-12-02T02:07:43Z</code>) and local dates in the same format (e.g., <code>2006-12-02</code>) in your system&#8217;s configured time zone.</p>
<p>May also be set with the <code>UV_EXCLUDE_NEWER</code> environment variable.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -2232,9 +2323,16 @@ uv tree [OPTIONS]
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -2246,7 +2344,7 @@ uv tree [OPTIONS]
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -2575,6 +2673,13 @@ uv tool run [OPTIONS] [COMMAND]
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--config-setting</code>, <code>-C</code> <i>config-setting</i></dt><dd><p>Settings to pass to the PEP 517 build backend, specified as <code>KEY=VALUE</code> pairs</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
@ -2586,7 +2691,7 @@ uv tool run [OPTIONS] [COMMAND]
<p>Accepts both RFC 3339 timestamps (e.g., <code>2006-12-02T02:07:43Z</code>) and local dates in the same format (e.g., <code>2006-12-02</code>) in your system&#8217;s configured time zone.</p>
<p>May also be set with the <code>UV_EXCLUDE_NEWER</code> environment variable.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -2606,9 +2711,16 @@ uv tool run [OPTIONS] [COMMAND]
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -2620,7 +2732,7 @@ uv tool run [OPTIONS] [COMMAND]
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -2864,6 +2976,13 @@ uv tool install [OPTIONS] <PACKAGE>
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--config-setting</code>, <code>-C</code> <i>config-setting</i></dt><dd><p>Settings to pass to the PEP 517 build backend, specified as <code>KEY=VALUE</code> pairs</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
@ -2875,7 +2994,7 @@ uv tool install [OPTIONS] <PACKAGE>
<p>Accepts both RFC 3339 timestamps (e.g., <code>2006-12-02T02:07:43Z</code>) and local dates in the same format (e.g., <code>2006-12-02</code>) in your system&#8217;s configured time zone.</p>
<p>May also be set with the <code>UV_EXCLUDE_NEWER</code> environment variable.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -2895,9 +3014,16 @@ uv tool install [OPTIONS] <PACKAGE>
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -2909,7 +3035,7 @@ uv tool install [OPTIONS] <PACKAGE>
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -3149,6 +3275,13 @@ uv tool upgrade [OPTIONS] <NAME>...
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--config-setting</code>, <code>-C</code> <i>config-setting</i></dt><dd><p>Settings to pass to the PEP 517 build backend, specified as <code>KEY=VALUE</code> pairs</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
@ -3160,7 +3293,7 @@ uv tool upgrade [OPTIONS] <NAME>...
<p>Accepts both RFC 3339 timestamps (e.g., <code>2006-12-02T02:07:43Z</code>) and local dates in the same format (e.g., <code>2006-12-02</code>) in your system&#8217;s configured time zone.</p>
<p>May also be set with the <code>UV_EXCLUDE_NEWER</code> environment variable.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -3176,9 +3309,16 @@ uv tool upgrade [OPTIONS] <NAME>...
<p>May also be set with the <code>UV_FIND_LINKS</code> environment variable.</p>
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -3190,7 +3330,7 @@ uv tool upgrade [OPTIONS] <NAME>...
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -4702,6 +4842,13 @@ uv pip compile [OPTIONS] <SRC_FILE>...
<p>Used to reflect custom build scripts and commands that wrap <code>uv pip compile</code>.</p>
<p>May also be set with the <code>UV_CUSTOM_COMPILE_COMMAND</code> environment variable.</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
@ -4725,7 +4872,7 @@ uv pip compile [OPTIONS] <SRC_FILE>...
<p>Only applies to <code>pyproject.toml</code>, <code>setup.py</code>, and <code>setup.cfg</code> sources.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -4743,9 +4890,16 @@ uv pip compile [OPTIONS] <SRC_FILE>...
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -4757,7 +4911,7 @@ uv pip compile [OPTIONS] <SRC_FILE>...
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -5101,6 +5255,13 @@ uv pip sync [OPTIONS] <SRC_FILE>...
<p>This is equivalent to pip&#8217;s <code>--constraint</code> option.</p>
<p>May also be set with the <code>UV_CONSTRAINT</code> environment variable.</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
@ -5114,7 +5275,7 @@ uv pip sync [OPTIONS] <SRC_FILE>...
<p>Accepts both RFC 3339 timestamps (e.g., <code>2006-12-02T02:07:43Z</code>) and local dates in the same format (e.g., <code>2006-12-02</code>) in your system&#8217;s configured time zone.</p>
<p>May also be set with the <code>UV_EXCLUDE_NEWER</code> environment variable.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -5130,9 +5291,16 @@ uv pip sync [OPTIONS] <SRC_FILE>...
<p>May also be set with the <code>UV_FIND_LINKS</code> environment variable.</p>
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -5144,7 +5312,7 @@ uv pip sync [OPTIONS] <SRC_FILE>...
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -5438,6 +5606,13 @@ uv pip install [OPTIONS] <PACKAGE|--requirement <REQUIREMENT>|--editable <EDITAB
<p>This is equivalent to pip&#8217;s <code>--constraint</code> option.</p>
<p>May also be set with the <code>UV_CONSTRAINT</code> environment variable.</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
@ -5461,7 +5636,7 @@ uv pip install [OPTIONS] <PACKAGE|--requirement <REQUIREMENT>|--editable <EDITAB
<p>Only applies to <code>pyproject.toml</code>, <code>setup.py</code>, and <code>setup.cfg</code> sources.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -5477,9 +5652,16 @@ uv pip install [OPTIONS] <PACKAGE|--requirement <REQUIREMENT>|--editable <EDITAB
<p>May also be set with the <code>UV_FIND_LINKS</code> environment variable.</p>
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -5491,7 +5673,7 @@ uv pip install [OPTIONS] <PACKAGE|--requirement <REQUIREMENT>|--editable <EDITAB
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -6620,6 +6802,13 @@ uv venv [OPTIONS] [PATH]
<p>While uv configuration can be included in a <code>pyproject.toml</code> file, it is not allowed in this context.</p>
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
@ -6631,7 +6820,7 @@ uv venv [OPTIONS] [PATH]
<p>Accepts both RFC 3339 timestamps (e.g., <code>2006-12-02T02:07:43Z</code>) and local dates in the same format (e.g., <code>2006-12-02</code>) in your system&#8217;s configured time zone.</p>
<p>May also be set with the <code>UV_EXCLUDE_NEWER</code> environment variable.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -6647,9 +6836,16 @@ uv venv [OPTIONS] [PATH]
<p>May also be set with the <code>UV_FIND_LINKS</code> environment variable.</p>
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -6661,7 +6857,7 @@ uv venv [OPTIONS] [PATH]
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -6868,6 +7064,13 @@ uv build [OPTIONS] [SRC]
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p>
</dd><dt><code>--config-setting</code>, <code>-C</code> <i>config-setting</i></dt><dd><p>Settings to pass to the PEP 517 build backend, specified as <code>KEY=VALUE</code> pairs</p>
</dd><dt><code>--default-index</code> <i>default-index</i></dt><dd><p>The URL of the default package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>The index given by this flag is given lower priority than all other indexes specified via the <code>--index</code> flag.</p>
<p>May also be set with the <code>UV_DEFAULT_INDEX</code> environment variable.</p>
</dd><dt><code>--directory</code> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
@ -6879,7 +7082,7 @@ uv build [OPTIONS] [SRC]
<p>Accepts both RFC 3339 timestamps (e.g., <code>2006-12-02T02:07:43Z</code>) and local dates in the same format (e.g., <code>2006-12-02</code>) in your system&#8217;s configured time zone.</p>
<p>May also be set with the <code>UV_EXCLUDE_NEWER</code> environment variable.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
</dd><dt><code>--extra-index-url</code> <i>extra-index-url</i></dt><dd><p>(Deprecated: use <code>--index</code> instead) Extra URLs of package indexes to use, in addition to <code>--index-url</code>.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
@ -6895,9 +7098,16 @@ uv build [OPTIONS] [SRC]
<p>May also be set with the <code>UV_FIND_LINKS</code> environment variable.</p>
</dd><dt><code>--help</code>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt><code>--index</code> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>
<p>All indexes provided via this flag take priority over the index specified by <code>--default-index</code> (which defaults to PyPI). When multiple <code>--index</code> flags are provided, earlier values take priority.</p>
<p>May also be set with the <code>UV_INDEX</code> environment variable.</p>
</dd><dt><code>--index-strategy</code> <i>index-strategy</i></dt><dd><p>The strategy to use when resolving against multiple index URLs.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attack can upload a malicious package under the same name to a secondary.</p>
<p>By default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (<code>first-match</code>). This prevents &quot;dependency confusion&quot; attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.</p>
<p>May also be set with the <code>UV_INDEX_STRATEGY</code> environment variable.</p>
<p>Possible values:</p>
@ -6909,7 +7119,7 @@ uv build [OPTIONS] [SRC]
<li><code>unsafe-best-match</code>: Search for every package name across all indexes, preferring the &quot;best&quot; version found. If a package version is in multiple indexes, only look at the entry for the first index</li>
</ul>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
</dd><dt><code>--index-url</code>, <code>-i</code> <i>index-url</i></dt><dd><p>(Deprecated: use <code>--default-index</code> instead) The URL of the Python package index (by default: &lt;https://pypi.org/simple&gt;).</p>
<p>Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.</p>

View file

@ -73,6 +73,51 @@ environments = ["sys_platform == 'darwin'"]
---
### [`index`](#index) {: #index }
The indexes to use when resolving dependencies.
Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
(the simple repository API), or a local directory laid out in the same format.
Indexes are considered in the order in which they're defined, such that the first-defined
index has the highest priority. Further, the indexes provided by this setting are given
higher priority than any indexes specified via [`index_url`](#index-url) or
[`extra_index_url`](#extra-index-url). uv will only consider the first index that contains
a given package, unless an alternative [index strategy](#index-strategy) is specified.
If an index is marked as `explicit = true`, it will be used exclusively for those
dependencies that select it explicitly via `[tool.uv.sources]`, as in:
```toml
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
explicit = true
[tool.uv.sources]
torch = { index = "pytorch" }
```
If an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is
given the lowest priority when resolving packages. Additionally, marking an index as default will disable the
PyPI default index.
**Default value**: `"[]"`
**Type**: `dict`
**Example usage**:
```toml title="pyproject.toml"
[tool.uv]
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
```
---
### [`managed`](#managed) {: #managed }
Whether the project is managed by uv. If `false`, uv will ignore the project when
@ -533,11 +578,14 @@ Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep
(the simple repository API), or a local directory laid out in the same format.
All indexes provided via this flag take priority over the index specified by
[`index_url`](#index-url). When multiple indexes are provided, earlier values take priority.
[`index_url`](#index-url) or [`index`](#index) with `default = true`. When multiple indexes
are provided, earlier values take priority.
To control uv's resolution strategy when multiple indexes are present, see
[`index_strategy`](#index-strategy).
(Deprecated: use `index` instead.)
**Default value**: `[]`
**Type**: `list[str]`
@ -591,14 +639,69 @@ formats described above.
---
### [`index`](#index) {: #index }
The package indexes to use when resolving dependencies.
Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
(the simple repository API), or a local directory laid out in the same format.
Indexes are considered in the order in which they're defined, such that the first-defined
index has the highest priority. Further, the indexes provided by this setting are given
higher priority than any indexes specified via [`index_url`](#index-url) or
[`extra_index_url`](#extra-index-url). uv will only consider the first index that contains
a given package, unless an alternative [index strategy](#index-strategy) is specified.
If an index is marked as `explicit = true`, it will be used exclusively for those
dependencies that select it explicitly via `[tool.uv.sources]`, as in:
```toml
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
explicit = true
[tool.uv.sources]
torch = { index = "pytorch" }
```
If an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is
given the lowest priority when resolving packages. Additionally, marking an index as default will disable the
PyPI default index.
**Default value**: `"[]"`
**Type**: `dict`
**Example usage**:
=== "pyproject.toml"
```toml
[tool.uv]
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
```
=== "uv.toml"
```toml
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
```
---
### [`index-strategy`](#index-strategy) {: #index-strategy }
The strategy to use when resolving against multiple index URLs.
By default, uv will stop at the first index on which a given package is available, and
limit resolutions to those present on that first index (`first-match`). This prevents
"dependency confusion" attacks, whereby an attack can upload a malicious package under the
same name to a secondary.
"dependency confusion" attacks, whereby an attacker can upload a malicious package under the
same name to an alternate index.
**Default value**: `"first-index"`
@ -633,7 +736,9 @@ Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep
(the simple repository API), or a local directory laid out in the same format.
The index provided by this setting is given lower priority than any indexes specified via
[`extra_index_url`](#extra-index-url).
[`extra_index_url`](#extra-index-url) or [`index`](#index).
(Deprecated: use `index` instead.)
**Default value**: `"https://pypi.org/simple"`
@ -1911,8 +2016,8 @@ The strategy to use when resolving against multiple index URLs.
By default, uv will stop at the first index on which a given package is available, and
limit resolutions to those present on that first index (`first-match`). This prevents
"dependency confusion" attacks, whereby an attack can upload a malicious package under the
same name to a secondary.
"dependency confusion" attacks, whereby an attacker can upload a malicious package under the
same name to an alternate index.
**Default value**: `"first-index"`

View file

@ -106,6 +106,7 @@ nav:
- Configuration files: configuration/files.md
- Environment variables: configuration/environment.md
- Authentication: configuration/authentication.md
- Package indexes: configuration/indexes.md
- Integration guides:
- guides/integration/index.md
- Docker: guides/integration/docker.md

51
uv.schema.json generated
View file

@ -128,7 +128,7 @@
]
},
"extra-index-url": {
"description": "Extra URLs of package indexes to use, in addition to `--index-url`.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) (the simple repository API), or a local directory laid out in the same format.\n\nAll indexes provided via this flag take priority over the index specified by [`index_url`](#index-url). When multiple indexes are provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see [`index_strategy`](#index-strategy).",
"description": "Extra URLs of package indexes to use, in addition to `--index-url`.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) (the simple repository API), or a local directory laid out in the same format.\n\nAll indexes provided via this flag take priority over the index specified by [`index_url`](#index-url) or [`index`](#index) with `default = true`. When multiple indexes are provided, earlier values take priority.\n\nTo control uv's resolution strategy when multiple indexes are present, see [`index_strategy`](#index-strategy).\n\n(Deprecated: use `index` instead.)",
"type": [
"array",
"null"
@ -147,8 +147,18 @@
"$ref": "#/definitions/FlatIndexLocation"
}
},
"index": {
"description": "The indexes to use when resolving dependencies.\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) (the simple repository API), or a local directory laid out in the same format.\n\nIndexes are considered in the order in which they're defined, such that the first-defined index has the highest priority. Further, the indexes provided by this setting are given higher priority than any indexes specified via [`index_url`](#index-url) or [`extra_index_url`](#extra-index-url). uv will only consider the first index that contains a given package, unless an alternative [index strategy](#index-strategy) is specified.\n\nIf an index is marked as `explicit = true`, it will be used exclusively for those dependencies that select it explicitly via `[tool.uv.sources]`, as in:\n\n```toml [[tool.uv.index]] name = \"pytorch\" url = \"https://download.pytorch.org/whl/cu121\" explicit = true\n\n[tool.uv.sources] torch = { index = \"pytorch\" } ```\n\nIf an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is given the lowest priority when resolving packages. Additionally, marking an index as default will disable the PyPI default index.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/Index"
}
},
"index-strategy": {
"description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (`first-match`). This prevents \"dependency confusion\" attacks, whereby an attack can upload a malicious package under the same name to a secondary.",
"description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (`first-match`). This prevents \"dependency confusion\" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.",
"anyOf": [
{
"$ref": "#/definitions/IndexStrategy"
@ -159,7 +169,7 @@
]
},
"index-url": {
"description": "The URL of the Python package index (by default: <https://pypi.org/simple>).\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) (the simple repository API), or a local directory laid out in the same format.\n\nThe index provided by this setting is given lower priority than any indexes specified via [`extra_index_url`](#extra-index-url).",
"description": "The URL of the Python package index (by default: <https://pypi.org/simple>).\n\nAccepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) (the simple repository API), or a local directory laid out in the same format.\n\nThe index provided by this setting is given lower priority than any indexes specified via [`extra_index_url`](#extra-index-url) or [`index`](#index).\n\n(Deprecated: use `index` instead.)",
"anyOf": [
{
"$ref": "#/definitions/IndexUrl"
@ -545,6 +555,39 @@
"description": "The path to a directory of distributions, or a URL to an HTML file with a flat listing of distributions.",
"type": "string"
},
"Index": {
"type": "object",
"required": [
"url"
],
"properties": {
"default": {
"description": "Mark the index as the default index.\n\nBy default, uv uses PyPI as the default index, such that even if additional indexes are defined via `[[tool.uv.index]]`, PyPI will still be used as a fallback for packages that aren't found elsewhere. To disable the PyPI default, set `default = true` on at least one other index.\n\nMarking an index as default will move it to the front of the list of indexes, such that it is given the highest priority when resolving packages.",
"default": false,
"type": "boolean"
},
"explicit": {
"description": "Mark the index as explicit.\n\nExplicit indexes will _only_ be used when explicitly requested via a `[tool.uv.sources]` definition, as in:\n\n```toml [[tool.uv.index]] name = \"pytorch\" url = \"https://download.pytorch.org/whl/cu121\" explicit = true\n\n[tool.uv.sources] torch = { index = \"pytorch\" } ```",
"default": false,
"type": "boolean"
},
"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\" } ```",
"type": [
"string",
"null"
]
},
"url": {
"description": "The URL of the index.\n\nExpects to receive a URL (e.g., `https://pypi.org/simple`) or a local path.",
"allOf": [
{
"$ref": "#/definitions/IndexUrl"
}
]
}
}
},
"IndexStrategy": {
"oneOf": [
{
@ -803,7 +846,7 @@
]
},
"index-strategy": {
"description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (`first-match`). This prevents \"dependency confusion\" attacks, whereby an attack can upload a malicious package under the same name to a secondary.",
"description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (`first-match`). This prevents \"dependency confusion\" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.",
"anyOf": [
{
"$ref": "#/definitions/IndexStrategy"