Skip to content

Commit

Permalink
Improve Python discovery source messages (#8890)
Browse files Browse the repository at this point in the history
e.g.

```
❯ echo "anyio" |  cargo run -q -- pip compile - -v
DEBUG uv 0.4.30 (107ab3d 2024-11-07)
DEBUG Starting Python discovery for a default Python
DEBUG Looking for exact match for request a default Python
DEBUG Searching for default Python interpreter in virtual environments, managed installations, or search path
DEBUG Found `cpython-3.12.7-macos-aarch64-none` at `/Users/zb/workspace/uv/.venv/bin/python3` (virtual environment)
```
```
❯ cargo run -q -- pip install anyio -v
DEBUG uv 0.4.30 (107ab3d 2024-11-07)
DEBUG Searching for default Python interpreter in virtual environments
DEBUG Found `cpython-3.12.7-macos-aarch64-none` at `/Users/zb/workspace/uv/.venv/bin/python3` (virtual environment)
```

vs

```
❯ uv  pip install anyio -v
DEBUG uv 0.4.30 (61ed2a2 2024-11-04)
DEBUG Searching for default Python interpreter in system path
DEBUG Found `cpython-3.12.7-macos-aarch64-none` at `/Users/zb/workspace/uv/.venv/bin/python3` (virtual environment)
```

```
❯ echo "anyio" | uv pip compile - -v
DEBUG uv 0.4.30 (61ed2a2 2024-11-04)
DEBUG Starting Python discovery for a default Python
DEBUG Looking for exact match for request a default Python
DEBUG Searching for default Python interpreter in managed installations or system path
DEBUG Found `cpython-3.12.7-macos-aarch64-none` at `/Users/zb/workspace/uv/.venv/bin/python3` (virtual environment)
```
  • Loading branch information
zanieb authored Nov 7, 2024
1 parent b4fa46b commit 88331e7
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 48 deletions.
2 changes: 1 addition & 1 deletion crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ pub enum Commands {
/// interpreters are found by searching for Python executables in the `PATH` environment
/// variable.
///
/// On Windows, the `py` launcher is also invoked to find Python executables.
/// On Windows, the registry is also searched for Python executables.
///
/// By default, uv will download Python if a version cannot be found. This behavior can be
/// disabled with the `--no-python-downloads` flag or the `python-downloads` setting.
Expand Down
98 changes: 74 additions & 24 deletions crates/uv-python/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ pub enum EnvironmentPreference {
Any,
}

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub(crate) struct DiscoveryPreferences {
python_preference: PythonPreference,
environment_preference: EnvironmentPreference,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PythonVariant {
#[default]
Expand Down Expand Up @@ -276,7 +282,7 @@ fn python_executables_from_environments<'a>(
///
/// - Managed Python installations (e.g. `uv python install`)
/// - The search path (i.e. `PATH`)
/// - The `py` launcher (Windows only)
/// - The registry (Windows only)
///
/// The ordering and presence of each source is determined by the [`PythonPreference`].
///
Expand Down Expand Up @@ -753,6 +759,12 @@ pub fn find_python_installations<'a>(
preference: PythonPreference,
cache: &'a Cache,
) -> Box<dyn Iterator<Item = Result<FindPythonResult, Error>> + 'a> {
let sources = DiscoveryPreferences {
python_preference: preference,
environment_preference: environments,
}
.sources(request);

match request {
PythonRequest::File(path) => Box::new(std::iter::once({
if preference.allows(PythonSource::ProvidedPath) {
Expand Down Expand Up @@ -831,7 +843,7 @@ pub fn find_python_installations<'a>(
}
}
PythonRequest::Any => Box::new({
debug!("Searching for any Python interpreter in {preference}");
debug!("Searching for any Python interpreter in {sources}");
python_interpreters(&VersionRequest::Any, None, environments, preference, cache).map(
|result| {
result
Expand All @@ -841,7 +853,7 @@ pub fn find_python_installations<'a>(
)
}),
PythonRequest::Default => Box::new({
debug!("Searching for default Python interpreter in {preference}");
debug!("Searching for default Python interpreter in {sources}");
python_interpreters(
&VersionRequest::Default,
None,
Expand All @@ -860,7 +872,7 @@ pub fn find_python_installations<'a>(
return Box::new(std::iter::once(Err(Error::InvalidVersionRequest(err))));
};
Box::new({
debug!("Searching for {request} in {preference}");
debug!("Searching for {request} in {sources}");
python_interpreters(version, None, environments, preference, cache).map(|result| {
result
.map(PythonInstallation::from_tuple)
Expand All @@ -869,7 +881,7 @@ pub fn find_python_installations<'a>(
})
}
PythonRequest::Implementation(implementation) => Box::new({
debug!("Searching for a {request} interpreter in {preference}");
debug!("Searching for a {request} interpreter in {sources}");
python_interpreters(
&VersionRequest::Default,
Some(implementation),
Expand All @@ -894,7 +906,7 @@ pub fn find_python_installations<'a>(
return Box::new(std::iter::once(Err(Error::InvalidVersionRequest(err))));
};
Box::new({
debug!("Searching for {request} in {preference}");
debug!("Searching for {request} in {sources}");
python_interpreters(
version,
Some(implementation),
Expand Down Expand Up @@ -922,7 +934,7 @@ pub fn find_python_installations<'a>(
};
};
Box::new({
debug!("Searching for {request} in {preference}");
debug!("Searching for {request} in {sources}");
python_interpreters(
request.version().unwrap_or(&VersionRequest::Default),
request.implementation(),
Expand Down Expand Up @@ -2256,22 +2268,27 @@ impl fmt::Display for PythonSource {
}

impl PythonPreference {
/// Return the sources that are considered when searching for a Python interpreter.
fn sources(self) -> &'static [&'static str] {
/// Return the sources that are considered when searching for a Python interpreter with this
/// preference.
fn sources(self) -> &'static [PythonSource] {
match self {
Self::OnlyManaged => &["managed installations"],
Self::OnlyManaged => &[PythonSource::Managed],
Self::Managed | Self::System => {
if cfg!(windows) {
&["managed installations", "system path", "`py` launcher"]
&[
PythonSource::Managed,
PythonSource::SearchPath,
PythonSource::Registry,
]
} else {
&["managed installations", "system path"]
&[PythonSource::Managed, PythonSource::SearchPath]
}
}
Self::OnlySystem => {
if cfg!(windows) {
&["system path", "`py` launcher"]
&[PythonSource::Registry, PythonSource::SearchPath]
} else {
&["system path"]
&[PythonSource::SearchPath]
}
}
}
Expand All @@ -2280,29 +2297,62 @@ impl PythonPreference {

impl fmt::Display for PythonPreference {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", conjunction(self.sources()))
f.write_str(match self {
Self::OnlyManaged => "only managed",
Self::Managed => "prefer managed",
Self::System => "prefer system",
Self::OnlySystem => "only system",
})
}
}

impl fmt::Display for PythonNotFound {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let sources = match self.environment_preference {
impl DiscoveryPreferences {
/// Return a string describing the sources that are considered when searching for Python with
/// the given preferences.
fn sources(&self, request: &PythonRequest) -> String {
let python_sources = self
.python_preference
.sources()
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>();
match self.environment_preference {
EnvironmentPreference::Any => conjunction(
&["virtual environments"]
.into_iter()
.chain(self.python_preference.sources().iter().copied())
.chain(python_sources.iter().map(String::as_str))
.collect::<Vec<_>>(),
),
EnvironmentPreference::ExplicitSystem => {
if self.request.is_explicit_system() {
conjunction(&["virtual", "system environment"])
if request.is_explicit_system() {
conjunction(
&["virtual environments"]
.into_iter()
.chain(python_sources.iter().map(String::as_str))
.collect::<Vec<_>>(),
)
} else {
conjunction(&["virtual environment"])
conjunction(&["virtual environments"])
}
}
EnvironmentPreference::OnlySystem => conjunction(self.python_preference.sources()),
EnvironmentPreference::OnlySystem => conjunction(
&python_sources
.iter()
.map(String::as_str)
.collect::<Vec<_>>(),
),
EnvironmentPreference::OnlyVirtual => conjunction(&["virtual environments"]),
};
}
}
}

impl fmt::Display for PythonNotFound {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let sources = DiscoveryPreferences {
python_preference: self.python_preference,
environment_preference: self.environment_preference,
}
.sources(&self.request);

match self.request {
PythonRequest::Default | PythonRequest::Any => {
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/tests/it/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,11 @@ impl TestContext {
#[must_use]
pub fn with_filtered_python_sources(mut self) -> Self {
self.filters.push((
"managed installations or system path".to_string(),
"managed installations or search path".to_string(),
"[PYTHON SOURCES]".to_string(),
));
self.filters.push((
"managed installations, system path, or `py` launcher".to_string(),
"managed installations, search path, or registry".to_string(),
"[PYTHON SOURCES]".to_string(),
));
self
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/tests/it/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ fn help_subcommand() {
interpreters are found by searching for Python executables in the `PATH` environment
variable.
On Windows, the `py` launcher is also invoked to find Python executables.
On Windows, the registry is also searched for Python executables.
By default, uv will download Python if a version cannot be found. This behavior can be
disabled with the `--no-python-downloads` flag or the `python-downloads` setting.
Expand Down
12 changes: 6 additions & 6 deletions crates/uv/tests/it/python_find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fn python_find() {
----- stdout -----
----- stderr -----
error: No interpreter found in virtual environments, managed installations, system path, or `py` launcher
error: No interpreter found in virtual environments, managed installations, search path, or registry
"###);
} else {
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, ""), @r###"
Expand All @@ -29,7 +29,7 @@ fn python_find() {
----- stdout -----
----- stderr -----
error: No interpreter found in virtual environments, managed installations, or system path
error: No interpreter found in virtual environments, managed installations, or search path
"###);
}

Expand Down Expand Up @@ -116,7 +116,7 @@ fn python_find() {
----- stdout -----
----- stderr -----
error: No interpreter found for PyPy in virtual environments, managed installations, system path, or `py` launcher
error: No interpreter found for PyPy in virtual environments, managed installations, search path, or registry
"###);
} else {
uv_snapshot!(context.filters(), context.python_find().arg("pypy"), @r###"
Expand All @@ -125,7 +125,7 @@ fn python_find() {
----- stdout -----
----- stderr -----
error: No interpreter found for PyPy in virtual environments, managed installations, or system path
error: No interpreter found for PyPy in virtual environments, managed installations, or search path
"###);
}

Expand Down Expand Up @@ -441,7 +441,7 @@ fn python_find_unsupported_version() {
----- stdout -----
----- stderr -----
error: No interpreter found for Python 4.2 in virtual environments, managed installations, or system path
error: No interpreter found for Python 4.2 in virtual environments, managed installations, or search path
"###);

// Request a low version with a range
Expand All @@ -451,7 +451,7 @@ fn python_find_unsupported_version() {
----- stdout -----
----- stderr -----
error: No interpreter found for Python <3.0 in virtual environments, managed installations, or system path
error: No interpreter found for Python <3.0 in virtual environments, managed installations, or search path
"###);

// Request free-threaded Python on unsupported version
Expand Down
18 changes: 9 additions & 9 deletions crates/uv/tests/it/python_pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ fn python_pin() {
Updated `.python-version` from `[PYTHON-3.11]` -> `pypy`
----- stderr -----
warning: No interpreter found for PyPy in managed installations or system path
warning: No interpreter found for PyPy in managed installations or search path
"###);

let python_version = context.read(PYTHON_VERSION_FILENAME);
Expand All @@ -188,7 +188,7 @@ fn python_pin() {
Updated `.python-version` from `pypy` -> `3.7`
----- stderr -----
warning: No interpreter found for Python 3.7 in managed installations or system path
warning: No interpreter found for Python 3.7 in managed installations or search path
"###);

let python_version = context.read(PYTHON_VERSION_FILENAME);
Expand All @@ -212,7 +212,7 @@ fn python_pin_no_python() {
Pinned `.python-version` to `3.12`
----- stderr -----
warning: No interpreter found for Python 3.12 in managed installations or system path
warning: No interpreter found for Python 3.12 in managed installations or search path
"###);
}

Expand Down Expand Up @@ -411,7 +411,7 @@ fn warning_pinned_python_version_not_installed() -> anyhow::Result<()> {
3.12
----- stderr -----
warning: Failed to resolve pinned Python version `3.12`: No interpreter found for Python 3.12 in managed installations, system path, or `py` launcher
warning: Failed to resolve pinned Python version `3.12`: No interpreter found for Python 3.12 in managed installations, search path, or registry
"###);
} else {
uv_snapshot!(context.filters(), context.python_pin(), @r###"
Expand All @@ -421,7 +421,7 @@ fn warning_pinned_python_version_not_installed() -> anyhow::Result<()> {
3.12
----- stderr -----
warning: Failed to resolve pinned Python version `3.12`: No interpreter found for Python 3.12 in managed installations or system path
warning: Failed to resolve pinned Python version `3.12`: No interpreter found for Python 3.12 in managed installations or search path
"###);
}

Expand All @@ -440,7 +440,7 @@ fn python_pin_resolve_no_python() {
----- stdout -----
----- stderr -----
error: No interpreter found for Python 3.12 in managed installations, system path, or `py` launcher
error: No interpreter found for Python 3.12 in managed installations, search path, or registry
"###);
} else {
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.12"), @r###"
Expand All @@ -449,7 +449,7 @@ fn python_pin_resolve_no_python() {
----- stdout -----
----- stderr -----
error: No interpreter found for Python 3.12 in managed installations or system path
error: No interpreter found for Python 3.12 in managed installations or search path
"###);
}
}
Expand Down Expand Up @@ -605,7 +605,7 @@ fn python_pin_resolve() {
----- stdout -----
----- stderr -----
error: No interpreter found for PyPy in managed installations or system path
error: No interpreter found for PyPy in managed installations or search path
"###);

let python_version = context.read(PYTHON_VERSION_FILENAME);
Expand All @@ -626,7 +626,7 @@ fn python_pin_resolve() {
----- stdout -----
----- stderr -----
error: No interpreter found for Python 3.7 in managed installations or system path
error: No interpreter found for Python 3.7 in managed installations or search path
"###);

let python_version = context.read(PYTHON_VERSION_FILENAME);
Expand Down
8 changes: 4 additions & 4 deletions crates/uv/tests/it/venv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ fn create_venv_unknown_python_minor() {
----- stdout -----
----- stderr -----
× No interpreter found for Python 3.100 in managed installations, system path, or `py` launcher
× No interpreter found for Python 3.100 in managed installations, search path, or registry
"###
);
} else {
Expand All @@ -615,7 +615,7 @@ fn create_venv_unknown_python_minor() {
----- stdout -----
----- stderr -----
× No interpreter found for Python 3.100 in managed installations or system path
× No interpreter found for Python 3.100 in managed installations or search path
"###
);
}
Expand Down Expand Up @@ -643,7 +643,7 @@ fn create_venv_unknown_python_patch() {
----- stdout -----
----- stderr -----
× No interpreter found for Python 3.12.100 in managed installations, system path, or `py` launcher
× No interpreter found for Python 3.12.100 in managed installations, search path, or registry
"###
);
} else {
Expand All @@ -653,7 +653,7 @@ fn create_venv_unknown_python_patch() {
----- stdout -----
----- stderr -----
× No interpreter found for Python 3.12.100 in managed installations or system path
× No interpreter found for Python 3.12.100 in managed installations or search path
"###
);
}
Expand Down
Loading

0 comments on commit 88331e7

Please sign in to comment.