Skip to content

Commit ec49980

Browse files
authored
Add support for Python version requests in uv python list (#12375)
Allows `uv python list <request>` to filter the installed list. I often want this and it's not hard to add. I tested the remote download filtering locally (#12381 is needed for snapshot tests) ``` ❯ cargo run -q -- python list --all-versions 3.13 cpython-3.13.2-macos-aarch64-none <download available> cpython-3.13.1-macos-aarch64-none /opt/homebrew/opt/[email protected]/bin/python3.13 -> ../Frameworks/Python.framework/Versions/3.13/bin/python3.13 cpython-3.13.1-macos-aarch64-none <download available> cpython-3.13.0-macos-aarch64-none /Users/zb/.local/share/uv/python/cpython-3.13.0-macos-aarch64-none/bin/python3.13 ❯ cargo run -q -- python list --all-versions 3.13 --only-installed cpython-3.13.1-macos-aarch64-none /opt/homebrew/opt/[email protected]/bin/python3.13 -> ../Frameworks/Python.framework/Versions/3.13/bin/python3.13 cpython-3.13.0-macos-aarch64-none /Users/zb/.local/share/uv/python/cpython-3.13.0-macos-aarch64-none/bin/python3.13 ```
1 parent cdd6de5 commit ec49980

File tree

7 files changed

+199
-15
lines changed

7 files changed

+199
-15
lines changed

crates/uv-cli/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -4577,6 +4577,11 @@ pub enum PythonCommand {
45774577
#[derive(Args)]
45784578
#[allow(clippy::struct_excessive_bools)]
45794579
pub struct PythonListArgs {
4580+
/// A Python request to filter by.
4581+
///
4582+
/// See `uv help python` to view supported request formats.
4583+
pub request: Option<String>,
4584+
45804585
/// List all Python versions, including old patch versions.
45814586
///
45824587
/// By default, only the latest patch version is shown for each minor version.

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

+16-7
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ struct PrintData {
5252
/// List available Python installations.
5353
#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
5454
pub(crate) async fn list(
55+
request: Option<String>,
5556
kinds: PythonListKinds,
5657
all_versions: bool,
5758
all_platforms: bool,
@@ -63,23 +64,31 @@ pub(crate) async fn list(
6364
cache: &Cache,
6465
printer: Printer,
6566
) -> Result<ExitStatus> {
67+
let request = request.as_deref().map(PythonRequest::parse);
68+
let base_download_request = if python_preference == PythonPreference::OnlySystem {
69+
None
70+
} else {
71+
// If the user request cannot be mapped to a download request, we won't show any downloads
72+
PythonDownloadRequest::from_request(request.as_ref().unwrap_or(&PythonRequest::Any))
73+
};
74+
6675
let mut output = BTreeSet::new();
67-
if python_preference != PythonPreference::OnlySystem {
76+
if let Some(base_download_request) = base_download_request {
6877
let download_request = match kinds {
6978
PythonListKinds::Installed => None,
7079
PythonListKinds::Downloads => Some(if all_platforms {
71-
PythonDownloadRequest::default()
80+
base_download_request
7281
} else {
73-
PythonDownloadRequest::from_env()?
82+
base_download_request.fill()?
7483
}),
7584
PythonListKinds::Default => {
7685
if python_downloads.is_automatic() {
7786
Some(if all_platforms {
78-
PythonDownloadRequest::default()
87+
base_download_request
7988
} else if all_arches {
80-
PythonDownloadRequest::from_env()?.with_any_arch()
89+
base_download_request.fill()?.with_any_arch()
8190
} else {
82-
PythonDownloadRequest::from_env()?
91+
base_download_request.fill()?
8392
})
8493
} else {
8594
// If fetching is not automatic, then don't show downloads as available by default
@@ -109,7 +118,7 @@ pub(crate) async fn list(
109118
match kinds {
110119
PythonListKinds::Installed | PythonListKinds::Default => {
111120
Some(find_python_installations(
112-
&PythonRequest::Any,
121+
request.as_ref().unwrap_or(&PythonRequest::Any),
113122
EnvironmentPreference::OnlySystem,
114123
python_preference,
115124
cache,

crates/uv/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
12721272
let cache = cache.init()?;
12731273

12741274
commands::python_list(
1275+
args.request,
12751276
args.kinds,
12761277
args.all_versions,
12771278
args.all_platforms,

crates/uv/src/settings.rs

+3
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,7 @@ pub(crate) enum PythonListKinds {
826826
#[allow(clippy::struct_excessive_bools)]
827827
#[derive(Debug, Clone)]
828828
pub(crate) struct PythonListSettings {
829+
pub(crate) request: Option<String>,
829830
pub(crate) kinds: PythonListKinds,
830831
pub(crate) all_platforms: bool,
831832
pub(crate) all_arches: bool,
@@ -839,6 +840,7 @@ impl PythonListSettings {
839840
#[allow(clippy::needless_pass_by_value)]
840841
pub(crate) fn resolve(args: PythonListArgs, _filesystem: Option<FilesystemOptions>) -> Self {
841842
let PythonListArgs {
843+
request,
842844
all_versions,
843845
all_platforms,
844846
all_arches,
@@ -857,6 +859,7 @@ impl PythonListSettings {
857859
};
858860

859861
Self {
862+
request,
860863
kinds,
861864
all_platforms,
862865
all_arches,

crates/uv/tests/it/python_list.rs

+153-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use uv_python::platform::{Arch, Os};
12
use uv_static::EnvVars;
23

34
use crate::common::{uv_snapshot, TestContext};
@@ -27,6 +28,79 @@ fn python_list() {
2728
----- stderr -----
2829
");
2930

31+
// Request Python 3.12
32+
uv_snapshot!(context.filters(), context.python_list().arg("3.12"), @r"
33+
success: true
34+
exit_code: 0
35+
----- stdout -----
36+
cpython-3.12.[X]-[PLATFORM] [PYTHON-3.12]
37+
38+
----- stderr -----
39+
");
40+
41+
// Request Python 3.11
42+
uv_snapshot!(context.filters(), context.python_list().arg("3.11"), @r"
43+
success: true
44+
exit_code: 0
45+
----- stdout -----
46+
cpython-3.11.[X]-[PLATFORM] [PYTHON-3.11]
47+
48+
----- stderr -----
49+
");
50+
51+
// Request CPython
52+
uv_snapshot!(context.filters(), context.python_list().arg("cpython"), @r"
53+
success: true
54+
exit_code: 0
55+
----- stdout -----
56+
cpython-3.12.[X]-[PLATFORM] [PYTHON-3.12]
57+
cpython-3.11.[X]-[PLATFORM] [PYTHON-3.11]
58+
59+
----- stderr -----
60+
");
61+
62+
// Request CPython 3.12
63+
uv_snapshot!(context.filters(), context.python_list().arg("[email protected]"), @r"
64+
success: true
65+
exit_code: 0
66+
----- stdout -----
67+
cpython-3.12.[X]-[PLATFORM] [PYTHON-3.12]
68+
69+
----- stderr -----
70+
");
71+
72+
// Request CPython 3.12 via partial key syntax
73+
uv_snapshot!(context.filters(), context.python_list().arg("cpython-3.12"), @r"
74+
success: true
75+
exit_code: 0
76+
----- stdout -----
77+
cpython-3.12.[X]-[PLATFORM] [PYTHON-3.12]
78+
79+
----- stderr -----
80+
");
81+
82+
// Request CPython 3.12 for the current platform
83+
let os = Os::from_env();
84+
let arch = Arch::from_env();
85+
86+
uv_snapshot!(context.filters(), context.python_list().arg(format!("cpython-3.12-{os}-{arch}")), @r"
87+
success: true
88+
exit_code: 0
89+
----- stdout -----
90+
cpython-3.12.[X]-[PLATFORM] [PYTHON-3.12]
91+
92+
----- stderr -----
93+
");
94+
95+
// Request PyPy (which should be missing)
96+
uv_snapshot!(context.filters(), context.python_list().arg("pypy"), @r"
97+
success: true
98+
exit_code: 0
99+
----- stdout -----
100+
101+
----- stderr -----
102+
");
103+
30104
// Swap the order of the Python versions
31105
context.python_versions.reverse();
32106

@@ -42,16 +116,12 @@ fn python_list() {
42116

43117
// Request Python 3.11
44118
uv_snapshot!(context.filters(), context.python_list().arg("3.11"), @r"
45-
success: false
46-
exit_code: 2
119+
success: true
120+
exit_code: 0
47121
----- stdout -----
122+
cpython-3.11.[X]-[PLATFORM] [PYTHON-3.11]
48123
49124
----- stderr -----
50-
error: unexpected argument '3.11' found
51-
52-
Usage: uv python list [OPTIONS]
53-
54-
For more information, try '--help'.
55125
");
56126
}
57127

@@ -134,3 +204,79 @@ fn python_list_venv() {
134204
----- stderr -----
135205
");
136206
}
207+
208+
#[cfg(unix)]
209+
#[test]
210+
fn python_list_unsupported_version() {
211+
let context: TestContext = TestContext::new_with_versions(&["3.12"])
212+
.with_filtered_python_symlinks()
213+
.with_filtered_python_keys();
214+
215+
// Request a low version
216+
uv_snapshot!(context.filters(), context.python_list().arg("3.6"), @r"
217+
success: false
218+
exit_code: 2
219+
----- stdout -----
220+
221+
----- stderr -----
222+
error: Invalid version request: Python <3.7 is not supported but 3.6 was requested.
223+
");
224+
225+
// Request a low version with a patch
226+
uv_snapshot!(context.filters(), context.python_list().arg("3.6.9"), @r"
227+
success: false
228+
exit_code: 2
229+
----- stdout -----
230+
231+
----- stderr -----
232+
error: Invalid version request: Python <3.7 is not supported but 3.6.9 was requested.
233+
");
234+
235+
// Request a really low version
236+
uv_snapshot!(context.filters(), context.python_list().arg("2.6"), @r"
237+
success: false
238+
exit_code: 2
239+
----- stdout -----
240+
241+
----- stderr -----
242+
error: Invalid version request: Python <3.7 is not supported but 2.6 was requested.
243+
");
244+
245+
// Request a really low version with a patch
246+
uv_snapshot!(context.filters(), context.python_list().arg("2.6.8"), @r"
247+
success: false
248+
exit_code: 2
249+
----- stdout -----
250+
251+
----- stderr -----
252+
error: Invalid version request: Python <3.7 is not supported but 2.6.8 was requested.
253+
");
254+
255+
// Request a future version
256+
uv_snapshot!(context.filters(), context.python_list().arg("4.2"), @r"
257+
success: true
258+
exit_code: 0
259+
----- stdout -----
260+
261+
----- stderr -----
262+
");
263+
264+
// Request a low version with a range
265+
uv_snapshot!(context.filters(), context.python_list().arg("<3.0"), @r"
266+
success: true
267+
exit_code: 0
268+
----- stdout -----
269+
270+
----- stderr -----
271+
");
272+
273+
// Request free-threaded Python on unsupported version
274+
uv_snapshot!(context.filters(), context.python_list().arg("3.12t"), @r"
275+
success: false
276+
exit_code: 2
277+
----- stdout -----
278+
279+
----- stderr -----
280+
error: Invalid version request: Python <3.13 does not support free-threading but 3.12t was requested.
281+
");
282+
}

docs/concepts/python-versions.md

+12
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,18 @@ To list installed and available Python versions:
173173
$ uv python list
174174
```
175175

176+
To filter the Python versions, provide a request, e.g., to show all Python 3.13 interpreters:
177+
178+
```console
179+
$ uv python list 3.13
180+
```
181+
182+
Or, to show all PyPy interpreters:
183+
184+
```console
185+
$ uv python list pypy
186+
```
187+
176188
By default, downloads for other platforms and old patch versions are hidden.
177189

178190
To view all versions:

docs/reference/cli.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -4603,9 +4603,17 @@ Use `--only-installed` to omit available downloads.
46034603
<h3 class="cli-reference">Usage</h3>
46044604

46054605
```
4606-
uv python list [OPTIONS]
4606+
uv python list [OPTIONS] [REQUEST]
46074607
```
46084608

4609+
<h3 class="cli-reference">Arguments</h3>
4610+
4611+
<dl class="cli-reference"><dt id="uv-python-list--request"><a href="#uv-python-list--request"<code>REQUEST</code></a></dt><dd><p>A Python request to filter by.</p>
4612+
4613+
<p>See <a href="#uv-python">uv python</a> to view supported request formats.</p>
4614+
4615+
</dd></dl>
4616+
46094617
<h3 class="cli-reference">Options</h3>
46104618

46114619
<dl class="cli-reference"><dt id="uv-python-list--all-arches"><a href="#uv-python-list--all-arches"><code>--all-arches</code></a></dt><dd><p>List Python downloads for all architectures.</p>

0 commit comments

Comments
 (0)