Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support module __main__.py through python -m (entry points/console scripts) #1995

Open
SoniEx2 opened this issue Feb 14, 2020 · 14 comments
Open

Comments

@SoniEx2
Copy link

SoniEx2 commented Feb 14, 2020

-m is a python standard (PEP 338) and we should support and encourage it. best way to do that is to defer to it when asked to. we should ideally also deprecate the old ways to further encourage -m usage.

@jaraco jaraco changed the title Support module __main__.py through python -m (entry points/console scripts) Support runpy execution Feb 15, 2020
@jaraco
Copy link
Member

jaraco commented Feb 15, 2020

The behavior is largely documented in the runpy module. Setuptools supports this for its easy_install behavior as python -m easy_install. Is there other behavior you're expecting?

@SoniEx2
Copy link
Author

SoniEx2 commented Feb 15, 2020

No, the original title is correct. I want entry points/console scripts that use -m behaviour.

@SoniEx2 SoniEx2 changed the title Support runpy execution Support module __main__.py through python -m (entry points/console scripts) Feb 15, 2020
@jaraco
Copy link
Member

jaraco commented Feb 15, 2020

Can you give an example of what you're after?

@SoniEx2
Copy link
Author

SoniEx2 commented Feb 15, 2020

ideally something along the lines of:

setup.py:

runpy_scripts={"ganarchy": "ganarchy"}

ganarchy/__main__.py:

import ganarchy.cli
import ganarchy.cli.debug
# import other ganarchy.cli.* modules etc

ganarchy.cli.main()

but idk how the setup.py should look for it.

@jaraco
Copy link
Member

jaraco commented Feb 15, 2020

I think I understand. So you want setuptools to accept a mapping of package name to console_script entrypoint name and from that, it will generate a __main__ in the package that loads that entry point and invokes it?

Note that the generated module would always be essentially boilerplate:

# ganarchy/__main__.py
import ganarchy.cli
if __name__ == '__main__':
  ganarchy.cli.main()

I'm a little wary of generating modules in a package. I'm also uneasy coupling console_scripts to another feature (runpy module generation).

I think I'd rather solicit a separate entry point for runpy indication:

# setup.cfg
[options.entry_points]
console_scripts =
  ganarchy = ganarchy.cli:main
runpy_modules =
  ganarchy = ganarchy.cli:main

This approach has the advantage of supplying the two similar features in a parallel way.

The biggest disadvantage to this approach is that, similar to console_scripts, it would be the responsibility of the installer to generate these runpy modules.

I wouldn't restrict the design to just package modules (__main__). Any module could be suitable, including top-level unimportable modules (like in pip-run) and submodules (like pep517.build).

The main reason I'm wary to generate content is because it's not obvious when that content should be generated (should it appear in the sdist, in the built module, only at install time, is it optional?). Lots of design considerations.

I'm more inclined to think that the package should simply supply the runpy script as part of the source, that the advantage of making it declarative is more trouble than than the value it provides.

Can you elaborate on what problem you're trying to solve and what design you have in mind?

@SoniEx2
Copy link
Author

SoniEx2 commented Feb 15, 2020

nonono, no __main__ generator.

it should install in the PATH (or equivalent) the following script:

ganarchy (shell script in PATH):

#!/usr/bin/env python -m ganarchy

or equivalent.

no if __name__=="__main__":. also note that the ganarchy.cli.* imports are used for side-effects (they modify ganarchy.cli.main)

@SoniEx2
Copy link
Author

SoniEx2 commented Feb 15, 2020

this is for existing __main__.py that can already be used from python -m.

@dbivolaru
Copy link

dbivolaru commented Aug 14, 2020

Was looking for a similar solution and stumbled upon this question.
My solution is as follows.

setup.py:

setup(
    # other arguments here...
    entry_points={
        'console_scripts': ['ganarchy=ganarchy.cli:entry_point']
    }
    # yet some other arguments here...
)

And add at the end of ganarchy/cli/__init__.py:

# your original code...

def entry_point():
    import runpy
    runpy.run_module(__name__)

This ensures both $ python -m ganarchy.cli and $ ganarchy will be doing the same thing.
This is tested and working (for posterity in case someone else also stumbles upon this from a search engine).

In terms of integrating this in setuptools as a feature, the API could look like (my proposal):

    entry_points={
        'console_scripts': ['ganarchy:=ganarchy.cli']
    }

The := instead of = would signify that setuptools would generate the entry_point() stub, as above, that calls runpy.run_module with the string after the :=.

@jaraco
Copy link
Member

jaraco commented Oct 4, 2020

The := instead of = would signify that setuptools would generate the entry_point() stub, as above, that calls runpy.run_module with the string after the :=.

Interesting idea. Unfortunately, I don't think it's compatible with other syntaxes supported. I believe, for example, a dictionary is supported for the value for console_scripts. There may also be syntaxes in setup.cfg that would not support this syntax.

it should install in the PATH (or equivalent) the following script:

Oh. That's interesting too. The biggest problem I see with that approach is it may not be compatible with executable wrappers like those used on Windows. It would still definitely require some support from pip (and other installers).

@Xophmeister
Copy link

Similar to @dbivolaru, I "solved" this by using:

[options.entry_points]
console_scripts =
  my_script = my.module.name.__main__:main

i.e., Explicitly addressing the main function in my __main__.py, whereas it could have simply been invoked with python -m my.module.name. It's ugly and took a bit of trial-and-error, but it works.

In the current system, you have to delimit module and the entrypoint callable with a colon. What if the logic was something like:

  • Split on :
    • If there are two components, then proceed as currently;
    • If there is only one component, then interpret this as a direct module invocation?

@pradyunsg
Copy link
Member

pradyunsg commented Feb 18, 2022

FWIW, after #2671 is resolved, this will have a universal cross build-backend answer -- putting something like the following in pyproject.toml:

[project]
...

[project.scripts]
script-name = "awesome.package.__main__:main"

olof added a commit to olof/mxmda that referenced this issue Mar 12, 2023
This also has the side effect of messing up my __main__; because
setuptools doesn't support __main__ modules, the entrypoints must refer
to a function.

Reference: pypa/setuptools#1995
@itcarroll
Copy link

Navigating packaging docs for first time as a novice package developer and Google brought me here and here. Both discussions seem to have stalled, sadly.

This ...

[project.scripts]
script-name = "awesome.package.__main__:main"

just seems like an artifact from the days before __main__.py and its forcing me to introduce the if __name__ == "__main__" boilerplate. I hope these discussions can resume. Where is the current blockage?

@genevieve-me
Copy link

+1. Adding a module/__main__.py is a best practice anyways, so since I already wrote that file for python -m module to work, it would be nice if pip defaulted to using it if present under project.scripts.script-name = somenamespace without the extra boilerplate.

@abravalheri
Copy link
Contributor

+1. Adding a module/main.py is a best practice anyways, so since I already wrote that file for python -m module to work, it would be nice if pip defaulted to using it if present under project.scripts.script-name = somenamespace without the extra boilerplate.

Hi @genevieve-me, thank you very much for the feedback.
I think this issue touches more than setuptools, as it is related to the specifications for entry-points and to installers that generate wrappers for the console_scripts entry-points.
Therefore probably a better forum to discuss changes in the standards/interoperability is https://discuss.python.org/c/packaging/14.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants