From 5f8362b9fa070b546ab5db64fcdf5317c4adf857 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 30 Aug 2024 15:17:58 -0400 Subject: [PATCH] Warn when discovered Python is incompatible with PEP 723 script --- crates/uv/src/commands/project/run.rs | 24 ++++++++--- crates/uv/tests/run.rs | 62 +++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 8f8cb7185bc83..dce482401aeaa 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -25,7 +25,7 @@ use uv_python::{ }; use uv_requirements::{RequirementsSource, RequirementsSpecification}; use uv_scripts::Pep723Script; -use uv_warnings::warn_user_once; +use uv_warnings::{warn_user, warn_user_once}; use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace, WorkspaceError}; use crate::commands::pip::loggers::{ @@ -111,11 +111,15 @@ pub(crate) async fn run( .and_then(PythonVersionFile::into_version) { Some(request) - // (3) `Requires-Python` in `pyproject.toml` + // (3) `Requires-Python` in the script } else { - script.metadata.requires_python.map(|requires_python| { - PythonRequest::Version(VersionRequest::Range(requires_python)) - }) + script + .metadata + .requires_python + .as_ref() + .map(|requires_python| { + PythonRequest::Version(VersionRequest::Range(requires_python.clone())) + }) }; let client_builder = BaseClientBuilder::new() @@ -134,6 +138,16 @@ pub(crate) async fn run( .await? .into_interpreter(); + if let Some(requires_python) = script.metadata.requires_python.as_ref() { + if !requires_python.contains(interpreter.python_version()) { + warn_user!( + "Python {} does not satisfy the script's `requires-python` specifier: `{}`", + interpreter.python_version(), + requires_python + ); + } + } + // Install the script requirements, if necessary. Otherwise, use an isolated environment. if let Some(dependencies) = script.metadata.dependencies { // // Collect any `tool.uv.sources` from the script. diff --git a/crates/uv/tests/run.rs b/crates/uv/tests/run.rs index ffa5261acfca0..ee0d30f10a789 100644 --- a/crates/uv/tests/run.rs +++ b/crates/uv/tests/run.rs @@ -386,6 +386,68 @@ fn run_pep723_script() -> Result<()> { Ok(()) } +#[test] +fn run_pep723_script_requires_python() -> Result<()> { + let context = TestContext::new_with_versions(&["3.8", "3.11"]); + + // If we have a `.python-version` that's incompatible with the script, we should error. + let python_version = context.temp_dir.child(PYTHON_VERSION_FILENAME); + python_version.write_str("3.8")?; + + // If the script contains a PEP 723 tag, we should install its requirements. + let test_script = context.temp_dir.child("main.py"); + test_script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "iniconfig", + # ] + # /// + + import iniconfig + + x: str | int = "hello" + print(x) + "# + })?; + + uv_snapshot!(context.filters(), context.run().arg("main.py"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Reading inline script metadata from: main.py + warning: Python 3.8.[X] does not satisfy the script's `requires-python` specifier: `>=3.11` + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + Traceback (most recent call last): + File "main.py", line 10, in + x: str | int = "hello" + TypeError: unsupported operand type(s) for |: 'type' and 'type' + "###); + + // Delete the `.python-version` file to allow the script to run. + fs_err::remove_file(&python_version)?; + + uv_snapshot!(context.filters(), context.run().arg("main.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + hello + + ----- stderr ----- + Reading inline script metadata from: main.py + Resolved 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "###); + + Ok(()) +} + /// Run a `.pyw` script. The script should be executed with `pythonw.exe`. #[test] #[cfg(windows)]