Improve error message when a virtual environment Python symlink is broken (#12168)

When removing a Python interpreter underneath an existing venv, uv
currently shows a not found error:

```
error: Failed to inspect Python interpreter from active virtual environment at `.venv/bin/python3`
  Caused by: Python interpreter not found at `/home/konsti/projects/uv/.venv/bin/python3`
```

This is unintuitive, as the file for the Python interpreter does exist,
it is a broken symlink that needs to be replaced with `uv venv`.

I've been encountering those occasionally, and I expect users that
switch between versions a lot will, too, especially when they also use
pyenv or a similar Python manager.

The new error hints at this solution:

```
error: Failed to inspect Python interpreter from active virtual environment at `.venv/bin/python3`
  Caused by: Broken symlink at `.venv/bin/python3`, was the underlying Python interpreter removed?

hint: To recreate the virtual environment, run `uv venv`
```
This commit is contained in:
konsti
2025-05-07 20:24:53 +02:00
committed by GitHub
parent a43333351e
commit 364e3999d4
6 changed files with 134 additions and 36 deletions
+17 -12
View File
@@ -35,7 +35,7 @@ use crate::virtualenv::{
};
#[cfg(windows)]
use crate::windows_registry::{registry_pythons, WindowsPython};
use crate::{Interpreter, PythonVersion};
use crate::{BrokenSymlink, Interpreter, PythonVersion};
/// A request to find a Python installation.
///
@@ -815,7 +815,8 @@ impl Error {
);
false
}
InterpreterError::NotFound(path) => {
InterpreterError::NotFound(path)
| InterpreterError::BrokenSymlink(BrokenSymlink { path, .. }) => {
// If the interpreter is from an active, valid virtual environment, we should
// fail because it's broken
if let Some(Ok(true)) = matches!(source, PythonSource::ActiveEnvironment)
@@ -894,11 +895,13 @@ pub fn find_python_installations<'a>(
debug!("Checking for Python interpreter at {request}");
match python_installation_from_executable(path, cache) {
Ok(installation) => Ok(Ok(installation)),
Err(InterpreterError::NotFound(_)) => Ok(Err(PythonNotFound {
request: request.clone(),
python_preference: preference,
environment_preference: environments,
})),
Err(InterpreterError::NotFound(_) | InterpreterError::BrokenSymlink(_)) => {
Ok(Err(PythonNotFound {
request: request.clone(),
python_preference: preference,
environment_preference: environments,
}))
}
Err(err) => Err(Error::Query(
Box::new(err),
path.clone(),
@@ -918,11 +921,13 @@ pub fn find_python_installations<'a>(
debug!("Checking for Python interpreter in {request}");
match python_installation_from_directory(path, cache) {
Ok(installation) => Ok(Ok(installation)),
Err(InterpreterError::NotFound(_)) => Ok(Err(PythonNotFound {
request: request.clone(),
python_preference: preference,
environment_preference: environments,
})),
Err(InterpreterError::NotFound(_) | InterpreterError::BrokenSymlink(_)) => {
Ok(Err(PythonNotFound {
request: request.clone(),
python_preference: preference,
environment_preference: environments,
}))
}
Err(err) => Err(Error::Query(
Box::new(err),
path.clone(),