uv icon indicating copy to clipboard operation
uv copied to clipboard

entry points/scripts with additional "attributes" are not generated correctly (e.g. for Pyinvoke)

Open markmmm opened this issue 6 months ago • 4 comments

uv generates console scripts incorrectly for entry_points that have dotted function paths. For example Invoke's setup.py:

    entry_points={
        "console_scripts": [
            "invoke = invoke.main:program.run",
            "inv = invoke.main:program.run",
        ]
    },

The generated script will look like:

#!/path/to/uv-venv/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from invoke.main import program.run
if __name__ == "__main__":
    sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
    sys.exit(program.run())

And trying to run this (Python 3.12.0) generates:

uv-venv/bin/inv
  File "/Users/markm/clmv2/ServiceTest/uv-venv/bin/inv", line 5
    from invoke.main import program.run
                                   ^
SyntaxError: invalid syntax

The pip generated console script separates the import_name from the function_name

#!/path/to/py-venv/bin/python3.12
# -*- coding: utf-8 -*-
import re
import sys
from invoke.main import program
if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(program.run())
  • Steps to Reproduce:
    • uv pip install pyinvoke
    • inv (or invoke)
  • Platform: macOS 14.3 (23D56) (M1)
  • uv-version: uv 0.1.3

I see there is a comment in the UV code asking what is the format - I think we can probably follow the rules here: https://github.com/pypa/setuptools/blob/main/docs/userguide/entry_point.rst#entry-points-syntax i.e.

Nested object

If you supply:

<name> = <package_or_module>:<object>.<attr>.<nested_attr>

this will be roughly interpreted as:

from <package_or_module> import <object>
parsed_value = <object>.<attr>.<nested_attr>

Also maybe not relevant - but the script parsing regex looks different between Pip and uv:

  • Pip: https://github.com/pypa/pip/blob/7f8a6844037fb7255cfd0d34ff8e8cf44f2598d4/src/pip/_vendor/distlib/util.py#L710
  • uv: https://github.com/astral-sh/uv/blob/main/crates/install-wheel-rs/src/script.rs#L46C9-L46C155

I don't mind trying to fix this - but unsure whether I should add an "import_name" field to script::Script - or provide a function to return these from the string (i.e. instead of using entrypoint.import_name

Would the preference be to extract this in the regex - or via string parsing/splitting?

markmmm avatar Feb 17 '24 04:02 markmmm

Awesome, thank you! Would love help. I think it'd be reasonable to add a field to script::Script, but I don't have a strong preference at this point.

charliermarsh avatar Feb 17 '24 04:02 charliermarsh

Thanks for writing up such a clear issue.

charliermarsh avatar Feb 17 '24 04:02 charliermarsh

I also ran into this when installing granian. Which also has a dot in the script but not in the module name.

Below I attached the differences between pip and uv I found, which match the problem.

[project.scripts]
granian = 'granian:cli.cli'

The script generated by pip is:

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from granian import cli
if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(cli.cli())

While uv creates

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from granian import cli.cli
if __name__ == "__main__":
    sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
    sys.exit(cli.cli())

WAKayser avatar Feb 17 '24 20:02 WAKayser

I can look into this.

MichaReiser avatar Feb 17 '24 22:02 MichaReiser