From 04c445a3dbdefe530ced0228c424b3a6d9827edc Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 8 Nov 2024 09:52:32 -0500 Subject: [PATCH] Respect `--index-url` in `uv pip list` (#8942) ## Summary As an oversight, these arguments weren't being respected from the CLI or elsewhere -- we always hit PyPI, ignored `--exclude-newer`, etc. It has to do with the way that the `PipOptions` are setup -- there's a global struct that we pass around everywhere and fill in with defaults, so there's no type safety to guarantee that we provide whatever it is we need to use in the command. The newer APIs are much better about this. Closes #8927. --- crates/uv-cli/src/lib.rs | 46 ++++++++++++++++++++++ crates/uv-cli/src/options.rs | 20 +++++++++- crates/uv/src/settings.rs | 3 +- crates/uv/tests/it/pip_list.rs | 54 +++++++++++++++++++++++--- docs/reference/cli.md | 70 ++++++++++++++++++++++++++++++++++ 5 files changed, 185 insertions(+), 8 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 74a7f967d..ecef77a8e 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1926,6 +1926,9 @@ pub struct PipListArgs { #[arg(long, overrides_with("strict"), hide = true)] pub no_strict: bool, + #[command(flatten)] + pub fetch: FetchArgs, + /// The Python interpreter for which packages should be listed. /// /// By default, uv lists packages in a virtual environment but will show @@ -4698,6 +4701,49 @@ pub struct ResolverInstallerArgs { pub no_sources: bool, } +/// Arguments that are used by commands that need to fetch from the Simple API. +#[derive(Args)] +#[allow(clippy::struct_excessive_bools)] +pub struct FetchArgs { + #[command(flatten)] + pub index_args: IndexArgs, + + /// 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 attacker can upload a malicious package under the + /// same name to an alternate index. + #[arg( + long, + value_enum, + env = EnvVars::UV_INDEX_STRATEGY, + help_heading = "Index options" + )] + pub index_strategy: Option, + + /// Attempt to use `keyring` for authentication for index URLs. + /// + /// At present, only `--keyring-provider subprocess` is supported, which configures uv to + /// use the `keyring` CLI to handle authentication. + /// + /// Defaults to `disabled`. + #[arg( + long, + value_enum, + env = EnvVars::UV_KEYRING_PROVIDER, + help_heading = "Index options" + )] + pub keyring_provider: Option, + + /// Limit candidate packages to those that were uploaded prior to the given date. + /// + /// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same + /// format (e.g., `2006-12-02`) in your system's configured time zone. + #[arg(long, env = EnvVars::UV_EXCLUDE_NEWER, help_heading = "Resolver options")] + pub exclude_newer: Option, +} + #[derive(Args)] pub struct DisplayTreeArgs { /// Maximum display depth of the dependency tree diff --git a/crates/uv-cli/src/options.rs b/crates/uv-cli/src/options.rs index 771585990..a431e8643 100644 --- a/crates/uv-cli/src/options.rs +++ b/crates/uv-cli/src/options.rs @@ -5,7 +5,7 @@ use uv_resolver::PrereleaseMode; use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions}; use crate::{ - BuildOptionsArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs, + BuildOptionsArgs, FetchArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs, ResolverInstallerArgs, }; @@ -163,6 +163,24 @@ impl From for PipOptions { } } +impl From for PipOptions { + fn from(args: FetchArgs) -> Self { + let FetchArgs { + index_args, + index_strategy, + keyring_provider, + exclude_newer, + } = args; + + Self { + index_strategy, + keyring_provider, + exclude_newer, + ..PipOptions::from(index_args) + } + } +} + impl From for PipOptions { fn from(args: IndexArgs) -> Self { let IndexArgs { diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 4a3ff5e3d..ee3e8aba8 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1693,6 +1693,7 @@ impl PipListSettings { no_outdated, strict, no_strict, + fetch, python, system, no_system, @@ -1709,7 +1710,7 @@ impl PipListSettings { python: python.and_then(Maybe::into_option), system: flag(system, no_system), strict: flag(strict, no_strict), - ..PipOptions::default() + ..PipOptions::from(fetch) }, filesystem, ), diff --git a/crates/uv/tests/it/pip_list.rs b/crates/uv/tests/it/pip_list.rs index 6b1c1cd55..5025fda29 100644 --- a/crates/uv/tests/it/pip_list.rs +++ b/crates/uv/tests/it/pip_list.rs @@ -124,10 +124,9 @@ fn list_outdated_columns() -> Result<()> { success: true exit_code: 0 ----- stdout ----- - Package Version Latest Type - ------- ------- ----------- ----- - anyio 3.0.0 4.6.2.post1 wheel - idna 3.6 3.10 wheel + Package Version Latest Type + ------- ------- ------ ----- + anyio 3.0.0 4.3.0 wheel ----- stderr ----- "### @@ -165,7 +164,7 @@ fn list_outdated_json() -> Result<()> { success: true exit_code: 0 ----- stdout ----- - [{"name":"anyio","version":"3.0.0","latest_version":"4.6.2.post1","latest_filetype":"wheel"},{"name":"idna","version":"3.6","latest_version":"3.10","latest_filetype":"wheel"}] + [{"name":"anyio","version":"3.0.0","latest_version":"4.3.0","latest_filetype":"wheel"}] ----- stderr ----- "### @@ -231,6 +230,49 @@ fn list_outdated_git() -> Result<()> { Ok(()) } +#[test] +fn list_outdated_index() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_txt = context.temp_dir.child("requirements.txt"); + requirements_txt.write_str("anyio==3.0.0")?; + + uv_snapshot!(context.pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--strict"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==3.0.0 + + idna==3.6 + + sniffio==1.3.1 + "### + ); + + uv_snapshot!(context.pip_list() + .arg("--outdated") + .arg("--index-url") + .arg("https://test.pypi.org/simple"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Package Version Latest Type + ------- ------- ------ ----- + anyio 3.0.0 3.5.0 wheel + + ----- stderr ----- + "### + ); + + Ok(()) +} + #[test] fn list_editable() { let context = TestContext::new("3.12"); @@ -343,7 +385,7 @@ fn list_editable_only() { ----- stderr ----- error: the argument '--editable' cannot be used with '--exclude-editable' - Usage: uv pip list --cache-dir [CACHE_DIR] --editable + Usage: uv pip list --cache-dir [CACHE_DIR] --editable --exclude-newer For more information, try '--help'. "### diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 254c5b064..c7585ae21 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -6634,6 +6634,13 @@ uv pip list [OPTIONS]

While uv configuration can be included in a pyproject.toml file, it is not allowed in this context.

May also be set with the UV_CONFIG_FILE environment variable.

+
--default-index default-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.

+ +

May also be set with the UV_DEFAULT_INDEX environment variable.

--directory directory

Change to the given directory prior to running the command.

Relative paths are resolved with the given directory as the base.

@@ -6646,6 +6653,25 @@ uv pip list [OPTIONS]
--exclude-editable

Exclude any editable packages from output

+
--exclude-newer exclude-newer

Limit candidate packages to those that were uploaded prior to the given date.

+ +

Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system’s configured time zone.

+ +

May also be set with the UV_EXCLUDE_NEWER environment variable.

+
--extra-index-url extra-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.

+ +

All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.

+ +

May also be set with the UV_EXTRA_INDEX_URL environment variable.

+
--find-links, -f find-links

Locations to search for candidate distributions, in addition to those found in the registry indexes.

+ +

If a path, the target must be a directory that contains packages as wheel files (.whl) or source distributions (e.g., .tar.gz or .zip) at the top level.

+ +

If a URL, the page must contain a flat list of links to package files adhering to the formats described above.

+ +

May also be set with the UV_FIND_LINKS environment variable.

--format format

Select the output format between: columns (default), freeze, or json

[default: columns]

@@ -6660,6 +6686,48 @@ uv pip list [OPTIONS]
--help, -h

Display the concise help for this command

+
--index index

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.

+ +

May also be set with the UV_INDEX environment variable.

+
--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 attacker can upload a malicious package under the same name to an alternate index.

+ +

May also be set with the UV_INDEX_STRATEGY environment variable.

+

Possible values:

+ +
    +
  • first-index: Only use results from the first index that returns a match for a given package name
  • + +
  • unsafe-first-match: Search for every package name across all indexes, exhausting the versions from the first index before moving on to the next
  • + +
  • unsafe-best-match: Search for every package name across all indexes, preferring the "best" version found. If a package version is in multiple indexes, only look at the entry for the first index
  • +
+
--index-url, -i index-url

(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.

+ +

The index given by this flag is given lower priority than all other indexes specified via the --extra-index-url flag.

+ +

May also be set with the UV_INDEX_URL environment variable.

+
--keyring-provider keyring-provider

Attempt to use keyring for authentication for index URLs.

+ +

At present, only --keyring-provider subprocess is supported, which configures uv to use the keyring CLI to handle authentication.

+ +

Defaults to disabled.

+ +

May also be set with the UV_KEYRING_PROVIDER environment variable.

+

Possible values:

+ +
    +
  • disabled: Do not use keyring for credential lookup
  • + +
  • subprocess: Use the keyring command for credential lookup
  • +
--native-tls

Whether to load TLS certificates from the platform’s native certificate store.

By default, uv loads certificates from the bundled webpki-roots crate. The webpki-roots are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).

@@ -6675,6 +6743,8 @@ uv pip list [OPTIONS]

Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

May also be set with the UV_NO_CONFIG environment variable.

+
--no-index

Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those provided via --find-links

+
--no-progress

Hide all progress outputs.

For example, spinners or progress bars.