From 9ccacc4bd5d0f5b464cab54cbbd59139740afaf5 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 8 Nov 2024 09:05:59 -0500 Subject: [PATCH] Respect --index-url in uv pip list --- 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 74a7f967db74..ecef77a8e4b1 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 771585990835..a431e8643ec2 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 4a3ff5e3d737..ee3e8aba8313 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 6b1c1cd555b1..5025fda295ed 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 254c5b064fe9..c7585ae21435 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.