diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index 6786684a0ed6..288cbba4df14 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -529,7 +529,9 @@ impl ManagedPythonDownload { } pub fn python_version(&self) -> PythonVersion { - self.key.version() + self.key + .version() + .expect("Managed Python downloads should always have valid versions") } /// Return the [`Url`] to use when downloading the distribution. If a mirror is set via the diff --git a/crates/uv-python/src/installation.rs b/crates/uv-python/src/installation.rs index 096c1f679349..3e2119de9ef1 100644 --- a/crates/uv-python/src/installation.rs +++ b/crates/uv-python/src/installation.rs @@ -242,9 +242,8 @@ impl PythonInstallationKey { &self.implementation } - pub fn version(&self) -> PythonVersion { + pub fn version(&self) -> Result { PythonVersion::from_str(&format!("{}.{}.{}", self.major, self.minor, self.patch)) - .expect("Python installation keys must have valid Python versions") } pub fn arch(&self) -> &Arch { @@ -338,7 +337,12 @@ impl Ord for PythonInstallationKey { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.implementation .cmp(&other.implementation) - .then_with(|| self.version().cmp(&other.version())) + .then_with(|| { + self.major + .cmp(&other.major) + .then_with(|| self.minor.cmp(&other.minor)) + .then_with(|| self.patch.cmp(&other.patch)) + }) .then_with(|| self.os.to_string().cmp(&other.os.to_string())) .then_with(|| self.arch.to_string().cmp(&other.arch.to_string())) .then_with(|| self.libc.to_string().cmp(&other.libc.to_string())) diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs index 70f28c96c593..fb289320e784 100644 --- a/crates/uv-python/src/managed.rs +++ b/crates/uv-python/src/managed.rs @@ -282,7 +282,9 @@ impl ManagedPythonInstallation { /// The [`PythonVersion`] of the toolchain. pub fn version(&self) -> PythonVersion { - self.key.version() + self.key + .version() + .expect("Managed Python installations should always have valid versions") } pub fn implementation(&self) -> &ImplementationName { @@ -329,13 +331,17 @@ impl ManagedPythonInstallation { let stdlib = if matches!(self.key.os, Os(target_lexicon::OperatingSystem::Windows)) { self.python_dir().join("Lib") } else { + let version = self + .key + .version() + .expect("Managed Python installations should always have valid versions"); let python = if matches!( self.key.implementation, LenientImplementationName::Known(ImplementationName::PyPy) ) { - format!("pypy{}", self.key.version().python_version()) + format!("pypy{}", version.python_version()) } else { - format!("python{}", self.key.version().python_version()) + format!("python{}", version.python_version()) }; self.python_dir().join("lib").join(python) }; diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 4d94975cb38d..9da452d8c747 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -178,7 +178,13 @@ pub(crate) async fn install( "{}", format!( "Installed {} {}", - format!("Python {}", installed.version()).bold(), + format!( + "Python {}", + installed.version().expect( + "Managed Python installations should always have valid versions" + ) + ) + .bold(), format!("in {}", elapsed(start.elapsed())).dimmed() ) .dimmed() diff --git a/crates/uv/src/commands/python/list.rs b/crates/uv/src/commands/python/list.rs index e1dc8eb89a9b..443efeb1674e 100644 --- a/crates/uv/src/commands/python/list.rs +++ b/crates/uv/src/commands/python/list.rs @@ -4,6 +4,7 @@ use std::fmt::Write; use anyhow::Result; use owo_colors::OwoColorize; use rustc_hash::FxHashSet; +use tracing::warn; use uv_cache::Cache; use uv_fs::Simplified; use uv_python::downloads::PythonDownloadRequest; @@ -106,9 +107,17 @@ pub(crate) async fn list( } } + let version = match key.version() { + Err(err) => { + warn!("Excluding {key} due to invalid Python version: {err}"); + continue; + } + Ok(version) => version, + }; + // Only show the latest patch version for each download unless all were requested if !matches!(kind, Kind::System) { - if let [major, minor, ..] = key.version().release() { + if let [major, minor, ..] = version.release() { if !seen_minor.insert(( *key.os(), *major, @@ -122,7 +131,7 @@ pub(crate) async fn list( } } } - if let [major, minor, patch] = key.version().release() { + if let [major, minor, patch] = version.release() { if !seen_patch.insert(( *key.os(), *major, diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs index 934331bd3177..540832b2e1cd 100644 --- a/crates/uv/src/commands/python/uninstall.rs +++ b/crates/uv/src/commands/python/uninstall.rs @@ -148,7 +148,13 @@ async fn do_uninstall( "{}", format!( "Uninstalled {} {}", - format!("Python {}", uninstalled.version()).bold(), + format!( + "Python {}", + uninstalled.version().expect( + "Managed Python installations should always have valid versions" + ) + ) + .bold(), format!("in {}", elapsed(start.elapsed())).dimmed() ) .dimmed()