Skip to content

Commit

Permalink
Respect .python-version files and pyproject.toml in uv python find
Browse files Browse the repository at this point in the history
  • Loading branch information
zanieb committed Aug 21, 2024
1 parent d2053f4 commit 4733b14
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 11 deletions.
7 changes: 7 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3031,6 +3031,13 @@ pub struct PythonFindArgs {
///
/// See `uv help python` to view supported request formats.
pub request: Option<String>,

/// Avoid discovering a project or workspace.
///
/// Otherwise, when no request is provided, the Python requirement of a project in the current
/// directory or parent directories will be used.
#[arg(long, alias = "no_workspace")]
pub no_project: bool,
}

#[derive(Args)]
Expand Down
53 changes: 45 additions & 8 deletions crates/uv/src/commands/python/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,60 @@ use anstream::println;
use anyhow::Result;

use uv_cache::Cache;
use uv_fs::Simplified;
use uv_python::{EnvironmentPreference, PythonInstallation, PythonPreference, PythonRequest};
use uv_fs::{Simplified, CWD};
use uv_python::{
EnvironmentPreference, PythonInstallation, PythonPreference, PythonRequest, PythonVersionFile,
VersionRequest,
};
use uv_resolver::RequiresPython;
use uv_warnings::warn_user_once;
use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceError};

use crate::commands::ExitStatus;
use crate::commands::{project::find_requires_python, ExitStatus};

/// Find a Python interpreter.
pub(crate) async fn find(
request: Option<String>,
no_project: bool,
no_config: bool,
python_preference: PythonPreference,
cache: &Cache,
) -> Result<ExitStatus> {
let request = match request {
Some(request) => PythonRequest::parse(&request),
None => PythonRequest::Any,
};
// (1) Explicit request from user
let mut request = request.map(|request| PythonRequest::parse(&request));

// (2) Request from `.python-version`
if request.is_none() {
request = PythonVersionFile::discover(&*CWD, no_config)
.await?
.and_then(PythonVersionFile::into_version);
}

// (3) `Requires-Python` in `pyproject.toml`
if request.is_none() && !no_project {
let project = match VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await {
Ok(project) => Some(project),
Err(WorkspaceError::MissingProject(_)) => None,
Err(WorkspaceError::MissingPyprojectToml) => None,
Err(WorkspaceError::NonWorkspace(_)) => None,
Err(err) => {
warn_user_once!("{err}");
None
}
};

if let Some(project) = project {
request = find_requires_python(project.workspace())?
.as_ref()
.map(RequiresPython::specifiers)
.map(|specifiers| {
PythonRequest::Version(VersionRequest::Range(specifiers.clone()))
});
}
}

let python = PythonInstallation::find(
&request,
&request.unwrap_or_default(),
EnvironmentPreference::OnlySystem,
python_preference,
cache,
Expand Down
9 changes: 8 additions & 1 deletion crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,14 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
// Initialize the cache.
let cache = cache.init()?;

commands::python_find(args.request, globals.python_preference, &cache).await
commands::python_find(
args.request,
args.no_project,
cli.no_config,
globals.python_preference,
&cache,
)
.await
}
Commands::Python(PythonNamespace {
command: PythonCommand::Pin(args),
Expand Down
11 changes: 9 additions & 2 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,15 +564,22 @@ impl PythonUninstallSettings {
#[derive(Debug, Clone)]
pub(crate) struct PythonFindSettings {
pub(crate) request: Option<String>,
pub(crate) no_project: bool,
}

impl PythonFindSettings {
/// Resolve the [`PythonFindSettings`] from the CLI and workspace configuration.
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn resolve(args: PythonFindArgs, _filesystem: Option<FilesystemOptions>) -> Self {
let PythonFindArgs { request } = args;
let PythonFindArgs {
request,
no_project,
} = args;

Self { request }
Self {
request,
no_project,
}
}
}

Expand Down
95 changes: 95 additions & 0 deletions crates/uv/tests/python_find.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#![cfg(all(feature = "python", feature = "pypi"))]

use assert_fs::fixture::FileWriteStr;
use assert_fs::prelude::PathChild;
use indoc::indoc;

use common::{uv_snapshot, TestContext};
use uv_python::platform::{Arch, Os};

Expand Down Expand Up @@ -148,3 +152,94 @@ fn python_find() {
----- stderr -----
"###);
}

#[test]
fn python_find_pin() {
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]);

// Pin to a version
uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @r###"
success: true
exit_code: 0
----- stdout -----
Pinned `.python-version` to `3.12`
----- stderr -----
"###);

// We should find the pinned version, not the first on the path
uv_snapshot!(context.filters(), context.python_find(), @r###"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
"###);

// Unless explicitly requested
uv_snapshot!(context.filters(), context.python_find().arg("3.11"), @r###"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
"###);

// Or `--no-config` is used
uv_snapshot!(context.filters(), context.python_find().arg("--no-config"), @r###"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
"###);
}

#[test]
fn python_find_project() {
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]);

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml
.write_str(indoc! {r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
"#})
.unwrap();

// We should respect the project's required version, not the first on the path
uv_snapshot!(context.filters(), context.python_find(), @r###"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]
----- stderr -----
"###);

// Unless explicitly requested
uv_snapshot!(context.filters(), context.python_find().arg("3.11"), @r###"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
"###);

// Or `--no-project` is used
uv_snapshot!(context.filters(), context.python_find().arg("--no-project"), @r###"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]
----- stderr -----
"###);
}

0 comments on commit 4733b14

Please sign in to comment.