Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6038,6 +6038,12 @@ pub struct PythonListArgs {
#[arg(long, alias = "all_architectures")]
pub all_arches: bool,

/// Show all Python variants, including debug and freethreaded builds.
///
/// By default, debug and freethreaded builds are hidden from the list.
#[arg(long)]
pub all_variants: bool,

/// Only show installed Python versions.
///
/// By default, installed distributions and available downloads for the current platform are shown.
Expand Down
27 changes: 23 additions & 4 deletions crates/uv-python/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1742,6 +1742,21 @@ impl PythonVariant {
}
}
impl PythonRequest {
/// Return the [`PythonVariant`] of the request, if any.
pub fn variant(&self) -> Option<PythonVariant> {
match self {
Self::Version(version) => version.variant(),
Self::ImplementationVersion(_, version) => version.variant(),
Self::Default
| Self::Any
| Self::Directory(_)
| Self::File(_)
| Self::ExecutableName(_)
| Self::Implementation(_)
| Self::Key(_) => None,
}
}

/// Create a request from a string.
///
/// This cannot fail, which means weird inputs will be parsed as [`PythonRequest::File`] or
Expand Down Expand Up @@ -3453,7 +3468,8 @@ mod tests {
os: None,
libc: None,
build: None,
prereleases: None
prereleases: None,
all_variants: false
})
);
assert_eq!(
Expand All @@ -3473,7 +3489,8 @@ mod tests {
os: Some(Os::new(target_lexicon::OperatingSystem::Darwin(None))),
libc: Some(Libc::None),
build: None,
prereleases: None
prereleases: None,
all_variants: false
})
);
assert_eq!(
Expand All @@ -3490,7 +3507,8 @@ mod tests {
os: None,
libc: None,
build: None,
prereleases: None
prereleases: None,
all_variants: false
})
);
assert_eq!(
Expand All @@ -3510,7 +3528,8 @@ mod tests {
os: None,
libc: None,
build: None,
prereleases: None
prereleases: None,
all_variants: false
})
);

Expand Down
35 changes: 30 additions & 5 deletions crates/uv-python/src/downloads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ pub struct PythonDownloadRequest {
/// Whether to allow pre-releases or not. If not set, defaults to true if [`Self::version`] is
/// not None, and false otherwise.
pub(crate) prereleases: Option<bool>,

/// Whether to include all Python variants (e.g., debug, freethreaded) in the results.
///
/// If `true`, all variants matching the version request are included.
/// If `false`, only the variant specified in the [`Self::version`] request is included,
/// defaulting to the default variant if no variant is specified.
pub(crate) all_variants: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -255,6 +262,7 @@ impl PythonDownloadRequest {
os: Option<Os>,
libc: Option<Libc>,
prereleases: Option<bool>,
all_variants: bool,
) -> Self {
Self {
version,
Expand All @@ -264,6 +272,7 @@ impl PythonDownloadRequest {
libc,
build: None,
prereleases,
all_variants,
}
}

Expand Down Expand Up @@ -319,6 +328,12 @@ impl PythonDownloadRequest {
self
}

#[must_use]
pub fn with_all_variants(mut self, all_variants: bool) -> Self {
self.all_variants = all_variants;
self
}

#[must_use]
pub fn with_build(mut self, build: String) -> Self {
self.build = Some(build);
Expand Down Expand Up @@ -526,10 +541,10 @@ impl PythonDownloadRequest {
) {
return false;
}
if let Some(variant) = version.variant() {
if variant != key.variant {
return false;
}
// When all_variants is false, only match the variant from the version request.
// For example, `3.13+debug` will only match debug builds.
if !self.all_variants && version.variant().is_some_and(|v| v != key.variant) {
return false;
}
}
true
Expand Down Expand Up @@ -646,6 +661,7 @@ impl TryFrom<&PythonInstallationKey> for PythonDownloadRequest {
Some(*key.os()),
Some(*key.libc()),
Some(key.prerelease().is_some()),
false,
))
}
}
Expand All @@ -665,6 +681,7 @@ impl From<&ManagedPythonInstallation> for PythonDownloadRequest {
Some(*key.os()),
Some(*key.libc()),
Some(key.prerelease.is_some()),
false,
)
}
}
Expand Down Expand Up @@ -888,7 +905,15 @@ impl FromStr for PythonDownloadRequest {
}
}

Ok(Self::new(version, implementation, arch, os, libc, None))
Ok(Self::new(
version,
implementation,
arch,
os,
libc,
None,
false,
))
}
}

Expand Down
30 changes: 28 additions & 2 deletions crates/uv/src/commands/python/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub(crate) async fn list(
all_versions: bool,
all_platforms: bool,
all_arches: bool,
all_variants: bool,
show_urls: bool,
output_format: PythonListFormat,
python_downloads_json_url: Option<String>,
Expand All @@ -70,11 +71,27 @@ pub(crate) async fn list(
preview: Preview,
) -> Result<ExitStatus> {
let request = request.as_deref().map(PythonRequest::parse);

// Reject --all-variants with explicit non-default variant requests
if all_variants
&& request
.as_ref()
.and_then(uv_python::PythonRequest::variant)
.is_some_and(|v| v != uv_python::PythonVariant::Default)
{
return Err(anyhow::anyhow!(
"`--all-variants` cannot be used with a request that specifies a variant\n\n{}{} Use `--all-variants` to show all variants for a Python version, or specify an exact variant like `3.13t` or `3.13+freethreaded`, but not both",
"hint".bold().cyan(),
":".bold()
));
}

let base_download_request = if python_preference == PythonPreference::OnlySystem {
None
} else {
// If the user request cannot be mapped to a download request, we won't show any downloads
PythonDownloadRequest::from_request(request.as_ref().unwrap_or(&PythonRequest::Any))
.map(|request| request.with_all_variants(all_variants))
Copy link
Member

@zanieb zanieb Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

};

let client = client_builder.build();
Expand Down Expand Up @@ -114,8 +131,17 @@ pub(crate) async fn list(
.map(|request| download_list.iter_matching(request))
.into_iter()
.flatten()
// TODO(zanieb): Add a way to show debug downloads, we just hide them for now
.filter(|download| !download.key().variant().is_debug());
.filter(|download| {
// Show all variants when --all-variants is set
all_variants
// Show all variants when a specific non-default variant was requested
|| !matches!(
request.as_ref().and_then(uv_python::PythonRequest::variant),
Some(uv_python::PythonVariant::Default) | None
)
// Otherwise, hide debug builds by default
|| !download.key().variant().is_debug()
});

for download in downloads {
output.insert((
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args.all_versions,
args.all_platforms,
args.all_arches,
args.all_variants,
args.show_urls,
args.output_format,
args.python_downloads_json_url,
Expand Down
3 changes: 3 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,7 @@ pub(crate) struct PythonListSettings {
pub(crate) all_platforms: bool,
pub(crate) all_arches: bool,
pub(crate) all_versions: bool,
pub(crate) all_variants: bool,
pub(crate) show_urls: bool,
pub(crate) output_format: PythonListFormat,
pub(crate) python_downloads_json_url: Option<String>,
Expand All @@ -1012,6 +1013,7 @@ impl PythonListSettings {
all_arches,
only_installed,
only_downloads,
all_variants,
show_urls,
output_format,
python_downloads_json_url: python_downloads_json_url_arg,
Expand Down Expand Up @@ -1041,6 +1043,7 @@ impl PythonListSettings {
all_platforms,
all_arches,
all_versions,
all_variants,
show_urls,
output_format,
python_downloads_json_url,
Expand Down
Loading
Loading