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:
@@ -677,6 +677,8 @@ impl Display for StatusCodeError {
|
||||
pub enum Error {
|
||||
#[error("Failed to query Python interpreter")]
|
||||
Io(#[from] io::Error),
|
||||
#[error(transparent)]
|
||||
BrokenSymlink(BrokenSymlink),
|
||||
#[error("Python interpreter not found at `{0}`")]
|
||||
NotFound(PathBuf),
|
||||
#[error("Failed to query Python interpreter at `{path}`")]
|
||||
@@ -699,6 +701,33 @@ pub enum Error {
|
||||
Encode(#[from] rmp_serde::encode::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub struct BrokenSymlink {
|
||||
pub path: PathBuf,
|
||||
/// Whether the interpreter path looks like a virtual environment.
|
||||
pub venv: bool,
|
||||
}
|
||||
|
||||
impl Display for BrokenSymlink {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Broken symlink at `{}`, was the underlying Python interpreter removed?",
|
||||
self.path.user_display()
|
||||
)?;
|
||||
if self.venv {
|
||||
writeln!(
|
||||
f,
|
||||
"\n\n{}{} Consider recreating the environment (e.g., with `{}`)",
|
||||
"hint".bold().cyan(),
|
||||
":".bold(),
|
||||
"uv venv".green()
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(tag = "result", rename_all = "lowercase")]
|
||||
enum InterpreterInfoResult {
|
||||
@@ -879,7 +908,24 @@ impl InterpreterInfo {
|
||||
.and_then(Timestamp::from_path)
|
||||
.map_err(|err| {
|
||||
if err.kind() == io::ErrorKind::NotFound {
|
||||
Error::NotFound(executable.to_path_buf())
|
||||
// Check if it looks like a venv interpreter where the underlying Python
|
||||
// installation was removed.
|
||||
if absolute
|
||||
.symlink_metadata()
|
||||
.is_ok_and(|metadata| metadata.is_symlink())
|
||||
{
|
||||
let venv = executable
|
||||
.parent()
|
||||
.and_then(Path::parent)
|
||||
.map(|path| path.join("pyvenv.cfg").is_file())
|
||||
.unwrap_or(false);
|
||||
Error::BrokenSymlink(BrokenSymlink {
|
||||
path: executable.to_path_buf(),
|
||||
venv,
|
||||
})
|
||||
} else {
|
||||
Error::NotFound(executable.to_path_buf())
|
||||
}
|
||||
} else {
|
||||
err.into()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user