From 9e4d0f70c7a829ac04f2bd687b5ce550d589b59f Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Thu, 26 Sep 2024 22:56:43 -0700 Subject: [PATCH 01/10] Initial commit --- crates/uv-cli/src/lib.rs | 3 +++ crates/uv/src/lib.rs | 19 ++++++++++++++++--- crates/uv/src/settings.rs | 1 + 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 2699a519009f..32f90aebf6cf 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2548,6 +2548,9 @@ pub struct RunArgs { #[arg(long, conflicts_with = "locked")] pub frozen: bool, + #[arg(long)] + pub script: bool, + #[command(flatten)] pub installer: ResolverInstallerArgs, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index d46fa74747a1..9a58fcf29bc5 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::ffi::OsString; use std::fmt::Write; use std::io::stdout; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::ExitCode; use anstream::eprintln; @@ -131,8 +131,21 @@ async fn run(cli: Cli) -> Result { // Parse the external command, if necessary. let run_command = if let Commands::Project(command) = &*cli.command { - if let ProjectCommand::Run(uv_cli::RunArgs { command, .. }) = &**command { - Some(RunCommand::try_from(command)?) + if let ProjectCommand::Run(uv_cli::RunArgs { + command, script, .. + }) = &**command + { + if *script { + let (target, args) = command.split(); + if let Some(target) = target { + let path = PathBuf::from(&target); + Some(RunCommand::PythonScript(path, args.to_vec())) + } else { + Some(RunCommand::Empty) + } + } else { + Some(RunCommand::try_from(command)?) + } } else { None } diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index bbb253c1ff8b..b9b9b26ae300 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -247,6 +247,7 @@ impl RunSettings { no_dev, only_dev, no_editable, + script: _, command: _, with, with_editable, From 65313522dfed35e0e81c0531f7aff4ab46d460b7 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Fri, 27 Sep 2024 06:09:53 -0700 Subject: [PATCH 02/10] Add test --- crates/uv/tests/run.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/uv/tests/run.rs b/crates/uv/tests/run.rs index ed6514913689..d6447c075793 100644 --- a/crates/uv/tests/run.rs +++ b/crates/uv/tests/run.rs @@ -2127,3 +2127,37 @@ fn run_script_without_build_system() -> Result<()> { Ok(()) } + +#[test] +fn run_script_explicit() -> Result<()> { + let context = TestContext::new("3.12"); + + let test_script = context.temp_dir.child("script"); + test_script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "iniconfig", + # ] + # /// + import iniconfig + print("Hello, world!") + "# + })?; + + uv_snapshot!(context.filters(), context.run().arg("--script").arg("script"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello, world! + + ----- stderr ----- + Reading inline script metadata from: script + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "###); + + Ok(()) +} From c2857a597f5d00d6e0e88b91ebfe7e1a9b05912d Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Fri, 27 Sep 2024 06:20:16 -0700 Subject: [PATCH 03/10] Add docs --- crates/uv-cli/src/lib.rs | 3 +++ docs/reference/cli.md | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 32f90aebf6cf..4794fb9c3433 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2548,6 +2548,9 @@ pub struct RunArgs { #[arg(long, conflicts_with = "locked")] pub frozen: bool, + /// Run the given path as a PEP 723 script. + /// + /// Parses the file as a PEP 723 script, irrespective of its extension. #[arg(long)] pub script: bool, diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 2a7f325dfed8..659dae8cbcb1 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -366,6 +366,10 @@ uv run [OPTIONS]
  • lowest-direct: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies
  • +
    --script

    Run the given path as a PEP 723 script.

    + +

    Parses the file as a PEP 723 script, irrespective of its extension.

    +
    --upgrade, -U

    Allow package upgrades, ignoring pinned versions in any existing output file. Implies --refresh

    --upgrade-package, -P upgrade-package

    Allow upgrades for a specific package, ignoring pinned versions in any existing output file. Implies --refresh-package

    From bb613d64b562603aa4359dd871e7095f0c7ec71d Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Fri, 27 Sep 2024 06:41:00 -0700 Subject: [PATCH 04/10] More accurate error --- crates/uv/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 9a58fcf29bc5..2dfdf4570aa5 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -135,13 +135,15 @@ async fn run(cli: Cli) -> Result { command, script, .. }) = &**command { + // If the --script flag was passed, attempt to parse the command + // as a PEP 723 script if *script { let (target, args) = command.split(); if let Some(target) = target { let path = PathBuf::from(&target); Some(RunCommand::PythonScript(path, args.to_vec())) } else { - Some(RunCommand::Empty) + anyhow::bail!("Script path must be supplied when using `uv run --script`"); } } else { Some(RunCommand::try_from(command)?) From e9335b7f03a692a54cff0435e5280a127c2415da Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Fri, 27 Sep 2024 06:47:20 -0700 Subject: [PATCH 05/10] Clarify docs --- crates/uv-cli/src/lib.rs | 5 +++-- crates/uv/src/lib.rs | 2 -- docs/reference/cli.md | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 4794fb9c3433..cdb015e198f8 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2548,9 +2548,10 @@ pub struct RunArgs { #[arg(long, conflicts_with = "locked")] pub frozen: bool, - /// Run the given path as a PEP 723 script. + /// Run the given path as a Python script. /// - /// Parses the file as a PEP 723 script, irrespective of its extension. + /// Using `--script` will attempt to parse the path as a PEP 723 script, + /// irrespective of its extension. #[arg(long)] pub script: bool, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 2dfdf4570aa5..68efb85b1830 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -135,8 +135,6 @@ async fn run(cli: Cli) -> Result { command, script, .. }) = &**command { - // If the --script flag was passed, attempt to parse the command - // as a PEP 723 script if *script { let (target, args) = command.split(); if let Some(target) = target { diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 659dae8cbcb1..3d4a9e6f909f 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -366,9 +366,9 @@ uv run [OPTIONS]
  • lowest-direct: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies
  • -
    --script

    Run the given path as a PEP 723 script.

    +
    --script

    Run the given path as a Python script.

    -

    Parses the file as a PEP 723 script, irrespective of its extension.

    +

    Using --script will attempt to parse the path as a PEP 723 script, irrespective of its extension.

    --upgrade, -U

    Allow package upgrades, ignoring pinned versions in any existing output file. Implies --refresh

    From 1144e48afd7babe9b02387aceab4201576b2edb8 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Tue, 1 Oct 2024 15:13:33 -0700 Subject: [PATCH 06/10] Modify to use from_args --- crates/uv-cli/src/lib.rs | 4 ++-- crates/uv/src/commands/project/run.rs | 8 +++++++- crates/uv/src/lib.rs | 9 ++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 17bad4cb2bd3..43c79df8dc24 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2472,7 +2472,7 @@ pub struct RunArgs { /// Run a Python module. /// /// Equivalent to `python -m `. - #[arg(short, long)] + #[arg(short, long, conflicts_with = "script")] pub module: bool, /// Omit non-development dependencies. @@ -2558,7 +2558,7 @@ pub struct RunArgs { /// /// Using `--script` will attempt to parse the path as a PEP 723 script, /// irrespective of its extension. - #[arg(long)] + #[arg(long, short, conflicts_with = "module")] pub script: bool, #[command(flatten)] diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 48f3c2fb72f2..72746b38982b 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -990,7 +990,11 @@ impl std::fmt::Display for RunCommand { } impl RunCommand { - pub(crate) fn from_args(command: &ExternalCommand, module: bool) -> anyhow::Result { + pub(crate) fn from_args( + command: &ExternalCommand, + module: bool, + script: bool, + ) -> anyhow::Result { let (target, args) = command.split(); let Some(target) = target else { return Ok(Self::Empty); @@ -998,6 +1002,8 @@ impl RunCommand { if module { return Ok(Self::PythonModule(target.clone(), args.to_vec())); + } else if script { + return Ok(Self::PythonScript(target.clone().into(), args.to_vec())); } let target_path = PathBuf::from(target); diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 095739fd4d68..4e236d470ebd 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::ffi::OsString; use std::fmt::Write; use std::io::stdout; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::process::ExitCode; use anstream::eprintln; @@ -132,10 +132,13 @@ async fn run(cli: Cli) -> Result { // Parse the external command, if necessary. let run_command = if let Commands::Project(command) = &*cli.command { if let ProjectCommand::Run(uv_cli::RunArgs { - command, module, .. + command, + module, + script, + .. }) = &**command { - Some(RunCommand::from_args(command, *module)?) + Some(RunCommand::from_args(command, *module, *script)?) } else { None } From 27dd886c3d2625d87065a68d230e9a7ce571c15b Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Tue, 1 Oct 2024 15:28:18 -0700 Subject: [PATCH 07/10] Update docs --- docs/reference/cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index f4efb34ead85..83292120a0fe 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -371,7 +371,7 @@ uv run [OPTIONS]
  • lowest-direct: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies
  • -
    --script

    Run the given path as a Python script.

    +
    --script, -s

    Run the given path as a Python script.

    Using --script will attempt to parse the path as a PEP 723 script, irrespective of its extension.

    From 648bd41efbe9f80fa7803185565e5d963cafdf03 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Tue, 1 Oct 2024 16:40:38 -0700 Subject: [PATCH 08/10] Add tests --- crates/uv/tests/run.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/uv/tests/run.rs b/crates/uv/tests/run.rs index cca92e19d888..63cdcfbfb096 100644 --- a/crates/uv/tests/run.rs +++ b/crates/uv/tests/run.rs @@ -5,6 +5,7 @@ use anyhow::Result; use assert_cmd::assert::OutputAssertExt; use assert_fs::{fixture::ChildPath, prelude::*}; use indoc::indoc; +use predicates::str::contains; use std::path::Path; use uv_python::PYTHON_VERSION_FILENAME; @@ -2198,3 +2199,36 @@ fn run_script_explicit() -> Result<()> { Ok(()) } + +#[test] +fn run_script_explicit_no_file() -> Result<()> { + let context = TestContext::new("3.12"); + context + .run() + .arg("--script") + .arg("script") + .assert() + .stderr(contains("can't open file")) + .stderr(contains("[Errno 2] No such file or directory")); + + Ok(()) +} + +#[test] +fn run_script_explicit_directory() -> Result<()> { + let context = TestContext::new("3.12"); + + fs_err::create_dir(context.temp_dir.child("script"))?; + + uv_snapshot!(context.filters(), context.run().arg("--script").arg("script"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: failed to read from file `script` + Caused by: Is a directory (os error 21) + "###); + + Ok(()) +} From 51af3737765c91b56aff34f4bf5b12b0ca81db89 Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Tue, 1 Oct 2024 16:44:45 -0700 Subject: [PATCH 09/10] Fix lint --- crates/uv/tests/run.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/uv/tests/run.rs b/crates/uv/tests/run.rs index 63cdcfbfb096..9a1cef59e55c 100644 --- a/crates/uv/tests/run.rs +++ b/crates/uv/tests/run.rs @@ -2201,7 +2201,7 @@ fn run_script_explicit() -> Result<()> { } #[test] -fn run_script_explicit_no_file() -> Result<()> { +fn run_script_explicit_no_file() { let context = TestContext::new("3.12"); context .run() @@ -2210,8 +2210,6 @@ fn run_script_explicit_no_file() -> Result<()> { .assert() .stderr(contains("can't open file")) .stderr(contains("[Errno 2] No such file or directory")); - - Ok(()) } #[test] From 03aea54080bead3808f266435ef1c1c1b59b933f Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Tue, 1 Oct 2024 17:05:50 -0700 Subject: [PATCH 10/10] Fix windows test --- crates/uv/tests/run.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/uv/tests/run.rs b/crates/uv/tests/run.rs index 9a1cef59e55c..5b1be11520f9 100644 --- a/crates/uv/tests/run.rs +++ b/crates/uv/tests/run.rs @@ -2212,6 +2212,7 @@ fn run_script_explicit_no_file() { .stderr(contains("[Errno 2] No such file or directory")); } +#[cfg(target_family = "unix")] #[test] fn run_script_explicit_directory() -> Result<()> { let context = TestContext::new("3.12");