mirror of https://github.com/astral-sh/uv
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:
parent
3ee2b10738
commit
624e79a8a9
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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,48 +68,60 @@ 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 =
|
||||||
&PythonRequest::Any,
|
match kinds {
|
||||||
EnvironmentPreference::OnlySystem,
|
PythonListKinds::Installed | PythonListKinds::Default => {
|
||||||
python_preference,
|
Some(find_python_installations(
|
||||||
cache,
|
&PythonRequest::Any,
|
||||||
)
|
EnvironmentPreference::OnlySystem,
|
||||||
// Raise discovery errors if critical
|
python_preference,
|
||||||
.filter(|result| {
|
cache,
|
||||||
result
|
)
|
||||||
.as_ref()
|
// Raise discovery errors if critical
|
||||||
.err()
|
.filter(|result| {
|
||||||
.map_or(true, DiscoveryError::is_critical)
|
result
|
||||||
})
|
.as_ref()
|
||||||
.collect::<Result<Vec<Result<PythonInstallation, PythonNotFound>>, DiscoveryError>>()?
|
.err()
|
||||||
.into_iter()
|
.map_or(true, DiscoveryError::is_critical)
|
||||||
// Drop any "missing" installations
|
})
|
||||||
.filter_map(Result::ok);
|
.collect::<Result<Vec<Result<PythonInstallation, PythonNotFound>>, DiscoveryError>>()?
|
||||||
|
.into_iter()
|
||||||
for installation in installed {
|
// Drop any "missing" installations
|
||||||
let kind = if matches!(installation.source(), PythonSource::Managed) {
|
.filter_map(Result::ok))
|
||||||
Kind::Managed
|
}
|
||||||
} else {
|
PythonListKinds::Downloads => None,
|
||||||
Kind::System
|
|
||||||
};
|
};
|
||||||
output.insert((
|
|
||||||
installation.key(),
|
if let Some(installed) = installed {
|
||||||
kind,
|
for installation in installed {
|
||||||
Some(installation.interpreter().sys_executable().to_path_buf()),
|
let kind = if matches!(installation.source(), PythonSource::Managed) {
|
||||||
));
|
Kind::Managed
|
||||||
|
} else {
|
||||||
|
Kind::System
|
||||||
|
};
|
||||||
|
output.insert((
|
||||||
|
installation.key(),
|
||||||
|
kind,
|
||||||
|
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,30 +169,37 @@ 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 {
|
||||||
let is_symlink = fs_err::symlink_metadata(path)?.is_symlink();
|
Either::Left(path) => {
|
||||||
if is_symlink {
|
let is_symlink = fs_err::symlink_metadata(path)?.is_symlink();
|
||||||
writeln!(
|
if is_symlink {
|
||||||
printer.stdout(),
|
writeln!(
|
||||||
"{key:width$} {} -> {}",
|
printer.stdout(),
|
||||||
path.user_display().cyan(),
|
"{key:width$} {} -> {}",
|
||||||
path.read_link()?.user_display().cyan()
|
path.user_display().cyan(),
|
||||||
)?;
|
path.read_link()?.user_display().cyan()
|
||||||
} else {
|
)?;
|
||||||
writeln!(
|
} else {
|
||||||
printer.stdout(),
|
writeln!(
|
||||||
"{key:width$} {}",
|
printer.stdout(),
|
||||||
path.user_display().cyan()
|
"{key:width$} {}",
|
||||||
)?;
|
path.user_display().cyan()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Either::Right(url) => {
|
||||||
|
if show_urls {
|
||||||
|
writeln!(printer.stdout(), "{key:width$} {}", url.dimmed())?;
|
||||||
|
} else {
|
||||||
|
writeln!(
|
||||||
|
printer.stdout(),
|
||||||
|
"{key:width$} {}",
|
||||||
|
"<download available>".dimmed()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
writeln!(
|
|
||||||
printer.stdout(),
|
|
||||||
"{key:width$} {}",
|
|
||||||
"<download available>".dimmed()
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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><download available></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. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)</p>
|
<p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)</p>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue