mirror of https://github.com/astral-sh/uv
Include virtual environment interpreters in `uv python find` (#6521)
Previously, we excluded these and only looked at system interpreters. However, it makes sense for this to match the typical Python discovery experience. We could consider swapping the default... I'm not sure what makes more sense. If we change the default (as written now) — this could arguably be a breaking change.
This commit is contained in:
parent
d1cbcb30e3
commit
6cf5d13183
|
|
@ -3063,6 +3063,25 @@ pub struct PythonFindArgs {
|
|||
/// directory or parent directories will be used.
|
||||
#[arg(long, alias = "no_workspace")]
|
||||
pub no_project: bool,
|
||||
|
||||
/// Only find system Python interpreters.
|
||||
///
|
||||
/// By default, uv will report the first Python interpreter it would use, including those in an
|
||||
/// active virtual environment or a virtual environment in the current working directory or any
|
||||
/// parent directory.
|
||||
///
|
||||
/// The `--system` option instructs uv to skip virtual environment Python interpreters and
|
||||
/// restrict its search to the system path.
|
||||
#[arg(
|
||||
long,
|
||||
env = "UV_SYSTEM_PYTHON",
|
||||
value_parser = clap::builder::BoolishValueParser::new(),
|
||||
overrides_with("no_system")
|
||||
)]
|
||||
pub system: bool,
|
||||
|
||||
#[arg(long, overrides_with("system"), hide = true)]
|
||||
pub no_system: bool,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
|
|||
|
|
@ -18,9 +18,16 @@ pub(crate) async fn find(
|
|||
request: Option<String>,
|
||||
no_project: bool,
|
||||
no_config: bool,
|
||||
system: bool,
|
||||
python_preference: PythonPreference,
|
||||
cache: &Cache,
|
||||
) -> Result<ExitStatus> {
|
||||
let environment_preference = if system {
|
||||
EnvironmentPreference::OnlySystem
|
||||
} else {
|
||||
EnvironmentPreference::Any
|
||||
};
|
||||
|
||||
// (1) Explicit request from user
|
||||
let mut request = request.map(|request| PythonRequest::parse(&request));
|
||||
|
||||
|
|
@ -56,12 +63,15 @@ pub(crate) async fn find(
|
|||
|
||||
let python = PythonInstallation::find(
|
||||
&request.unwrap_or_default(),
|
||||
EnvironmentPreference::OnlySystem,
|
||||
environment_preference,
|
||||
python_preference,
|
||||
cache,
|
||||
)?;
|
||||
|
||||
println!("{}", python.interpreter().sys_executable().user_display());
|
||||
println!(
|
||||
"{}",
|
||||
uv_fs::absolutize_path(python.interpreter().sys_executable())?.simplified_display()
|
||||
);
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -943,6 +943,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
args.request,
|
||||
args.no_project,
|
||||
cli.no_config,
|
||||
args.system,
|
||||
globals.python_preference,
|
||||
&cache,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -564,6 +564,7 @@ impl PythonUninstallSettings {
|
|||
pub(crate) struct PythonFindSettings {
|
||||
pub(crate) request: Option<String>,
|
||||
pub(crate) no_project: bool,
|
||||
pub(crate) system: bool,
|
||||
}
|
||||
|
||||
impl PythonFindSettings {
|
||||
|
|
@ -573,11 +574,14 @@ impl PythonFindSettings {
|
|||
let PythonFindArgs {
|
||||
request,
|
||||
no_project,
|
||||
system,
|
||||
no_system,
|
||||
} = args;
|
||||
|
||||
Self {
|
||||
request,
|
||||
no_project,
|
||||
system: flag(system, no_system).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,8 @@ impl TestContext {
|
|||
self
|
||||
}
|
||||
|
||||
/// Add extra standard filtering for Python executable names.
|
||||
/// Add extra standard filtering for Python executable names, e.g., stripping version number
|
||||
/// and `.exe` suffixes.
|
||||
#[must_use]
|
||||
pub fn with_filtered_python_names(mut self) -> Self {
|
||||
if cfg!(windows) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
#![cfg(all(feature = "python", feature = "pypi"))]
|
||||
|
||||
use assert_fs::fixture::FileWriteStr;
|
||||
use assert_fs::prelude::PathChild;
|
||||
use assert_fs::{fixture::FileWriteStr, prelude::PathCreateDir};
|
||||
use fs_err::remove_dir_all;
|
||||
use indoc::indoc;
|
||||
|
||||
use common::{uv_snapshot, TestContext};
|
||||
|
|
@ -21,7 +22,7 @@ fn python_find() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No interpreter found in system path or `py` launcher
|
||||
error: No interpreter found in virtual environments, system path, or `py` launcher
|
||||
"###);
|
||||
} else {
|
||||
uv_snapshot!(context.filters(), context.python_find().env("UV_TEST_PYTHON_PATH", ""), @r###"
|
||||
|
|
@ -30,7 +31,7 @@ fn python_find() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No interpreter found in system path
|
||||
error: No interpreter found in virtual environments or system path
|
||||
"###);
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +118,7 @@ fn python_find() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No interpreter found for PyPy in system path or `py` launcher
|
||||
error: No interpreter found for PyPy in virtual environments, system path, or `py` launcher
|
||||
"###);
|
||||
} else {
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("pypy"), @r###"
|
||||
|
|
@ -126,7 +127,7 @@ fn python_find() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No interpreter found for PyPy in system path
|
||||
error: No interpreter found for PyPy in virtual environments or system path
|
||||
"###);
|
||||
}
|
||||
|
||||
|
|
@ -243,3 +244,149 @@ fn python_find_project() {
|
|||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_find_venv() {
|
||||
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"])
|
||||
// Enable additional filters for Windows compatibility
|
||||
.with_filtered_exe_suffix()
|
||||
.with_filtered_python_names()
|
||||
.with_filtered_virtualenv_bin();
|
||||
|
||||
// Create a virtual environment
|
||||
uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.12").arg("-q"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// We should find it first
|
||||
// TODO(zanieb): On Windows, this has in a different display path for virtual environments which
|
||||
// is super annoying and requires some changes to how we represent working directories in the
|
||||
// test context to resolve.
|
||||
#[cfg(not(windows))]
|
||||
uv_snapshot!(context.filters(), context.python_find(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[VENV]/[BIN]/python
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Even if the `VIRTUAL_ENV` is not set (the test context includes this by default)
|
||||
#[cfg(not(windows))]
|
||||
uv_snapshot!(context.filters(), context.python_find().env_remove("VIRTUAL_ENV"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[VENV]/[BIN]/python
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let child_dir = context.temp_dir.child("child");
|
||||
child_dir.create_dir_all().unwrap();
|
||||
|
||||
// Unless the system flag is passed
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("--system"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-3.11]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Or, `UV_SYSTEM_PYTHON` is set
|
||||
uv_snapshot!(context.filters(), context.python_find().env("UV_SYSTEM_PYTHON", "1"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-3.11]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Unless, `--no-system` is included
|
||||
// TODO(zanieb): Report this as a bug upstream — this should be allowed.
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("--no-system").env("UV_SYSTEM_PYTHON", "1"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: the argument '--no-system' cannot be used with '--system'
|
||||
|
||||
Usage: uv python find --cache-dir [CACHE_DIR] [REQUEST]
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
|
||||
// We should find virtual environments from a child directory
|
||||
#[cfg(not(windows))]
|
||||
uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir).env_remove("VIRTUAL_ENV"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[VENV]/[BIN]/python
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// A virtual environment in the child directory takes precedence over the parent
|
||||
uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.11").arg("-q").current_dir(&child_dir), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
uv_snapshot!(context.filters(), context.python_find().current_dir(&child_dir).env_remove("VIRTUAL_ENV"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[TEMP_DIR]/child/.venv/[BIN]/python
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// But if we delete the parent virtual environment
|
||||
remove_dir_all(context.temp_dir.child(".venv")).unwrap();
|
||||
|
||||
// And query from there... we should not find the child virtual environment
|
||||
uv_snapshot!(context.filters(), context.python_find(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[PYTHON-3.11]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Unless, it is requested by path
|
||||
#[cfg(not(windows))]
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("child/.venv"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[TEMP_DIR]/child/.venv/[BIN]/python
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Or activated via `VIRTUAL_ENV`
|
||||
#[cfg(not(windows))]
|
||||
uv_snapshot!(context.filters(), context.python_find().env("VIRTUAL_ENV", child_dir.join(".venv").as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[TEMP_DIR]/child/.venv/[BIN]/python
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3233,6 +3233,12 @@ uv python find [OPTIONS] [REQUEST]
|
|||
</ul>
|
||||
</dd><dt><code>--quiet</code>, <code>-q</code></dt><dd><p>Do not print any output</p>
|
||||
|
||||
</dd><dt><code>--system</code></dt><dd><p>Only find system Python interpreters.</p>
|
||||
|
||||
<p>By default, uv will report the first Python interpreter it would use, including those in an active virtual environment or a virtual environment in the current working directory or any parent directory.</p>
|
||||
|
||||
<p>The <code>--system</code> option instructs uv to skip virtual environment Python interpreters and restrict its search to the system path.</p>
|
||||
|
||||
</dd><dt><code>--verbose</code>, <code>-v</code></dt><dd><p>Use verbose output.</p>
|
||||
|
||||
<p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue