Skip to content

Commit 8eff8aa

Browse files
authored
Avoid panicking when encountering an invalid Python version during uv python list (#7131)
Closes #7129 Not entirely sure about the best approach yet.
1 parent 8a0e1fd commit 8eff8aa

File tree

6 files changed

+44
-11
lines changed

6 files changed

+44
-11
lines changed

crates/uv-python/src/downloads.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,9 @@ impl ManagedPythonDownload {
529529
}
530530

531531
pub fn python_version(&self) -> PythonVersion {
532-
self.key.version()
532+
self.key
533+
.version()
534+
.expect("Managed Python downloads should always have valid versions")
533535
}
534536

535537
/// Return the [`Url`] to use when downloading the distribution. If a mirror is set via the

crates/uv-python/src/installation.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,8 @@ impl PythonInstallationKey {
242242
&self.implementation
243243
}
244244

245-
pub fn version(&self) -> PythonVersion {
245+
pub fn version(&self) -> Result<PythonVersion, String> {
246246
PythonVersion::from_str(&format!("{}.{}.{}", self.major, self.minor, self.patch))
247-
.expect("Python installation keys must have valid Python versions")
248247
}
249248

250249
pub fn arch(&self) -> &Arch {
@@ -338,7 +337,12 @@ impl Ord for PythonInstallationKey {
338337
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
339338
self.implementation
340339
.cmp(&other.implementation)
341-
.then_with(|| self.version().cmp(&other.version()))
340+
.then_with(|| {
341+
self.major
342+
.cmp(&other.major)
343+
.then_with(|| self.minor.cmp(&other.minor))
344+
.then_with(|| self.patch.cmp(&other.patch))
345+
})
342346
.then_with(|| self.os.to_string().cmp(&other.os.to_string()))
343347
.then_with(|| self.arch.to_string().cmp(&other.arch.to_string()))
344348
.then_with(|| self.libc.to_string().cmp(&other.libc.to_string()))

crates/uv-python/src/managed.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,9 @@ impl ManagedPythonInstallation {
282282

283283
/// The [`PythonVersion`] of the toolchain.
284284
pub fn version(&self) -> PythonVersion {
285-
self.key.version()
285+
self.key
286+
.version()
287+
.expect("Managed Python installations should always have valid versions")
286288
}
287289

288290
pub fn implementation(&self) -> &ImplementationName {
@@ -329,13 +331,17 @@ impl ManagedPythonInstallation {
329331
let stdlib = if matches!(self.key.os, Os(target_lexicon::OperatingSystem::Windows)) {
330332
self.python_dir().join("Lib")
331333
} else {
334+
let version = self
335+
.key
336+
.version()
337+
.expect("Managed Python installations should always have valid versions");
332338
let python = if matches!(
333339
self.key.implementation,
334340
LenientImplementationName::Known(ImplementationName::PyPy)
335341
) {
336-
format!("pypy{}", self.key.version().python_version())
342+
format!("pypy{}", version.python_version())
337343
} else {
338-
format!("python{}", self.key.version().python_version())
344+
format!("python{}", version.python_version())
339345
};
340346
self.python_dir().join("lib").join(python)
341347
};

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,13 @@ pub(crate) async fn install(
178178
"{}",
179179
format!(
180180
"Installed {} {}",
181-
format!("Python {}", installed.version()).bold(),
181+
format!(
182+
"Python {}",
183+
installed.version().expect(
184+
"Managed Python installations should always have valid versions"
185+
)
186+
)
187+
.bold(),
182188
format!("in {}", elapsed(start.elapsed())).dimmed()
183189
)
184190
.dimmed()

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::fmt::Write;
44
use anyhow::Result;
55
use owo_colors::OwoColorize;
66
use rustc_hash::FxHashSet;
7+
use tracing::warn;
78
use uv_cache::Cache;
89
use uv_fs::Simplified;
910
use uv_python::downloads::PythonDownloadRequest;
@@ -106,9 +107,17 @@ pub(crate) async fn list(
106107
}
107108
}
108109

110+
let version = match key.version() {
111+
Err(err) => {
112+
warn!("Excluding {key} due to invalid Python version: {err}");
113+
continue;
114+
}
115+
Ok(version) => version,
116+
};
117+
109118
// Only show the latest patch version for each download unless all were requested
110119
if !matches!(kind, Kind::System) {
111-
if let [major, minor, ..] = key.version().release() {
120+
if let [major, minor, ..] = version.release() {
112121
if !seen_minor.insert((
113122
*key.os(),
114123
*major,
@@ -122,7 +131,7 @@ pub(crate) async fn list(
122131
}
123132
}
124133
}
125-
if let [major, minor, patch] = key.version().release() {
134+
if let [major, minor, patch] = version.release() {
126135
if !seen_patch.insert((
127136
*key.os(),
128137
*major,

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,13 @@ async fn do_uninstall(
148148
"{}",
149149
format!(
150150
"Uninstalled {} {}",
151-
format!("Python {}", uninstalled.version()).bold(),
151+
format!(
152+
"Python {}",
153+
uninstalled.version().expect(
154+
"Managed Python installations should always have valid versions"
155+
)
156+
)
157+
.bold(),
152158
format!("in {}", elapsed(start.elapsed())).dimmed()
153159
)
154160
.dimmed()

0 commit comments

Comments
 (0)