Skip to content

Commit 624e79a

Browse files
authored
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 ```
1 parent 3ee2b10 commit 624e79a

File tree

5 files changed

+111
-55
lines changed

5 files changed

+111
-55
lines changed

crates/uv-cli/src/lib.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -4225,8 +4225,20 @@ pub struct PythonListArgs {
42254225
/// Only show installed Python versions, exclude available downloads.
42264226
///
42274227
/// By default, available downloads for the current platform are shown.
4228-
#[arg(long)]
4228+
#[arg(long, conflicts_with("only_downloads"))]
42294229
pub only_installed: bool,
4230+
4231+
/// Only show Python downloads, exclude installed distributions.
4232+
///
4233+
/// By default, available downloads for the current platform are shown.
4234+
#[arg(long, conflicts_with("only_installed"))]
4235+
pub only_downloads: bool,
4236+
4237+
/// Show the URLs of available Python downloads.
4238+
///
4239+
/// By default, these display as `<download available>`.
4240+
#[arg(long)]
4241+
pub show_urls: bool,
42304242
}
42314243

42324244
#[derive(Args)]

crates/uv/src/commands/python/list.rs

+80-54
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::collections::BTreeSet;
22
use std::fmt::Write;
33

44
use anyhow::Result;
5+
use itertools::Either;
56
use owo_colors::OwoColorize;
67
use rustc_hash::FxHashSet;
78
use uv_cache::Cache;
@@ -29,6 +30,7 @@ pub(crate) async fn list(
2930
kinds: PythonListKinds,
3031
all_versions: bool,
3132
all_platforms: bool,
33+
show_urls: bool,
3234
python_preference: PythonPreference,
3335
python_downloads: PythonDownloads,
3436
cache: &Cache,
@@ -38,6 +40,11 @@ pub(crate) async fn list(
3840
if python_preference != PythonPreference::OnlySystem {
3941
let download_request = match kinds {
4042
PythonListKinds::Installed => None,
43+
PythonListKinds::Downloads => Some(if all_platforms {
44+
PythonDownloadRequest::default()
45+
} else {
46+
PythonDownloadRequest::from_env()?
47+
}),
4148
PythonListKinds::Default => {
4249
if python_downloads.is_automatic() {
4350
Some(if all_platforms {
@@ -61,48 +68,60 @@ pub(crate) async fn list(
6168
.flatten();
6269

6370
for download in downloads {
64-
output.insert((download.key().clone(), Kind::Download, None));
71+
output.insert((
72+
download.key().clone(),
73+
Kind::Download,
74+
Either::Right(download.url()),
75+
));
6576
}
6677
};
6778

68-
let installed = find_python_installations(
69-
&PythonRequest::Any,
70-
EnvironmentPreference::OnlySystem,
71-
python_preference,
72-
cache,
73-
)
74-
// Raise discovery errors if critical
75-
.filter(|result| {
76-
result
77-
.as_ref()
78-
.err()
79-
.map_or(true, DiscoveryError::is_critical)
80-
})
81-
.collect::<Result<Vec<Result<PythonInstallation, PythonNotFound>>, DiscoveryError>>()?
82-
.into_iter()
83-
// Drop any "missing" installations
84-
.filter_map(Result::ok);
85-
86-
for installation in installed {
87-
let kind = if matches!(installation.source(), PythonSource::Managed) {
88-
Kind::Managed
89-
} else {
90-
Kind::System
79+
let installed =
80+
match kinds {
81+
PythonListKinds::Installed | PythonListKinds::Default => {
82+
Some(find_python_installations(
83+
&PythonRequest::Any,
84+
EnvironmentPreference::OnlySystem,
85+
python_preference,
86+
cache,
87+
)
88+
// Raise discovery errors if critical
89+
.filter(|result| {
90+
result
91+
.as_ref()
92+
.err()
93+
.map_or(true, DiscoveryError::is_critical)
94+
})
95+
.collect::<Result<Vec<Result<PythonInstallation, PythonNotFound>>, DiscoveryError>>()?
96+
.into_iter()
97+
// Drop any "missing" installations
98+
.filter_map(Result::ok))
99+
}
100+
PythonListKinds::Downloads => None,
91101
};
92-
output.insert((
93-
installation.key(),
94-
kind,
95-
Some(installation.interpreter().sys_executable().to_path_buf()),
96-
));
102+
103+
if let Some(installed) = installed {
104+
for installation in installed {
105+
let kind = if matches!(installation.source(), PythonSource::Managed) {
106+
Kind::Managed
107+
} else {
108+
Kind::System
109+
};
110+
output.insert((
111+
installation.key(),
112+
kind,
113+
Either::Left(installation.interpreter().sys_executable().to_path_buf()),
114+
));
115+
}
97116
}
98117

99118
let mut seen_minor = FxHashSet::default();
100119
let mut seen_patch = FxHashSet::default();
101120
let mut seen_paths = FxHashSet::default();
102121
let mut include = Vec::new();
103-
for (key, kind, path) in output.iter().rev() {
122+
for (key, kind, uri) in output.iter().rev() {
104123
// Do not show the same path more than once
105-
if let Some(path) = path {
124+
if let Either::Left(path) = uri {
106125
if !seen_paths.insert(path) {
107126
continue;
108127
}
@@ -142,38 +161,45 @@ pub(crate) async fn list(
142161
}
143162
}
144163
}
145-
include.push((key, path));
164+
include.push((key, uri));
146165
}
147166

148167
// Compute the width of the first column.
149168
let width = include
150169
.iter()
151170
.fold(0usize, |acc, (key, _)| acc.max(key.to_string().len()));
152171

153-
for (key, path) in include {
172+
for (key, uri) in include {
154173
let key = key.to_string();
155-
if let Some(path) = path {
156-
let is_symlink = fs_err::symlink_metadata(path)?.is_symlink();
157-
if is_symlink {
158-
writeln!(
159-
printer.stdout(),
160-
"{key:width$} {} -> {}",
161-
path.user_display().cyan(),
162-
path.read_link()?.user_display().cyan()
163-
)?;
164-
} else {
165-
writeln!(
166-
printer.stdout(),
167-
"{key:width$} {}",
168-
path.user_display().cyan()
169-
)?;
174+
match uri {
175+
Either::Left(path) => {
176+
let is_symlink = fs_err::symlink_metadata(path)?.is_symlink();
177+
if is_symlink {
178+
writeln!(
179+
printer.stdout(),
180+
"{key:width$} {} -> {}",
181+
path.user_display().cyan(),
182+
path.read_link()?.user_display().cyan()
183+
)?;
184+
} else {
185+
writeln!(
186+
printer.stdout(),
187+
"{key:width$} {}",
188+
path.user_display().cyan()
189+
)?;
190+
}
191+
}
192+
Either::Right(url) => {
193+
if show_urls {
194+
writeln!(printer.stdout(), "{key:width$} {}", url.dimmed())?;
195+
} else {
196+
writeln!(
197+
printer.stdout(),
198+
"{key:width$} {}",
199+
"<download available>".dimmed()
200+
)?;
201+
}
170202
}
171-
} else {
172-
writeln!(
173-
printer.stdout(),
174-
"{key:width$} {}",
175-
"<download available>".dimmed()
176-
)?;
177203
}
178204
}
179205

crates/uv/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
10881088
args.kinds,
10891089
args.all_versions,
10901090
args.all_platforms,
1091+
args.show_urls,
10911092
globals.python_preference,
10921093
globals.python_downloads,
10931094
&cache,

crates/uv/src/settings.rs

+9
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,9 @@ impl ToolDirSettings {
703703
pub(crate) enum PythonListKinds {
704704
#[default]
705705
Default,
706+
/// Only list version downloads.
707+
Downloads,
708+
/// Only list installed versions.
706709
Installed,
707710
}
708711

@@ -713,6 +716,7 @@ pub(crate) struct PythonListSettings {
713716
pub(crate) kinds: PythonListKinds,
714717
pub(crate) all_platforms: bool,
715718
pub(crate) all_versions: bool,
719+
pub(crate) show_urls: bool,
716720
}
717721

718722
impl PythonListSettings {
@@ -723,10 +727,14 @@ impl PythonListSettings {
723727
all_versions,
724728
all_platforms,
725729
only_installed,
730+
only_downloads,
731+
show_urls,
726732
} = args;
727733

728734
let kinds = if only_installed {
729735
PythonListKinds::Installed
736+
} else if only_downloads {
737+
PythonListKinds::Downloads
730738
} else {
731739
PythonListKinds::default()
732740
};
@@ -735,6 +743,7 @@ impl PythonListSettings {
735743
kinds,
736744
all_platforms,
737745
all_versions,
746+
show_urls,
738747
}
739748
}
740749
}

docs/reference/cli.md

+8
Original file line numberDiff line numberDiff line change
@@ -4426,6 +4426,10 @@ uv python list [OPTIONS]
44264426

44274427
<p>When disabled, uv will only use locally cached data and locally available files.</p>
44284428

4429+
</dd><dt><code>--only-downloads</code></dt><dd><p>Only show Python downloads, exclude installed distributions.</p>
4430+
4431+
<p>By default, available downloads for the current platform are shown.</p>
4432+
44294433
</dd><dt><code>--only-installed</code></dt><dd><p>Only show installed Python versions, exclude available downloads.</p>
44304434

44314435
<p>By default, available downloads for the current platform are shown.</p>
@@ -4458,6 +4462,10 @@ uv python list [OPTIONS]
44584462
</ul>
44594463
</dd><dt><code>--quiet</code>, <code>-q</code></dt><dd><p>Do not print any output</p>
44604464

4465+
</dd><dt><code>--show-urls</code></dt><dd><p>Show the URLs of available Python downloads.</p>
4466+
4467+
<p>By default, these display as <code>&lt;download available&gt;</code>.</p>
4468+
44614469
</dd><dt><code>--verbose</code>, <code>-v</code></dt><dd><p>Use verbose output.</p>
44624470

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

0 commit comments

Comments
 (0)