The entry point must be a function that may be invoked using exactly zero arguments. If you want to pass in arguments from the command line, say you want to invoke it like:

$ pypy a1 a2

You need to read them from sys.argv instead. So your python module should contain this:

def program(arg1, arg2):
    print(arg1, arg2)

def main():
    import sys
    arg1, arg2 = sys.argv[1], sys.argv[2]
    program(arg1, arg2)

if __name__ == "__main__":
    main()

Alternatively, the main function may instead take an argv argument that defaults to sys.argv if importing sys is desirable at the top level of the module:

def main(argv=sys.argv):
    program(argv[1], argv[2])

Running that command as above should print out a1 a2 into the console. Error handling on user input is your own exercise.

Answer from metatoaster on Stack Overflow
🌐
Readthedocs
python-packaging.readthedocs.io › en › latest › command-line-scripts.html
Command Line Scripts — Python Packaging Tutorial
The command_line.py submodule exists only to service the command line tool (which is a convenient organization method): ... setup( ... entry_points = { 'console_scripts': ['funniest-joke=funniest.command_line:main'], } ... ) Again, once the package has been installed, we can use it in the same way. Setuptools will generate a standalone script ‘shim’ which imports your module and calls the registered function.
Discussions

python - How to add an entry_point/console script to setup.py that uses arguments - Stack Overflow
28 python setup tools console_scripts with arguments · 3 How to add console_script/entry point to setup.py that call a class function python More on stackoverflow.com
🌐 stackoverflow.com
argparse - Preferred way to expand a command line script to be used as a library in Python - Software Engineering Stack Exchange
I have a useful Python script that I've been invoking from the command line. It has decent number of options, maybe 20, and it's not unusual to run the script with six or seven flags. Then the rest... More on softwareengineering.stackexchange.com
🌐 softwareengineering.stackexchange.com
March 18, 2015
python - Difference between entry_points/console_scripts and scripts in setup.py? - Stack Overflow
I see the first approach allows ... for my script, but are there any other differences? Different original purposes, compatibility (setuptools, distutils, ...?), usage, ...? I am quite confused and a nice elaborated reply could help me (and probably also others) to properly understand all this. Update: Since I asked the question PyPA published these cool docs on the topic. ... The [update] link broken. I believe packaging.python.org/distr... More on stackoverflow.com
🌐 stackoverflow.com
plac does not work with console_scripts in setup.py
Traceback (most recent call last): ... main() missing 2 required positional arguments: 'repo_url' and 'commit_id' What is the correct way to use plac in combination with the console_scripts directive of a standard setup.py?... More on github.com
🌐 github.com
6
March 12, 2019
🌐
Bioinformatics Answers
biostars.org › p › 223523
python setup tools console_scripts with arguments
I want to implement my pypy.py script on commandline, I need to work with setup tools but the console_script does not work properly as my pypy.py needs two arguments, please guide me how can I modify it properly to work on commendline. ... Traceback (most recent call last): File "/usr/local/bin/python", line 9, in <module> load_entry_point('Pypy==0.1', 'console_scripts', 'pypy')() TypeError: main() takes at least 2 arguments (0 given) python setuptools • 4.9k views
🌐
PyPA
setuptools.pypa.io › en › latest › userguide › entry_point.html
Entry Points - setuptools 84.0.0 documentation
Instead of this approach using __main__.py, you can also create a user-friendly CLI executable that can be called directly without python -m. In the above example, to create a command hello-world that invokes timmins.hello_world, add a console script entry point to your configuration: ... from setuptools import setup setup( # ..., entry_points={ 'console_scripts': [ 'hello-world = timmins:hello_world', ] } )
🌐
Datacadamia
datacadamia.com › lang › python › engine › script
Python - Script
August 30, 2024 - In Python - setup.py · Example: ... Distributions can specify · console_scripts entry points in Python - setup.py, each referring to a function which can be used as a command ·...
Top answer
1 of 3
6

If there is not a good reason to not do so, I would definitely advocate a spin on option 3. As @jonsharp mentions, breaking up your utility into clean units of functionality is a good way to ensure testability. Even the smallest scripts can eventually morph into a much larger program and making sure that you have an extensible API sooner rather than later will alleviate much headache down the road.

The way I'd approach this is:

  1. Break up your code into logical methods with clean and clear I/O
  2. Add unit tests. Having them is never a bad thing.
  3. Rather than using if __name__ == '__main__', create a main() (or similar) method containing your entry point
  4. Use setuptool's setup() function to define the script entry point in your setup.py file.

For example:

from setuptools import setup
setup(
    name='mypackage',
    version='0.1',
    entry_points={
        'console_scripts': [ 'myscript = mypackage.mymodule:main' ],
    }
)

Now, not only is all of your code (including main()) is easily unit testable, but you can still have your console entry point once you've done a python setup.py install|develop.

Any validation I do using, say, ArgumentParser.add_mutually_exclusive_group will not apply when my tool is run as a library instead of a command line script.

Depending on how your API is designed, you may need to add some extra validation to input parameters, but that should likely be there to prevent unexpected input anyways.

Edit: The only time I would use generally use subprocess is when I'm calling into a non-Python application or another Python script that I don't own or have the time to refactor, but the latter only being as a last resort. Most well-written Python utilities will expose both command line utilities and internal API.

2 of 3
3

My guess is that you can pursue #3 by way of #2 and that it won't require anything close to a total refactor. Often in these situations, you just need to make a few adjustments at the entry points and (sometimes) exit points.

  • If needed, wrap your current script in a main() function.

    def main(args, stdin = None):
        if stdin is None:
            stdin = sys.stdin
    
        # full script here
    
    if __name__ == '__main__':
        main(sys.argv[1:])
    
  • That function should take a list of strings. When you parse command-line options, operate on args, not sys.argv.

  • Similarly, adjust other parts of your script to avoid direct operations on sys.stdin and related streams. Instead, use the arguments passed to your main() function.

Programatic users (as opposed to command-line users) will pass in a list of strings and any open file handles (or iterables) they want the code to use. Later, if needed, you can make the programatic use more natural by writing a function that knows how to convert typical positional and keyword-style arguments into the list of strings that your option parser needs.

🌐
Sixfeetup
sixfeetup.com › blog › python-command-line-tools-console_scripts
Building Python Command Line Tools, Part 2: Console Scripts
Ok, now that you have read in the arguments to our script, you need to make sure that a script is created when someone installs our product. The setup.py handles this for you by defining a [console_scripts] entry points section like this: entry_points = """\ [paste.app_factory] main = myapp:main [console_scripts] import_sessions = myapp.scripts:import_sessions """, This allows your Python function to be registered as a command line tool.
Find elsewhere
🌐
Python Packaging
packaging.python.org › specifications › entry-points
Entry points specification - Python Packaging User Guide
[console_scripts] foo = foomod:main ... system shell after the package is installed. The object reference points to a function which will be called with no arguments when this command is run....
🌐
GitHub
github.com › ialbert › plac › issues › 31
plac does not work with console_scripts in setup.py · Issue #31 · ialbert/plac
March 12, 2019 - I'm writing a program that I want to distribute with a setup.py that contains the following lines: entry_points={ 'console_scripts': [ 'git-explorer=git_explorer.cli:main', 'git-explorer-similar=git_explorer.find_similar:main', ], }, the...
Author: ialbert
🌐
Bouzekri
blog.bouzekri.net › 2017-10-08-bundle-cli-in-python-project.html
Bundle a CLI script in your Python project | Jonathan Bouzekri’s blog
October 8, 2017 - Each console script is defined with a string that follows this convention : <name of the executable> = <fullpath to a python module>:<name of the function to execute in the module> An example is worth all explanation, let’s continue to work with our previous basicpythonpackage: $ cat >setup.py <<EOL from setuptools import setup, find_packages setup( name="basicpythonpackage", version="0.1", packages=find_packages(), entry_points={ 'console_scripts': [ 'foo = basicpythonpackage.foo:my_func' ] } ) EOL $ cat >basicpythonpackage/foo.py <<EOL import sys def my_func(*args, **kwargs): print('args', args) print('kwargs', kwargs) EOL $ pip install .
🌐
Medium
medium.com › @richard.jones › python-script-or-command-line-utility-ef1cdac31b27
Python script or command line utility? | by Richard D Jones | Medium
July 13, 2018 - def main(): parser = argparse.ArgumentParser() parser.add_argument("-o", "--output") parser.add_argument("-i", "--input") args = parser.parse_args() run_my_program(args.input, args.output) In setup.py for your application, just define the entry_points for a console_script as follows: from setuptools import setup setup( name = 'myprogram', version = '1.0.0', # ....
🌐
Click Documentation
click.palletsprojects.com › en › stable › setuptools
Setuptools Integration — Click Documentation (8.5.x)
Navigation · index · next · previous · Click Documentation (8.5.x) » · Setuptools Integration · Setuptools Integration¶ · Moved to Packaging Entry Points · Overview
🌐
Astropy
docs.astropy.org › en › latest › development › scripts.html
Command-Line Scripts — Astropy v8.0.0.dev615+g017922a23
They should have a main function to parse the arguments and pass those arguments on to some library function so that the library function can be used programmatically when needed. The main function should accept an optional single argument that holds the sys.argv list, except for the script name (e.g., argv[1:]). It must then be added to the list of entry points in the setup.py file (see the example below).
🌐
Chriswarrick
chriswarrick.com › blog › 2014 › 09 › 15 › python-apps-the-right-way-entry_points-and-scripts
Python Apps the Right Way: entry points and scripts | Chris Warrick
September 15, 2014 - The main() function must not take any arguments, because that’s how entry_points executes things. This is the real deal: create the entry points in your setup.py file. ... You must use setuptools, otherwise this won’t work. The most important piece of code is the entry_points declaration ...
🌐
Ourpython
ourpython.com › python › how-to-add-an-entry-point-console-script-to-setup-py-that-uses-arguments
How to add an entry_point/console script to setup.py that uses arguments | OurPython.com
But in this particular case since True is already the default argument for the parameter print_me, this is probably not necessary, and changing 'test = mypackage.__main__:main(print_me=True)' to 'test = mypackage.__main__:main' should be enough. Make it a normal .py module so it can be imported, and define an entry point. Automatic script creation will take care of creating a script in bin that will call the entry point method.