mirror of https://github.com/astral-sh/uv
Support `uv run -m foo` to run a module (#7754)
## Summary This is another attempt using `module: bool` instead of `module: Option<String>` following #7322. The original PR can't be reopened after a force-push to the branch, I've created this new PR. Resolves #6638
This commit is contained in:
parent
1cae78dd03
commit
0dbf9ae4a7
|
|
@ -2469,6 +2469,12 @@ pub struct RunArgs {
|
|||
#[arg(long, overrides_with("dev"))]
|
||||
pub no_dev: bool,
|
||||
|
||||
/// Run a Python module.
|
||||
///
|
||||
/// Equivalent to `python -m <module>`.
|
||||
#[arg(short, long)]
|
||||
pub module: bool,
|
||||
|
||||
/// Omit non-development dependencies.
|
||||
///
|
||||
/// The project itself will also be omitted.
|
||||
|
|
@ -2495,7 +2501,7 @@ pub struct RunArgs {
|
|||
#[arg(long)]
|
||||
pub with: Vec<String>,
|
||||
|
||||
/// Run with the given packages installed as editables
|
||||
/// Run with the given packages installed as editables.
|
||||
///
|
||||
/// When used in a project, these dependencies will be layered on top of
|
||||
/// the project environment in a separate, ephemeral environment. These
|
||||
|
|
|
|||
|
|
@ -832,6 +832,9 @@ pub(crate) enum RunCommand {
|
|||
Python(Vec<OsString>),
|
||||
/// Execute a `python` script.
|
||||
PythonScript(PathBuf, Vec<OsString>),
|
||||
/// Search `sys.path` for the named module and execute its contents as the `__main__` module.
|
||||
/// Equivalent to `python -m module`.
|
||||
PythonModule(OsString, Vec<OsString>),
|
||||
/// Execute a `pythonw` script (Windows only).
|
||||
PythonGuiScript(PathBuf, Vec<OsString>),
|
||||
/// Execute a Python package containing a `__main__.py` file.
|
||||
|
|
@ -856,6 +859,7 @@ impl RunCommand {
|
|||
| Self::PythonPackage(..)
|
||||
| Self::PythonZipapp(..)
|
||||
| Self::Empty => Cow::Borrowed("python"),
|
||||
Self::PythonModule(..) => Cow::Borrowed("python -m"),
|
||||
Self::PythonGuiScript(..) => Cow::Borrowed("pythonw"),
|
||||
Self::PythonStdin(_) => Cow::Borrowed("python -c"),
|
||||
Self::External(executable, _) => executable.to_string_lossy(),
|
||||
|
|
@ -878,6 +882,13 @@ impl RunCommand {
|
|||
process.args(args);
|
||||
process
|
||||
}
|
||||
Self::PythonModule(module, args) => {
|
||||
let mut process = Command::new(interpreter.sys_executable());
|
||||
process.arg("-m");
|
||||
process.arg(module);
|
||||
process.args(args);
|
||||
process
|
||||
}
|
||||
Self::PythonGuiScript(target, args) => {
|
||||
let python_executable = interpreter.sys_executable();
|
||||
|
||||
|
|
@ -944,6 +955,14 @@ impl std::fmt::Display for RunCommand {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
Self::PythonModule(module, args) => {
|
||||
write!(f, "python -m")?;
|
||||
write!(f, " {}", module.to_string_lossy())?;
|
||||
for arg in args {
|
||||
write!(f, " {}", arg.to_string_lossy())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Self::PythonGuiScript(target, args) => {
|
||||
write!(f, "pythonw {}", target.display())?;
|
||||
for arg in args {
|
||||
|
|
@ -970,17 +989,18 @@ impl std::fmt::Display for RunCommand {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ExternalCommand> for RunCommand {
|
||||
type Error = std::io::Error;
|
||||
|
||||
fn try_from(command: &ExternalCommand) -> Result<Self, Self::Error> {
|
||||
impl RunCommand {
|
||||
pub(crate) fn from_args(command: &ExternalCommand, module: bool) -> anyhow::Result<Self> {
|
||||
let (target, args) = command.split();
|
||||
|
||||
let Some(target) = target else {
|
||||
return Ok(Self::Empty);
|
||||
};
|
||||
|
||||
let target_path = PathBuf::from(&target);
|
||||
if module {
|
||||
return Ok(Self::PythonModule(target.clone(), args.to_vec()));
|
||||
}
|
||||
|
||||
let target_path = PathBuf::from(target);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -131,8 +131,11 @@ 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, .. }) = &**command {
|
||||
Some(RunCommand::try_from(command)?)
|
||||
if let ProjectCommand::Run(uv_cli::RunArgs {
|
||||
command, module, ..
|
||||
}) = &**command
|
||||
{
|
||||
Some(RunCommand::from_args(command, *module)?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@ impl RunSettings {
|
|||
no_all_extras,
|
||||
dev,
|
||||
no_dev,
|
||||
module: _,
|
||||
only_dev,
|
||||
no_editable,
|
||||
command: _,
|
||||
|
|
|
|||
|
|
@ -1830,6 +1830,43 @@ fn run_zipapp() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a module equivalent to `python -m foo`.
|
||||
#[test]
|
||||
fn run_module() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().arg("-m").arg("__hello__"), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Hello world!
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
|
||||
uv_snapshot!(context.filters(), context.run().arg("-m").arg("http.server").arg("-h"), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
usage: server.py [-h] [--cgi] [-b ADDRESS] [-d DIRECTORY] [-p VERSION] [port]
|
||||
|
||||
positional arguments:
|
||||
port bind to this port (default: 8000)
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--cgi run as CGI server
|
||||
-b ADDRESS, --bind ADDRESS
|
||||
bind to this address (default: all interfaces)
|
||||
-d DIRECTORY, --directory DIRECTORY
|
||||
serve this directory (default: current directory)
|
||||
-p VERSION, --protocol VERSION
|
||||
conform to this HTTP version (default: HTTP/1.0)
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
}
|
||||
|
||||
/// When the `pyproject.toml` file is invalid.
|
||||
#[test]
|
||||
fn run_project_toml_error() -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -217,6 +217,10 @@ uv run [OPTIONS] <COMMAND>
|
|||
|
||||
<p>Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated, uv will exit with an error.</p>
|
||||
|
||||
</dd><dt><code>--module</code>, <code>-m</code></dt><dd><p>Run a Python module.</p>
|
||||
|
||||
<p>Equivalent to <code>python -m <module></code>.</p>
|
||||
|
||||
</dd><dt><code>--native-tls</code></dt><dd><p>Whether to load TLS certificates from the platform’s native certificate store.</p>
|
||||
|
||||
<p>By default, uv loads certificates from the bundled <code>webpki-roots</code> crate. The <code>webpki-roots</code> are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).</p>
|
||||
|
|
@ -380,7 +384,7 @@ uv run [OPTIONS] <COMMAND>
|
|||
|
||||
<p>When used in a project, these dependencies will be layered on top of the project environment in a separate, ephemeral environment. These dependencies are allowed to conflict with those specified by the project.</p>
|
||||
|
||||
</dd><dt><code>--with-editable</code> <i>with-editable</i></dt><dd><p>Run with the given packages installed as editables</p>
|
||||
</dd><dt><code>--with-editable</code> <i>with-editable</i></dt><dd><p>Run with the given packages installed as editables.</p>
|
||||
|
||||
<p>When used in a project, these dependencies will be layered on top of the project environment in a separate, ephemeral environment. These dependencies are allowed to conflict with those specified by the project.</p>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue