mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Support uv run --script (#7739)
This PR adds support for executing a script with ```uv run```, even when the script does not have a ```.py``` extension. Addresses #7396.
This commit is contained in:
parent
b0014835dc
commit
5ff7dc99cb
6 changed files with 92 additions and 4 deletions
|
@ -2472,7 +2472,7 @@ pub struct RunArgs {
|
|||
/// Run a Python module.
|
||||
///
|
||||
/// Equivalent to `python -m <module>`.
|
||||
#[arg(short, long)]
|
||||
#[arg(short, long, conflicts_with = "script")]
|
||||
pub module: bool,
|
||||
|
||||
/// Omit non-development dependencies.
|
||||
|
@ -2554,6 +2554,13 @@ pub struct RunArgs {
|
|||
#[arg(long, conflicts_with = "locked")]
|
||||
pub frozen: bool,
|
||||
|
||||
/// 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.
|
||||
#[arg(long, short, conflicts_with = "module")]
|
||||
pub script: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
pub installer: ResolverInstallerArgs,
|
||||
|
||||
|
|
|
@ -1021,7 +1021,11 @@ impl std::fmt::Display for RunCommand {
|
|||
}
|
||||
|
||||
impl RunCommand {
|
||||
pub(crate) fn from_args(command: &ExternalCommand, module: bool) -> anyhow::Result<Self> {
|
||||
pub(crate) fn from_args(
|
||||
command: &ExternalCommand,
|
||||
module: bool,
|
||||
script: bool,
|
||||
) -> anyhow::Result<Self> {
|
||||
let (target, args) = command.split();
|
||||
let Some(target) = target else {
|
||||
return Ok(Self::Empty);
|
||||
|
@ -1029,6 +1033,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);
|
||||
|
|
|
@ -132,10 +132,13 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
// 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
|
||||
}
|
||||
|
|
|
@ -248,6 +248,7 @@ impl RunSettings {
|
|||
module: _,
|
||||
only_dev,
|
||||
no_editable,
|
||||
script: _,
|
||||
command: _,
|
||||
with,
|
||||
with_editable,
|
||||
|
|
|
@ -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;
|
||||
|
@ -2164,3 +2165,69 @@ 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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_script_explicit_no_file() {
|
||||
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"));
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[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(())
|
||||
}
|
||||
|
|
|
@ -371,6 +371,10 @@ uv run [OPTIONS] <COMMAND>
|
|||
|
||||
<li><code>lowest-direct</code>: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies</li>
|
||||
</ul>
|
||||
</dd><dt><code>--script</code>, <code>-s</code></dt><dd><p>Run the given path as a Python script.</p>
|
||||
|
||||
<p>Using <code>--script</code> will attempt to parse the path as a PEP 723 script, irrespective of its extension.</p>
|
||||
|
||||
</dd><dt><code>--upgrade</code>, <code>-U</code></dt><dd><p>Allow package upgrades, ignoring pinned versions in any existing output file. Implies <code>--refresh</code></p>
|
||||
|
||||
</dd><dt><code>--upgrade-package</code>, <code>-P</code> <i>upgrade-package</i></dt><dd><p>Allow upgrades for a specific package, ignoring pinned versions in any existing output file. Implies <code>--refresh-package</code></p>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue