Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Python discovery source messages #8890

Merged
merged 1 commit into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a later pull request, I'll consolidate methods to take this type instead of the separate preferences.

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
Loading