mirror of https://github.com/astral-sh/uv
Respect `.python-version` files and `pyproject.toml` in `uv python find` (#6369)
I was surprised to find we didn't do this — we should find Python versions as we do everywhere else.
This commit is contained in:
parent
9a14e028df
commit
7140cdec79
|
|
@ -3029,6 +3029,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)]
|
||||
|
|
|
|||
|
|
@ -52,11 +52,11 @@ pub(crate) async fn run(
|
|||
isolated: bool,
|
||||
package: Option<PackageName>,
|
||||
no_project: bool,
|
||||
no_config: bool,
|
||||
extras: ExtrasSpecification,
|
||||
dev: bool,
|
||||
python: Option<String>,
|
||||
settings: ResolverInstallerSettings,
|
||||
|
||||
python_preference: PythonPreference,
|
||||
python_downloads: PythonDownloads,
|
||||
connectivity: Connectivity,
|
||||
|
|
@ -449,7 +449,9 @@ pub(crate) async fn run(
|
|||
Some(PythonRequest::parse(request))
|
||||
// (2) Request from `.python-version`
|
||||
} else {
|
||||
request_from_version_file(&CWD).await?
|
||||
PythonVersionFile::discover(&*CWD, no_config)
|
||||
.await?
|
||||
.and_then(PythonVersionFile::into_version)
|
||||
};
|
||||
|
||||
let python = PythonInstallation::find_or_download(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -689,7 +689,13 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
}
|
||||
Commands::Project(project) => {
|
||||
Box::pin(run_project(
|
||||
project, script, globals, filesystem, cache, printer,
|
||||
project,
|
||||
script,
|
||||
globals,
|
||||
cli.no_config,
|
||||
filesystem,
|
||||
cache,
|
||||
printer,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
|
@ -916,7 +922,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),
|
||||
|
|
@ -951,6 +964,8 @@ async fn run_project(
|
|||
project_command: Box<ProjectCommand>,
|
||||
script: Option<Pep723Script>,
|
||||
globals: GlobalSettings,
|
||||
// TODO(zanieb): Determine a better story for passing `no_config` in here
|
||||
no_config: bool,
|
||||
filesystem: Option<FilesystemOptions>,
|
||||
cache: Cache,
|
||||
printer: Printer,
|
||||
|
|
@ -1033,6 +1048,7 @@ async fn run_project(
|
|||
args.isolated,
|
||||
args.package,
|
||||
args.no_project,
|
||||
no_config,
|
||||
args.extras,
|
||||
args.dev,
|
||||
args.python,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
@ -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 -----
|
||||
"###);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3199,6 +3199,10 @@ uv python find [OPTIONS] [REQUEST]
|
|||
|
||||
<p>For example, spinners or progress bars.</p>
|
||||
|
||||
</dd><dt><code>--no-project</code></dt><dd><p>Avoid discovering a project or workspace.</p>
|
||||
|
||||
<p>Otherwise, when no request is provided, the Python requirement of a project in the current directory or parent directories will be used.</p>
|
||||
|
||||
</dd><dt><code>--no-python-downloads</code></dt><dd><p>Disable automatic downloads of Python</p>
|
||||
|
||||
</dd><dt><code>--offline</code></dt><dd><p>Disable network access.</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue