Skip to content

Commit

Permalink
Respect --index-url in uv pip list (#8942)
Browse files Browse the repository at this point in the history
## 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.
  • Loading branch information
charliermarsh authored Nov 8, 2024
1 parent 0b4e5cf commit 04c445a
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 8 deletions.
46 changes: 46 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<IndexStrategy>,

/// 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<KeyringProviderType>,

/// 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<ExcludeNewer>,
}

#[derive(Args)]
pub struct DisplayTreeArgs {
/// Maximum display depth of the dependency tree
Expand Down
20 changes: 19 additions & 1 deletion crates/uv-cli/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -163,6 +163,24 @@ impl From<ResolverInstallerArgs> for PipOptions {
}
}

impl From<FetchArgs> 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<IndexArgs> for PipOptions {
fn from(args: IndexArgs) -> Self {
let IndexArgs {
Expand Down
3 changes: 2 additions & 1 deletion crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1693,6 +1693,7 @@ impl PipListSettings {
no_outdated,
strict,
no_strict,
fetch,
python,
system,
no_system,
Expand All @@ -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,
),
Expand Down
54 changes: 48 additions & 6 deletions crates/uv/tests/it/pip_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 -----
"###
Expand Down Expand Up @@ -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 -----
"###
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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 <EXCLUDE_NEWER>
For more information, try '--help'.
"###
Expand Down
70 changes: 70 additions & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -6634,6 +6634,13 @@ uv pip list [OPTIONS]
<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>
Expand All @@ -6646,6 +6653,25 @@ uv pip list [OPTIONS]

</dd><dt><code>--exclude-editable</code></dt><dd><p>Exclude any editable packages from output</p>

</dd><dt><code>--exclude-newer</code> <i>exclude-newer</i></dt><dd><p>Limit candidate packages to those that were uploaded prior to the given date.</p>

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

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

<p>May also be set with the <code>UV_EXTRA_INDEX_URL</code> environment variable.</p>
</dd><dt><code>--find-links</code>, <code>-f</code> <i>find-links</i></dt><dd><p>Locations to search for candidate distributions, in addition to those found in the registry indexes.</p>

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

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

<p>May also be set with the <code>UV_FIND_LINKS</code> environment variable.</p>
</dd><dt><code>--format</code> <i>format</i></dt><dd><p>Select the output format between: <code>columns</code> (default), <code>freeze</code>, or <code>json</code></p>

<p>[default: columns]</p>
Expand All @@ -6660,6 +6686,48 @@ uv pip list [OPTIONS]
</ul>
</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 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>

<ul>
<li><code>first-index</code>: Only use results from the first index that returns a match for a given package name</li>

<li><code>unsafe-first-match</code>: Search for every package name across all indexes, exhausting the versions from the first index before moving on to the next</li>

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

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

<p>May also be set with the <code>UV_INDEX_URL</code> environment variable.</p>
</dd><dt><code>--keyring-provider</code> <i>keyring-provider</i></dt><dd><p>Attempt to use <code>keyring</code> for authentication for index URLs.</p>

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

<p>Defaults to <code>disabled</code>.</p>

<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p>
<p>Possible values:</p>

<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>

<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul>
</dd><dt><code>--native-tls</code></dt><dd><p>Whether to load TLS certificates from the platform&#8217;s native certificate store.</p>

<p>By default, uv loads certificates from the bundled <code>webpki-roots</code> crate. The <code>webpki-roots</code> are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).</p>
Expand All @@ -6675,6 +6743,8 @@ uv pip list [OPTIONS]
<p>Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.</p>

<p>May also be set with the <code>UV_NO_CONFIG</code> environment variable.</p>
</dd><dt><code>--no-index</code></dt><dd><p>Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those provided via <code>--find-links</code></p>

</dd><dt><code>--no-progress</code></dt><dd><p>Hide all progress outputs.</p>

<p>For example, spinners or progress bars.</p>
Expand Down

0 comments on commit 04c445a

Please sign in to comment.