diff --git a/crates/uv-scripts/src/lib.rs b/crates/uv-scripts/src/lib.rs index 46263eb1d..ca74c834a 100644 --- a/crates/uv-scripts/src/lib.rs +++ b/crates/uv-scripts/src/lib.rs @@ -14,7 +14,7 @@ static FINDER: LazyLock = LazyLock::new(|| Finder::new(b"# /// script")) #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Pep723Metadata { - pub dependencies: Vec>, + pub dependencies: Option>>, pub requires_python: Option, } diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 6a41d9e53..0da9643e0 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -92,6 +92,7 @@ pub(crate) async fn run( let reporter = PythonDownloadReporter::single(printer.filter(show_resolution)); // Determine whether the command to execute is a PEP 723 script. + let temp_dir; let script_interpreter = if let RunCommand::Python(target, _) = &command { if let Some(metadata) = uv_scripts::read_pep723_metadata(&target).await? { writeln!( @@ -129,28 +130,39 @@ pub(crate) async fn run( .await? .into_interpreter(); - // Install the script requirements. - let requirements = metadata - .dependencies - .into_iter() - .map(Requirement::from) - .collect(); - let spec = RequirementsSpecification::from_requirements(requirements); - let environment = CachedEnvironment::get_or_create( - spec, - interpreter, - &settings, - &state, - preview, - connectivity, - concurrency, - native_tls, - cache, - printer.filter(show_resolution), - ) - .await?; + // Install the script requirements, if necessary. Otherwise, use an isolated environment. + if let Some(dependencies) = metadata.dependencies { + let requirements = dependencies.into_iter().map(Requirement::from).collect(); + let spec = RequirementsSpecification::from_requirements(requirements); + let environment = CachedEnvironment::get_or_create( + spec, + interpreter, + &settings, + &state, + preview, + connectivity, + concurrency, + native_tls, + cache, + printer.filter(show_resolution), + ) + .await?; - Some(environment.into_interpreter()) + Some(environment.into_interpreter()) + } else { + // Create a virtual environment + temp_dir = cache.environment()?; + let environment = uv_virtualenv::create_venv( + temp_dir.path(), + interpreter, + uv_virtualenv::Prompt::None, + false, + false, + false, + )?; + + Some(environment.into_interpreter()) + } } else { None } diff --git a/crates/uv/tests/run.rs b/crates/uv/tests/run.rs index b3907fd4e..e4ce07b22 100644 --- a/crates/uv/tests/run.rs +++ b/crates/uv/tests/run.rs @@ -199,7 +199,7 @@ fn run_args() -> Result<()> { /// Run a PEP 723-compatible script. The script should take precedence over the workspace /// dependencies. #[test] -fn run_script() -> Result<()> { +fn run_pep723_script() -> Result<()> { let context = TestContext::new("3.12"); let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -298,6 +298,28 @@ fn run_script() -> Result<()> { Audited 4 packages in [TIME] "###); + // If the script contains a PEP 723 tag, it can omit the dependencies field. + let test_script = context.temp_dir.child("main.py"); + test_script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.11" + # /// + + print("Hello, world!") + "# + })?; + + // Running the script should install the requirements. + uv_snapshot!(context.filters(), context.run().arg("--preview").arg("main.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello, world! + + ----- stderr ----- + Reading inline script metadata from: main.py + "###); + Ok(()) }