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

Include virtual environment interpreters in uv python find #6521

Merged
merged 3 commits into from
Aug 23, 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
19 changes: 19 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3034,6 +3034,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)]
Expand Down
14 changes: 12 additions & 2 deletions crates/uv/src/commands/python/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down Expand Up @@ -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)
}
1 change: 1 addition & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
args.request,
args.no_project,
cli.no_config,
args.system,
globals.python_preference,
&cache,
)
Expand Down
4 changes: 4 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ impl PythonUninstallSettings {
pub(crate) struct PythonFindSettings {
pub(crate) request: Option<String>,
pub(crate) no_project: bool,
pub(crate) system: bool,
}

impl PythonFindSettings {
Expand All @@ -575,11 +576,14 @@ impl PythonFindSettings {
let PythonFindArgs {
request,
no_project,
system,
no_system,
} = args;

Self {
request,
no_project,
system: flag(system, no_system).unwrap_or_default(),
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/uv/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
157 changes: 152 additions & 5 deletions crates/uv/tests/python_find.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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###"
Expand All @@ -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
"###);
}

Expand Down Expand Up @@ -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###"
Expand All @@ -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
"###);
}

Expand Down Expand Up @@ -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 -----
"###);
}
6 changes: 6 additions & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -3221,6 +3221,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. (&lt;https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives&gt;)</p>
Expand Down
Loading