mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-01 14:31:12 +00:00
Improve display of selected interpreter sources (#3748)
e.g. this error message is not great ``` ❯ uv venv --python 3.12.2 × No interpreter found for Python 3.12.2 in provided path, search path, managed toolchains, or parent interpreter ```
This commit is contained in:
parent
6962147831
commit
0e75eb8d78
6 changed files with 103 additions and 75 deletions
|
@ -44,9 +44,15 @@ pub enum InterpreterRequest {
|
|||
/// The sources to consider when finding a Python interpreter.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub enum SourceSelector {
|
||||
// Consider all interpreter sources.
|
||||
#[default]
|
||||
All,
|
||||
Some(HashSet<InterpreterSource>),
|
||||
// Only consider system interpreter sources
|
||||
System,
|
||||
// Only consider virtual environment sources
|
||||
VirtualEnv,
|
||||
// Only consider a custom set of sources
|
||||
Custom(HashSet<InterpreterSource>),
|
||||
}
|
||||
|
||||
/// A Python interpreter version request.
|
||||
|
@ -579,11 +585,7 @@ pub fn find_interpreter(
|
|||
/// See [`find_interpreter`] for more details on interpreter discovery.
|
||||
pub fn find_default_interpreter(cache: &Cache) -> Result<InterpreterResult, Error> {
|
||||
let request = InterpreterRequest::Version(VersionRequest::Default);
|
||||
let sources = SourceSelector::from_sources([
|
||||
InterpreterSource::SearchPath,
|
||||
#[cfg(windows)]
|
||||
InterpreterSource::PyLauncher,
|
||||
]);
|
||||
let sources = SourceSelector::System;
|
||||
|
||||
let result = find_interpreter(&request, SystemPython::Required, &sources, cache)?;
|
||||
if let Ok(ref found) = result {
|
||||
|
@ -612,7 +614,7 @@ pub fn find_best_interpreter(
|
|||
debug!("Starting interpreter discovery for {}", request);
|
||||
|
||||
// Determine if we should be allowed to look outside of virtual environments.
|
||||
let sources = SourceSelector::from_env(system);
|
||||
let sources = SourceSelector::from_settings(system);
|
||||
|
||||
// First, check for an exact match (or the first available version if no Python versfion was provided)
|
||||
debug!("Looking for exact match for request {request}");
|
||||
|
@ -1079,19 +1081,33 @@ impl SourceSelector {
|
|||
pub(crate) fn from_sources(iter: impl IntoIterator<Item = InterpreterSource>) -> Self {
|
||||
let inner = HashSet::from_iter(iter);
|
||||
assert!(!inner.is_empty(), "Source selectors cannot be empty");
|
||||
Self::Some(inner)
|
||||
Self::Custom(inner)
|
||||
}
|
||||
|
||||
/// Return true if this selector includes the given [`InterpreterSource`].
|
||||
fn contains(&self, source: InterpreterSource) -> bool {
|
||||
match self {
|
||||
Self::All => true,
|
||||
Self::Some(sources) => sources.contains(&source),
|
||||
Self::System => [
|
||||
InterpreterSource::ProvidedPath,
|
||||
InterpreterSource::SearchPath,
|
||||
#[cfg(windows)]
|
||||
InterpreterSource::PyLauncher,
|
||||
InterpreterSource::ManagedToolchain,
|
||||
InterpreterSource::ParentInterpreter,
|
||||
]
|
||||
.contains(&source),
|
||||
Self::VirtualEnv => [
|
||||
InterpreterSource::DiscoveredEnvironment,
|
||||
InterpreterSource::ActiveEnvironment,
|
||||
]
|
||||
.contains(&source),
|
||||
Self::Custom(sources) => sources.contains(&source),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the default [`SourceSelector`] based on environment variables.
|
||||
pub fn from_env(system: SystemPython) -> Self {
|
||||
/// Return a [`SourceSelector`] based the settings.
|
||||
pub fn from_settings(system: SystemPython) -> Self {
|
||||
if env::var_os("UV_FORCE_MANAGED_PYTHON").is_some() {
|
||||
debug!("Only considering managed toolchains due to `UV_FORCE_MANAGED_PYTHON`");
|
||||
Self::from_sources([InterpreterSource::ManagedToolchain])
|
||||
|
@ -1106,18 +1122,8 @@ impl SourceSelector {
|
|||
} else {
|
||||
match system {
|
||||
SystemPython::Allowed | SystemPython::Explicit => Self::All,
|
||||
SystemPython::Required => Self::from_sources([
|
||||
InterpreterSource::ProvidedPath,
|
||||
InterpreterSource::SearchPath,
|
||||
#[cfg(windows)]
|
||||
InterpreterSource::PyLauncher,
|
||||
InterpreterSource::ManagedToolchain,
|
||||
InterpreterSource::ParentInterpreter,
|
||||
]),
|
||||
SystemPython::Disallowed => Self::from_sources([
|
||||
InterpreterSource::DiscoveredEnvironment,
|
||||
InterpreterSource::ActiveEnvironment,
|
||||
]),
|
||||
SystemPython::Required => Self::System,
|
||||
SystemPython::Disallowed => Self::VirtualEnv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1232,7 +1238,21 @@ impl fmt::Display for SourceSelector {
|
|||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::All => f.write_str("all sources"),
|
||||
Self::Some(sources) => {
|
||||
Self::VirtualEnv => f.write_str("virtual environments"),
|
||||
Self::System => {
|
||||
// TODO(zanieb): We intentionally omit managed toolchains for now since they are not public
|
||||
if cfg!(windows) {
|
||||
write!(
|
||||
f,
|
||||
"{} or {}",
|
||||
InterpreterSource::SearchPath,
|
||||
InterpreterSource::PyLauncher
|
||||
)
|
||||
} else {
|
||||
write!(f, "{}", InterpreterSource::SearchPath)
|
||||
}
|
||||
}
|
||||
Self::Custom(sources) => {
|
||||
let sources: Vec<_> = sources
|
||||
.iter()
|
||||
.sorted()
|
||||
|
|
|
@ -51,10 +51,7 @@ impl PythonEnvironment {
|
|||
|
||||
/// Create a [`PythonEnvironment`] for an existing virtual environment.
|
||||
pub fn from_virtualenv(cache: &Cache) -> Result<Self, Error> {
|
||||
let sources = SourceSelector::from_sources([
|
||||
InterpreterSource::DiscoveredEnvironment,
|
||||
InterpreterSource::ActiveEnvironment,
|
||||
]);
|
||||
let sources = SourceSelector::VirtualEnv;
|
||||
let request = InterpreterRequest::Version(VersionRequest::Default);
|
||||
let found = find_interpreter(&request, SystemPython::Disallowed, &sources, cache)??;
|
||||
|
||||
|
@ -109,7 +106,7 @@ impl PythonEnvironment {
|
|||
system: SystemPython,
|
||||
cache: &Cache,
|
||||
) -> Result<Self, Error> {
|
||||
let sources = SourceSelector::from_env(system);
|
||||
let sources = SourceSelector::from_settings(system);
|
||||
let request = InterpreterRequest::parse(request);
|
||||
let interpreter = find_interpreter(&request, system, &sources, cache)??.into_interpreter();
|
||||
Ok(Self(Arc::new(PythonEnvironmentShared {
|
||||
|
|
|
@ -700,12 +700,7 @@ mod tests {
|
|||
fn find_interpreter_version_minor() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let cache = Cache::temp()?;
|
||||
let sources = SourceSelector::from_sources([
|
||||
InterpreterSource::ProvidedPath,
|
||||
InterpreterSource::ActiveEnvironment,
|
||||
InterpreterSource::DiscoveredEnvironment,
|
||||
InterpreterSource::SearchPath,
|
||||
]);
|
||||
let sources = SourceSelector::All;
|
||||
|
||||
with_vars(
|
||||
[
|
||||
|
@ -757,12 +752,7 @@ mod tests {
|
|||
fn find_interpreter_version_patch() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let cache = Cache::temp()?;
|
||||
let sources = SourceSelector::from_sources([
|
||||
InterpreterSource::ProvidedPath,
|
||||
InterpreterSource::ActiveEnvironment,
|
||||
InterpreterSource::DiscoveredEnvironment,
|
||||
InterpreterSource::SearchPath,
|
||||
]);
|
||||
let sources = SourceSelector::All;
|
||||
|
||||
with_vars(
|
||||
[
|
||||
|
@ -814,12 +804,7 @@ mod tests {
|
|||
fn find_interpreter_version_minor_no_match() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let cache = Cache::temp()?;
|
||||
let sources = SourceSelector::from_sources([
|
||||
InterpreterSource::ProvidedPath,
|
||||
InterpreterSource::ActiveEnvironment,
|
||||
InterpreterSource::DiscoveredEnvironment,
|
||||
InterpreterSource::SearchPath,
|
||||
]);
|
||||
let sources = SourceSelector::All;
|
||||
|
||||
with_vars(
|
||||
[
|
||||
|
@ -861,12 +846,7 @@ mod tests {
|
|||
fn find_interpreter_version_patch_no_match() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let cache = Cache::temp()?;
|
||||
let sources = SourceSelector::from_sources([
|
||||
InterpreterSource::ProvidedPath,
|
||||
InterpreterSource::ActiveEnvironment,
|
||||
InterpreterSource::DiscoveredEnvironment,
|
||||
InterpreterSource::SearchPath,
|
||||
]);
|
||||
let sources = SourceSelector::All;
|
||||
|
||||
with_vars(
|
||||
[
|
||||
|
|
|
@ -170,7 +170,7 @@ pub(crate) async fn pip_compile(
|
|||
};
|
||||
let interpreter = if let Some(python) = python.as_ref() {
|
||||
let request = InterpreterRequest::parse(python);
|
||||
let sources = SourceSelector::from_env(system);
|
||||
let sources = SourceSelector::from_settings(system);
|
||||
find_interpreter(&request, system, &sources, &cache)??
|
||||
} else {
|
||||
let request = if let Some(version) = python_version.as_ref() {
|
||||
|
|
|
@ -121,7 +121,7 @@ async fn venv_impl(
|
|||
let interpreter = if let Some(python) = python_request.as_ref() {
|
||||
let system = uv_interpreter::SystemPython::Required;
|
||||
let request = InterpreterRequest::parse(python);
|
||||
let sources = SourceSelector::from_env(system);
|
||||
let sources = SourceSelector::from_settings(system);
|
||||
find_interpreter(&request, system, &sources, cache)
|
||||
} else {
|
||||
find_default_interpreter(cache)
|
||||
|
|
|
@ -218,20 +218,33 @@ fn create_venv_unknown_python_minor() {
|
|||
let mut command = context.venv_command();
|
||||
command
|
||||
.arg(context.venv.as_os_str())
|
||||
// Request a version we know we'll never see
|
||||
.arg("--python")
|
||||
.arg("3.15");
|
||||
.arg("3.100")
|
||||
// Unset this variable to force what the user would see
|
||||
.env_remove("UV_TEST_PYTHON_PATH");
|
||||
|
||||
// Note the `py` launcher is not included in the search in Windows due to
|
||||
// `UV_TEST_PYTHON_PATH` being set
|
||||
if cfg!(windows) {
|
||||
uv_snapshot!(&mut command, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× No interpreter found for Python 3.15 in active virtual environment or search path
|
||||
× No interpreter found for Python 3.100 in search path or `py` launcher output
|
||||
"###
|
||||
);
|
||||
} else {
|
||||
uv_snapshot!(&mut command, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× No interpreter found for Python 3.100 in search path
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
context.venv.assert(predicates::path::missing());
|
||||
}
|
||||
|
@ -240,18 +253,36 @@ fn create_venv_unknown_python_minor() {
|
|||
fn create_venv_unknown_python_patch() {
|
||||
let context = VenvTestContext::new(&["3.12"]);
|
||||
|
||||
uv_snapshot!(context.filters(), context.venv_command()
|
||||
let mut command = context.venv_command();
|
||||
command
|
||||
.arg(context.venv.as_os_str())
|
||||
// Request a version we know we'll never see
|
||||
.arg("--python")
|
||||
.arg("3.8.0"), @r###"
|
||||
.arg("3.12.100")
|
||||
// Unset this variable to force what the user would see
|
||||
.env_remove("UV_TEST_PYTHON_PATH");
|
||||
|
||||
if cfg!(windows) {
|
||||
uv_snapshot!(&mut command, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× No interpreter found for Python 3.8.0 in active virtual environment or search path
|
||||
× No interpreter found for Python 3.12.100 in search path or `py` launcher output
|
||||
"###
|
||||
);
|
||||
} else {
|
||||
uv_snapshot!(&mut command, @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× No interpreter found for Python 3.12.100 in search path
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
context.venv.assert(predicates::path::missing());
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue