astral-sh / uv

An extremely fast Python package and project manager, written in Rust.
https://docs.astral.sh/uv
Apache License 2.0
24.79k stars 715 forks source link

Inline script metadata ignored when using `uv run -m` #8317

Open rkern opened 1 week ago

rkern commented 1 week ago

uv run -m will not respond to PEP 723 inline script metadata that is included in the script module. My motivating use case is that I typically have some developer and CI conveniences wrapped up in a click script with lots of subcommands. I want it to run in an isolated Python venv which only needs click installed, not the project's venv. The inline script metadata with uv run works great for this. However, I usually implement this in a package with a __main__.py script (I factor some things out like configuration into a separate module and might have some data files; a package works great to isolate all of this to keep it tidy from the rest of the project). uv run -m does not seem to read the inline script metadata implemented in the __main__.py (or other modules run via -m).

It's entirely possible that this is not possible if you need the Python env already set up to dereference the package.module name to find the actual file. If that is the case, this limitation would be good to document.

Thank you!

❯ uv --version
uv 0.4.24 (b9cd54913 2024-10-17)

❯ tree
.
└── foo
    ├── __init__.py
    ├── __main__.py
    └── script1.py

2 directories, 3 files

❯ cat foo/script1.py
# /// script
# dependencies = [
#   "click"
# ]
# ///

import click

@click.command()
def cli():
    click.echo("Hello, world, from script1!")

if __name__ == "__main__":
    cli()

❯ cat foo/__main__.py
# /// script
# dependencies = [
#   "click"
# ]
# ///

import click

@click.command()
def cli():
    click.echo("Hello, world, from __main__!")

if __name__ == "__main__":
    cli()

❯ uv run foo/script1.py
Reading inline script metadata from: foo/script1.py
Hello, world, from script1!

❯ uv run -m foo.script1
Traceback (most recent call last):
  File "/Users/rkern/Library/Application Support/uv/python/cpython-3.10.14-macos-x86_64-none/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/Users/rkern/Library/Application Support/uv/python/cpython-3.10.14-macos-x86_64-none/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/Users/rkern/scratch/uv-m-bug/foo/script1.py", line 8, in <module>
    import click
ModuleNotFoundError: No module named 'click'

❯ uv run -m foo
Traceback (most recent call last):
  File "/Users/rkern/Library/Application Support/uv/python/cpython-3.10.14-macos-x86_64-none/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/Users/rkern/Library/Application Support/uv/python/cpython-3.10.14-macos-x86_64-none/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/Users/rkern/scratch/uv-m-bug/foo/__main__.py", line 8, in <module>
    import click
ModuleNotFoundError: No module named 'click'
zanieb commented 1 week ago

Thanks for the detailed write-up!

This feels like a pretty weird pattern, i.e., I don't think this is an intended use of PEP 723. I see how it's helpful, but I'm not sure if we should support it or if there's another pattern we can recommend.

rkern commented 1 week ago

It seems to me that the PEP 723 section on "Why not just set up a Python project with a pyproject.toml?" applies to the most obvious alternative pattern. This is an internal script not for redistribution (packaging in a bdist). It's just local stuff. It happens to be organized in small package with a __main__.py instead of a script because it happens to have multiple files.