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.
|
/// The sources to consider when finding a Python interpreter.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
pub enum SourceSelector {
|
pub enum SourceSelector {
|
||||||
|
// Consider all interpreter sources.
|
||||||
#[default]
|
#[default]
|
||||||
All,
|
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.
|
/// A Python interpreter version request.
|
||||||
|
@ -579,11 +585,7 @@ pub fn find_interpreter(
|
||||||
/// See [`find_interpreter`] for more details on interpreter discovery.
|
/// See [`find_interpreter`] for more details on interpreter discovery.
|
||||||
pub fn find_default_interpreter(cache: &Cache) -> Result<InterpreterResult, Error> {
|
pub fn find_default_interpreter(cache: &Cache) -> Result<InterpreterResult, Error> {
|
||||||
let request = InterpreterRequest::Version(VersionRequest::Default);
|
let request = InterpreterRequest::Version(VersionRequest::Default);
|
||||||
let sources = SourceSelector::from_sources([
|
let sources = SourceSelector::System;
|
||||||
InterpreterSource::SearchPath,
|
|
||||||
#[cfg(windows)]
|
|
||||||
InterpreterSource::PyLauncher,
|
|
||||||
]);
|
|
||||||
|
|
||||||
let result = find_interpreter(&request, SystemPython::Required, &sources, cache)?;
|
let result = find_interpreter(&request, SystemPython::Required, &sources, cache)?;
|
||||||
if let Ok(ref found) = result {
|
if let Ok(ref found) = result {
|
||||||
|
@ -612,7 +614,7 @@ pub fn find_best_interpreter(
|
||||||
debug!("Starting interpreter discovery for {}", request);
|
debug!("Starting interpreter discovery for {}", request);
|
||||||
|
|
||||||
// Determine if we should be allowed to look outside of virtual environments.
|
// 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)
|
// 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}");
|
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 {
|
pub(crate) fn from_sources(iter: impl IntoIterator<Item = InterpreterSource>) -> Self {
|
||||||
let inner = HashSet::from_iter(iter);
|
let inner = HashSet::from_iter(iter);
|
||||||
assert!(!inner.is_empty(), "Source selectors cannot be empty");
|
assert!(!inner.is_empty(), "Source selectors cannot be empty");
|
||||||
Self::Some(inner)
|
Self::Custom(inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if this selector includes the given [`InterpreterSource`].
|
/// Return true if this selector includes the given [`InterpreterSource`].
|
||||||
fn contains(&self, source: InterpreterSource) -> bool {
|
fn contains(&self, source: InterpreterSource) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::All => true,
|
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.
|
/// Return a [`SourceSelector`] based the settings.
|
||||||
pub fn from_env(system: SystemPython) -> Self {
|
pub fn from_settings(system: SystemPython) -> Self {
|
||||||
if env::var_os("UV_FORCE_MANAGED_PYTHON").is_some() {
|
if env::var_os("UV_FORCE_MANAGED_PYTHON").is_some() {
|
||||||
debug!("Only considering managed toolchains due to `UV_FORCE_MANAGED_PYTHON`");
|
debug!("Only considering managed toolchains due to `UV_FORCE_MANAGED_PYTHON`");
|
||||||
Self::from_sources([InterpreterSource::ManagedToolchain])
|
Self::from_sources([InterpreterSource::ManagedToolchain])
|
||||||
|
@ -1106,18 +1122,8 @@ impl SourceSelector {
|
||||||
} else {
|
} else {
|
||||||
match system {
|
match system {
|
||||||
SystemPython::Allowed | SystemPython::Explicit => Self::All,
|
SystemPython::Allowed | SystemPython::Explicit => Self::All,
|
||||||
SystemPython::Required => Self::from_sources([
|
SystemPython::Required => Self::System,
|
||||||
InterpreterSource::ProvidedPath,
|
SystemPython::Disallowed => Self::VirtualEnv,
|
||||||
InterpreterSource::SearchPath,
|
|
||||||
#[cfg(windows)]
|
|
||||||
InterpreterSource::PyLauncher,
|
|
||||||
InterpreterSource::ManagedToolchain,
|
|
||||||
InterpreterSource::ParentInterpreter,
|
|
||||||
]),
|
|
||||||
SystemPython::Disallowed => Self::from_sources([
|
|
||||||
InterpreterSource::DiscoveredEnvironment,
|
|
||||||
InterpreterSource::ActiveEnvironment,
|
|
||||||
]),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1232,7 +1238,21 @@ impl fmt::Display for SourceSelector {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::All => f.write_str("all sources"),
|
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
|
let sources: Vec<_> = sources
|
||||||
.iter()
|
.iter()
|
||||||
.sorted()
|
.sorted()
|
||||||
|
|
|
@ -51,10 +51,7 @@ impl PythonEnvironment {
|
||||||
|
|
||||||
/// Create a [`PythonEnvironment`] for an existing virtual environment.
|
/// Create a [`PythonEnvironment`] for an existing virtual environment.
|
||||||
pub fn from_virtualenv(cache: &Cache) -> Result<Self, Error> {
|
pub fn from_virtualenv(cache: &Cache) -> Result<Self, Error> {
|
||||||
let sources = SourceSelector::from_sources([
|
let sources = SourceSelector::VirtualEnv;
|
||||||
InterpreterSource::DiscoveredEnvironment,
|
|
||||||
InterpreterSource::ActiveEnvironment,
|
|
||||||
]);
|
|
||||||
let request = InterpreterRequest::Version(VersionRequest::Default);
|
let request = InterpreterRequest::Version(VersionRequest::Default);
|
||||||
let found = find_interpreter(&request, SystemPython::Disallowed, &sources, cache)??;
|
let found = find_interpreter(&request, SystemPython::Disallowed, &sources, cache)??;
|
||||||
|
|
||||||
|
@ -109,7 +106,7 @@ impl PythonEnvironment {
|
||||||
system: SystemPython,
|
system: SystemPython,
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let sources = SourceSelector::from_env(system);
|
let sources = SourceSelector::from_settings(system);
|
||||||
let request = InterpreterRequest::parse(request);
|
let request = InterpreterRequest::parse(request);
|
||||||
let interpreter = find_interpreter(&request, system, &sources, cache)??.into_interpreter();
|
let interpreter = find_interpreter(&request, system, &sources, cache)??.into_interpreter();
|
||||||
Ok(Self(Arc::new(PythonEnvironmentShared {
|
Ok(Self(Arc::new(PythonEnvironmentShared {
|
||||||
|
|
|
@ -700,12 +700,7 @@ mod tests {
|
||||||
fn find_interpreter_version_minor() -> Result<()> {
|
fn find_interpreter_version_minor() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let sources = SourceSelector::from_sources([
|
let sources = SourceSelector::All;
|
||||||
InterpreterSource::ProvidedPath,
|
|
||||||
InterpreterSource::ActiveEnvironment,
|
|
||||||
InterpreterSource::DiscoveredEnvironment,
|
|
||||||
InterpreterSource::SearchPath,
|
|
||||||
]);
|
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
|
@ -757,12 +752,7 @@ mod tests {
|
||||||
fn find_interpreter_version_patch() -> Result<()> {
|
fn find_interpreter_version_patch() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let sources = SourceSelector::from_sources([
|
let sources = SourceSelector::All;
|
||||||
InterpreterSource::ProvidedPath,
|
|
||||||
InterpreterSource::ActiveEnvironment,
|
|
||||||
InterpreterSource::DiscoveredEnvironment,
|
|
||||||
InterpreterSource::SearchPath,
|
|
||||||
]);
|
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
|
@ -814,12 +804,7 @@ mod tests {
|
||||||
fn find_interpreter_version_minor_no_match() -> Result<()> {
|
fn find_interpreter_version_minor_no_match() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let sources = SourceSelector::from_sources([
|
let sources = SourceSelector::All;
|
||||||
InterpreterSource::ProvidedPath,
|
|
||||||
InterpreterSource::ActiveEnvironment,
|
|
||||||
InterpreterSource::DiscoveredEnvironment,
|
|
||||||
InterpreterSource::SearchPath,
|
|
||||||
]);
|
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
|
@ -861,12 +846,7 @@ mod tests {
|
||||||
fn find_interpreter_version_patch_no_match() -> Result<()> {
|
fn find_interpreter_version_patch_no_match() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let sources = SourceSelector::from_sources([
|
let sources = SourceSelector::All;
|
||||||
InterpreterSource::ProvidedPath,
|
|
||||||
InterpreterSource::ActiveEnvironment,
|
|
||||||
InterpreterSource::DiscoveredEnvironment,
|
|
||||||
InterpreterSource::SearchPath,
|
|
||||||
]);
|
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
|
|
|
@ -170,7 +170,7 @@ pub(crate) async fn pip_compile(
|
||||||
};
|
};
|
||||||
let interpreter = if let Some(python) = python.as_ref() {
|
let interpreter = if let Some(python) = python.as_ref() {
|
||||||
let request = InterpreterRequest::parse(python);
|
let request = InterpreterRequest::parse(python);
|
||||||
let sources = SourceSelector::from_env(system);
|
let sources = SourceSelector::from_settings(system);
|
||||||
find_interpreter(&request, system, &sources, &cache)??
|
find_interpreter(&request, system, &sources, &cache)??
|
||||||
} else {
|
} else {
|
||||||
let request = if let Some(version) = python_version.as_ref() {
|
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 interpreter = if let Some(python) = python_request.as_ref() {
|
||||||
let system = uv_interpreter::SystemPython::Required;
|
let system = uv_interpreter::SystemPython::Required;
|
||||||
let request = InterpreterRequest::parse(python);
|
let request = InterpreterRequest::parse(python);
|
||||||
let sources = SourceSelector::from_env(system);
|
let sources = SourceSelector::from_settings(system);
|
||||||
find_interpreter(&request, system, &sources, cache)
|
find_interpreter(&request, system, &sources, cache)
|
||||||
} else {
|
} else {
|
||||||
find_default_interpreter(cache)
|
find_default_interpreter(cache)
|
||||||
|
|
|
@ -218,20 +218,33 @@ fn create_venv_unknown_python_minor() {
|
||||||
let mut command = context.venv_command();
|
let mut command = context.venv_command();
|
||||||
command
|
command
|
||||||
.arg(context.venv.as_os_str())
|
.arg(context.venv.as_os_str())
|
||||||
|
// Request a version we know we'll never see
|
||||||
.arg("--python")
|
.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
|
if cfg!(windows) {
|
||||||
// `UV_TEST_PYTHON_PATH` being set
|
uv_snapshot!(&mut command, @r###"
|
||||||
uv_snapshot!(&mut command, @r###"
|
success: false
|
||||||
success: false
|
exit_code: 1
|
||||||
exit_code: 1
|
----- stdout -----
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
----- 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());
|
context.venv.assert(predicates::path::missing());
|
||||||
}
|
}
|
||||||
|
@ -240,18 +253,36 @@ fn create_venv_unknown_python_minor() {
|
||||||
fn create_venv_unknown_python_patch() {
|
fn create_venv_unknown_python_patch() {
|
||||||
let context = VenvTestContext::new(&["3.12"]);
|
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())
|
.arg(context.venv.as_os_str())
|
||||||
|
// Request a version we know we'll never see
|
||||||
.arg("--python")
|
.arg("--python")
|
||||||
.arg("3.8.0"), @r###"
|
.arg("3.12.100")
|
||||||
success: false
|
// Unset this variable to force what the user would see
|
||||||
exit_code: 1
|
.env_remove("UV_TEST_PYTHON_PATH");
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
if cfg!(windows) {
|
||||||
× No interpreter found for Python 3.8.0 in active virtual environment or search path
|
uv_snapshot!(&mut command, @r###"
|
||||||
"###
|
success: false
|
||||||
);
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
× 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());
|
context.venv.assert(predicates::path::missing());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue