Allow --script to be provided with uv run - (#10035)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86_64 (push) Blocked by required conditions
CI / check system | python3.10 on windows (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on linux (push) Blocked by required conditions
CI / check system | conda3.8 on linux (push) Blocked by required conditions
CI / check system | conda3.11 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on macos (push) Blocked by required conditions
CI / check system | conda3.11 on windows (push) Blocked by required conditions
CI / check system | conda3.8 on windows (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

## Summary

Closes #10021.
This commit is contained in:
Charlie Marsh 2024-12-19 12:52:57 -05:00 committed by GitHub
parent 5a3826d9ff
commit 4513ce0e2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 182 additions and 10 deletions

View file

@ -1182,6 +1182,8 @@ pub(crate) enum RunCommand {
PythonZipapp(PathBuf, Vec<OsString>),
/// Execute a `python` script provided via `stdin`.
PythonStdin(Vec<u8>, Vec<OsString>),
/// Execute a `pythonw` script provided via `stdin`.
PythonGuiStdin(Vec<u8>, Vec<OsString>),
/// Execute a Python script provided via a remote URL.
PythonRemote(tempfile::NamedTempFile, Vec<OsString>),
/// Execute an external command.
@ -1209,6 +1211,13 @@ impl RunCommand {
}
}
Self::PythonStdin(..) => Cow::Borrowed("python -c"),
Self::PythonGuiStdin(..) => {
if cfg!(windows) {
Cow::Borrowed("pythonw -c")
} else {
Cow::Borrowed("python -c")
}
}
Self::External(executable, _) => executable.to_string_lossy(),
}
}
@ -1280,6 +1289,38 @@ impl RunCommand {
process
}
Self::PythonGuiStdin(script, args) => {
let python_executable = interpreter.sys_executable();
// Use `pythonw.exe` if it exists, otherwise fall back to `python.exe`.
// See `install-wheel-rs::get_script_executable`.gd
let pythonw_executable = python_executable
.file_name()
.map(|name| {
let new_name = name.to_string_lossy().replace("python", "pythonw");
python_executable.with_file_name(new_name)
})
.filter(|path| path.is_file())
.unwrap_or_else(|| python_executable.to_path_buf());
let mut process = Command::new(&pythonw_executable);
process.arg("-c");
#[cfg(unix)]
{
use std::os::unix::ffi::OsStringExt;
process.arg(OsString::from_vec(script.clone()));
}
#[cfg(not(unix))]
{
let script = String::from_utf8(script.clone()).expect("script is valid UTF-8");
process.arg(script);
}
process.args(args);
process
}
Self::External(executable, args) => {
let mut process = Command::new(executable);
process.args(args);
@ -1328,6 +1369,10 @@ impl std::fmt::Display for RunCommand {
write!(f, "python -c")?;
Ok(())
}
Self::PythonGuiStdin(..) => {
write!(f, "pythonw -c")?;
Ok(())
}
Self::External(executable, args) => {
write!(f, "{}", executable.to_string_lossy())?;
for arg in args {
@ -1360,6 +1405,19 @@ impl RunCommand {
return Ok(Self::Empty);
};
if target.eq_ignore_ascii_case("-") {
let mut buf = Vec::with_capacity(1024);
std::io::stdin().read_to_end(&mut buf)?;
return if module {
Err(anyhow!("Cannot run a Python module from stdin"))
} else if gui_script {
Ok(Self::PythonGuiStdin(buf, args.to_vec()))
} else {
Ok(Self::PythonStdin(buf, args.to_vec()))
};
}
let target_path = PathBuf::from(target);
// Determine whether the user provided a remote script.
@ -1402,21 +1460,17 @@ 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()));
} else if gui_script {
return Ok(Self::PythonGuiScript(target.clone().into(), args.to_vec()));
} else if script {
return Ok(Self::PythonScript(target.clone().into(), args.to_vec()));
}
let metadata = target_path.metadata();
let is_file = metadata.as_ref().map_or(false, std::fs::Metadata::is_file);
let is_dir = metadata.as_ref().map_or(false, std::fs::Metadata::is_dir);
if target.eq_ignore_ascii_case("-") {
let mut buf = Vec::with_capacity(1024);
std::io::stdin().read_to_end(&mut buf)?;
Ok(Self::PythonStdin(buf, args.to_vec()))
} else if target.eq_ignore_ascii_case("python") {
if target.eq_ignore_ascii_case("python") {
Ok(Self::Python(args.to_vec()))
} else if target_path
.extension()

View file

@ -175,9 +175,9 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
Some(RunCommand::PythonRemote(script, _)) => {
Pep723Metadata::read(&script).await?.map(Pep723Item::Remote)
}
Some(RunCommand::PythonStdin(contents, _)) => {
Pep723Metadata::parse(contents)?.map(Pep723Item::Stdin)
}
Some(
RunCommand::PythonStdin(contents, _) | RunCommand::PythonGuiStdin(contents, _),
) => Pep723Metadata::parse(contents)?.map(Pep723Item::Stdin),
_ => None,
}
} else if let ProjectCommand::Remove(uv_cli::RemoveArgs {

View file

@ -2544,6 +2544,20 @@ fn run_module() {
"#);
}
#[test]
fn run_module_stdin() {
let context = TestContext::new("3.12");
uv_snapshot!(context.filters(), context.run().arg("-m").arg("-"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Cannot run a Python module from stdin
"###);
}
/// When the `pyproject.toml` file is invalid.
#[test]
fn run_project_toml_error() -> Result<()> {
@ -2874,6 +2888,40 @@ fn run_script_explicit() -> Result<()> {
Ok(())
}
#[test]
fn run_script_explicit_stdin() -> 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("-").stdin(std::fs::File::open(test_script)?), @r###"
success: true
exit_code: 0
----- stdout -----
Hello, world!
----- stderr -----
Reading inline script metadata from `stdin`
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");
@ -2942,6 +2990,41 @@ fn run_gui_script_explicit_windows() -> Result<()> {
Ok(())
}
#[test]
#[cfg(windows)]
fn run_gui_script_explicit_stdin_windows() -> 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("--gui-script").arg("-").stdin(std::fs::File::open(test_script)?), @r###"
success: true
exit_code: 0
----- stdout -----
Hello, world!
----- stderr -----
Reading inline script metadata from `stdin`
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
"###);
Ok(())
}
#[test]
#[cfg(not(windows))]
fn run_gui_script_explicit_unix() -> Result<()> {
@ -2974,6 +3057,41 @@ fn run_gui_script_explicit_unix() -> Result<()> {
Ok(())
}
#[test]
#[cfg(not(windows))]
fn run_gui_script_explicit_stdin_unix() -> 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("--gui-script").arg("-").stdin(std::fs::File::open(test_script)?), @r###"
success: true
exit_code: 0
----- stdout -----
Hello, world!
----- stderr -----
Reading inline script metadata from `stdin`
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
"###);
Ok(())
}
#[test]
fn run_remote_pep723_script() {
let context = TestContext::new("3.12").with_filtered_python_names();