Add `--show-urls` and `--only-downloads` to `uv python list` (#8062)

These are useful for creating a mirror of the Python downloads for a
given uv version, e.g.:

```
❯ cargo run -q -- python list --show-urls --only-downloads
cpython-3.13.0-macos-aarch64-none     https://github.com/indygreg/python-build-standalone/releases/download/20241008/cpython-3.13.0%2B20241008-aarch64-apple-darwin-install_only_stripped.tar.gz
cpython-3.12.7-macos-aarch64-none     https://github.com/indygreg/python-build-standalone/releases/download/20241008/cpython-3.12.7%2B20241008-aarch64-apple-darwin-install_only_stripped.tar.gz
cpython-3.11.10-macos-aarch64-none    https://github.com/indygreg/python-build-standalone/releases/download/20241008/cpython-3.11.10%2B20241008-aarch64-apple-darwin-install_only_stripped.tar.gz
cpython-3.10.15-macos-aarch64-none    https://github.com/indygreg/python-build-standalone/releases/download/20241008/cpython-3.10.15%2B20241008-aarch64-apple-darwin-install_only_stripped.tar.gz
cpython-3.9.20-macos-aarch64-none     https://github.com/indygreg/python-build-standalone/releases/download/20241008/cpython-3.9.20%2B20241008-aarch64-apple-darwin-install_only_stripped.tar.gz
cpython-3.8.20-macos-aarch64-none     https://github.com/indygreg/python-build-standalone/releases/download/20241002/cpython-3.8.20%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz
pypy-3.10.14-macos-aarch64-none       https://downloads.python.org/pypy/pypy3.10-v7.3.17-macos_arm64.tar.bz2
pypy-3.9.19-macos-aarch64-none        https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2
pypy-3.8.16-macos-aarch64-none        https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2
```
This commit is contained in:
Zanie Blue 2024-12-10 12:52:40 -06:00 committed by GitHub
parent 3ee2b10738
commit 624e79a8a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 111 additions and 55 deletions

View File

@ -4225,8 +4225,20 @@ pub struct PythonListArgs {
/// Only show installed Python versions, exclude available downloads. /// Only show installed Python versions, exclude available downloads.
/// ///
/// By default, available downloads for the current platform are shown. /// By default, available downloads for the current platform are shown.
#[arg(long)] #[arg(long, conflicts_with("only_downloads"))]
pub only_installed: bool, pub only_installed: bool,
/// Only show Python downloads, exclude installed distributions.
///
/// By default, available downloads for the current platform are shown.
#[arg(long, conflicts_with("only_installed"))]
pub only_downloads: bool,
/// Show the URLs of available Python downloads.
///
/// By default, these display as `<download available>`.
#[arg(long)]
pub show_urls: bool,
} }
#[derive(Args)] #[derive(Args)]

View File

@ -2,6 +2,7 @@ use std::collections::BTreeSet;
use std::fmt::Write; use std::fmt::Write;
use anyhow::Result; use anyhow::Result;
use itertools::Either;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use uv_cache::Cache; use uv_cache::Cache;
@ -29,6 +30,7 @@ pub(crate) async fn list(
kinds: PythonListKinds, kinds: PythonListKinds,
all_versions: bool, all_versions: bool,
all_platforms: bool, all_platforms: bool,
show_urls: bool,
python_preference: PythonPreference, python_preference: PythonPreference,
python_downloads: PythonDownloads, python_downloads: PythonDownloads,
cache: &Cache, cache: &Cache,
@ -38,6 +40,11 @@ pub(crate) async fn list(
if python_preference != PythonPreference::OnlySystem { if python_preference != PythonPreference::OnlySystem {
let download_request = match kinds { let download_request = match kinds {
PythonListKinds::Installed => None, PythonListKinds::Installed => None,
PythonListKinds::Downloads => Some(if all_platforms {
PythonDownloadRequest::default()
} else {
PythonDownloadRequest::from_env()?
}),
PythonListKinds::Default => { PythonListKinds::Default => {
if python_downloads.is_automatic() { if python_downloads.is_automatic() {
Some(if all_platforms { Some(if all_platforms {
@ -61,11 +68,18 @@ pub(crate) async fn list(
.flatten(); .flatten();
for download in downloads { for download in downloads {
output.insert((download.key().clone(), Kind::Download, None)); output.insert((
download.key().clone(),
Kind::Download,
Either::Right(download.url()),
));
} }
}; };
let installed = find_python_installations( let installed =
match kinds {
PythonListKinds::Installed | PythonListKinds::Default => {
Some(find_python_installations(
&PythonRequest::Any, &PythonRequest::Any,
EnvironmentPreference::OnlySystem, EnvironmentPreference::OnlySystem,
python_preference, python_preference,
@ -81,8 +95,12 @@ pub(crate) async fn list(
.collect::<Result<Vec<Result<PythonInstallation, PythonNotFound>>, DiscoveryError>>()? .collect::<Result<Vec<Result<PythonInstallation, PythonNotFound>>, DiscoveryError>>()?
.into_iter() .into_iter()
// Drop any "missing" installations // Drop any "missing" installations
.filter_map(Result::ok); .filter_map(Result::ok))
}
PythonListKinds::Downloads => None,
};
if let Some(installed) = installed {
for installation in installed { for installation in installed {
let kind = if matches!(installation.source(), PythonSource::Managed) { let kind = if matches!(installation.source(), PythonSource::Managed) {
Kind::Managed Kind::Managed
@ -92,17 +110,18 @@ pub(crate) async fn list(
output.insert(( output.insert((
installation.key(), installation.key(),
kind, kind,
Some(installation.interpreter().sys_executable().to_path_buf()), Either::Left(installation.interpreter().sys_executable().to_path_buf()),
)); ));
} }
}
let mut seen_minor = FxHashSet::default(); let mut seen_minor = FxHashSet::default();
let mut seen_patch = FxHashSet::default(); let mut seen_patch = FxHashSet::default();
let mut seen_paths = FxHashSet::default(); let mut seen_paths = FxHashSet::default();
let mut include = Vec::new(); let mut include = Vec::new();
for (key, kind, path) in output.iter().rev() { for (key, kind, uri) in output.iter().rev() {
// Do not show the same path more than once // Do not show the same path more than once
if let Some(path) = path { if let Either::Left(path) = uri {
if !seen_paths.insert(path) { if !seen_paths.insert(path) {
continue; continue;
} }
@ -142,7 +161,7 @@ pub(crate) async fn list(
} }
} }
} }
include.push((key, path)); include.push((key, uri));
} }
// Compute the width of the first column. // Compute the width of the first column.
@ -150,9 +169,10 @@ pub(crate) async fn list(
.iter() .iter()
.fold(0usize, |acc, (key, _)| acc.max(key.to_string().len())); .fold(0usize, |acc, (key, _)| acc.max(key.to_string().len()));
for (key, path) in include { for (key, uri) in include {
let key = key.to_string(); let key = key.to_string();
if let Some(path) = path { match uri {
Either::Left(path) => {
let is_symlink = fs_err::symlink_metadata(path)?.is_symlink(); let is_symlink = fs_err::symlink_metadata(path)?.is_symlink();
if is_symlink { if is_symlink {
writeln!( writeln!(
@ -168,6 +188,10 @@ pub(crate) async fn list(
path.user_display().cyan() path.user_display().cyan()
)?; )?;
} }
}
Either::Right(url) => {
if show_urls {
writeln!(printer.stdout(), "{key:width$} {}", url.dimmed())?;
} else { } else {
writeln!( writeln!(
printer.stdout(), printer.stdout(),
@ -176,6 +200,8 @@ pub(crate) async fn list(
)?; )?;
} }
} }
}
}
Ok(ExitStatus::Success) Ok(ExitStatus::Success)
} }

View File

@ -1088,6 +1088,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.kinds, args.kinds,
args.all_versions, args.all_versions,
args.all_platforms, args.all_platforms,
args.show_urls,
globals.python_preference, globals.python_preference,
globals.python_downloads, globals.python_downloads,
&cache, &cache,

View File

@ -703,6 +703,9 @@ impl ToolDirSettings {
pub(crate) enum PythonListKinds { pub(crate) enum PythonListKinds {
#[default] #[default]
Default, Default,
/// Only list version downloads.
Downloads,
/// Only list installed versions.
Installed, Installed,
} }
@ -713,6 +716,7 @@ pub(crate) struct PythonListSettings {
pub(crate) kinds: PythonListKinds, pub(crate) kinds: PythonListKinds,
pub(crate) all_platforms: bool, pub(crate) all_platforms: bool,
pub(crate) all_versions: bool, pub(crate) all_versions: bool,
pub(crate) show_urls: bool,
} }
impl PythonListSettings { impl PythonListSettings {
@ -723,10 +727,14 @@ impl PythonListSettings {
all_versions, all_versions,
all_platforms, all_platforms,
only_installed, only_installed,
only_downloads,
show_urls,
} = args; } = args;
let kinds = if only_installed { let kinds = if only_installed {
PythonListKinds::Installed PythonListKinds::Installed
} else if only_downloads {
PythonListKinds::Downloads
} else { } else {
PythonListKinds::default() PythonListKinds::default()
}; };
@ -735,6 +743,7 @@ impl PythonListSettings {
kinds, kinds,
all_platforms, all_platforms,
all_versions, all_versions,
show_urls,
} }
} }
} }

View File

@ -4426,6 +4426,10 @@ uv python list [OPTIONS]
<p>When disabled, uv will only use locally cached data and locally available files.</p> <p>When disabled, uv will only use locally cached data and locally available files.</p>
</dd><dt><code>--only-downloads</code></dt><dd><p>Only show Python downloads, exclude installed distributions.</p>
<p>By default, available downloads for the current platform are shown.</p>
</dd><dt><code>--only-installed</code></dt><dd><p>Only show installed Python versions, exclude available downloads.</p> </dd><dt><code>--only-installed</code></dt><dd><p>Only show installed Python versions, exclude available downloads.</p>
<p>By default, available downloads for the current platform are shown.</p> <p>By default, available downloads for the current platform are shown.</p>
@ -4458,6 +4462,10 @@ uv python list [OPTIONS]
</ul> </ul>
</dd><dt><code>--quiet</code>, <code>-q</code></dt><dd><p>Do not print any output</p> </dd><dt><code>--quiet</code>, <code>-q</code></dt><dd><p>Do not print any output</p>
</dd><dt><code>--show-urls</code></dt><dd><p>Show the URLs of available Python downloads.</p>
<p>By default, these display as <code>&lt;download available&gt;</code>.</p>
</dd><dt><code>--verbose</code>, <code>-v</code></dt><dd><p>Use verbose output.</p> </dd><dt><code>--verbose</code>, <code>-v</code></dt><dd><p>Use verbose output.</p>
<p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (&lt;https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives&gt;)</p> <p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (&lt;https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives&gt;)</p>