diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index c2e1f48bf35e..4b2d011f74b2 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -25,6 +25,7 @@ use crate::managed::ManagedPythonInstallations; use crate::microsoft_store::find_microsoft_store_pythons; #[cfg(windows)] use crate::py_launcher::{registry_pythons, WindowsPython}; +use crate::virtualenv::Error as VirtualEnvError; use crate::virtualenv::{ conda_environment_from_env, virtualenv_from_env, virtualenv_from_working_dir, virtualenv_python_executable, CondaEnvironmentKind, @@ -729,10 +730,27 @@ impl Error { false } InterpreterError::NotFound(path) => { - trace!("Skipping missing interpreter at {}", path.display()); - false + // 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) + .then(|| { + path.parent() + .and_then(Path::parent) + .map(|path| path.join("pyvenv.cfg").try_exists()) + }) + .flatten() + { + true + } else { + trace!("Skipping missing interpreter at {}", path.display()); + false + } } }, + Error::VirtualEnv(VirtualEnvError::MissingPyVenvCfg(path)) => { + trace!("Skipping broken virtualenv at {}", path.display()); + false + } _ => true, } } diff --git a/crates/uv/tests/it/python_find.rs b/crates/uv/tests/it/python_find.rs index 4efd52b31cc9..6aeb5f25b469 100644 --- a/crates/uv/tests/it/python_find.rs +++ b/crates/uv/tests/it/python_find.rs @@ -6,7 +6,7 @@ use indoc::indoc; use uv_python::platform::{Arch, Os}; use uv_static::EnvVars; -use crate::common::{uv_snapshot, TestContext}; +use crate::common::{uv_snapshot, venv_bin_path, TestContext}; #[test] fn python_find() { @@ -560,3 +560,57 @@ fn python_find_unsupported_version() { error: Invalid version request: Python <3.13 does not support free-threading but 3.12t was requested. "###); } + +#[test] +fn python_find_venv_invalid() { + let context: TestContext = TestContext::new("3.12") + // Enable additional filters for Windows compatibility + .with_filtered_exe_suffix() + .with_filtered_python_names() + .with_filtered_virtualenv_bin(); + + // We find the virtual environment + uv_snapshot!(context.filters(), context.python_find(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + [VENV]/[BIN]/python + + ----- stderr ----- + "###); + + // If the binaries are missing from a virtual environment, we fail + fs_err::remove_dir_all(venv_bin_path(&context.venv)).unwrap(); + + uv_snapshot!(context.filters(), context.python_find(), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to inspect Python interpreter from active virtual environment at `.venv/[BIN]/python` + Caused by: Python interpreter not found at `[VENV]/[BIN]/python` + "###); + + // Unless the virtual environment is not active + uv_snapshot!(context.filters(), context.python_find().env_remove("VIRTUAL_ENV"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + [PYTHON-3.12] + + ----- stderr ----- + "###); + + // If there's not a `pyvenv.cfg` file, it's also non-fatal, we ignore the environment + fs_err::remove_file(context.venv.join("pyvenv.cfg")).unwrap(); + + uv_snapshot!(context.filters(), context.python_find(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + [PYTHON-3.12] + + ----- stderr ----- + "###); +}