Building a Python CLI: how to properly declare entry_points in setuptools.setup

Viewed 797

Solved using click instead of argparse, see below..


I'm building a Python command line tool, following a great tutorial by Thomas Stringer. My tool is used like so

$ python rsearch.py -f foo -b bar

I packaged it with python setup.py sdist and python setup.py install, following the docs. All working great! Now I install it as a pip package with pip install -e . and my executable is ready to roll, right?

$ which rsearch
/usr/local/bin/rsearch

Problem is

$ rsearch -f foo.txt -b bar/
Searching...
Searching... 
Found item from foo.txt in bar/path/to/target/
Searching...
Done!
Traceback (most recent call last):
  File "/usr/local/bin/rsearch", line 11, in <module>
    load_entry_point('rsearch', 'console_scripts', 'rsearch')()
TypeError: main() missing 2 required positional arguments: 'foo' and 'bar'

I think the issue is that my entry_points are not specified correctly:

from setuptools import setup
setup(
    name='rsearch',
    version='0.0.1',
    py_modules=['rsearch'],
    entry_points={
        'console_scripts': [
            'rsearch=rsearch.__main__:main'
        ]
    })

Based on the tutorial I followed, albeit from 2016, this should work. I know distutils.core is the preferred setup() method for Python 3.7, but I've tried that as well and I am getting the same result.

My __main__.py looks like this:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
    '--foo', 
    '-f',
    help='Foo help'
)
parser.add_argument(
    '--bar', 
    '-b', 
    help='Bar help'
)

def main(foi, gras):
    # code code code 
    # code code..

args = parser.parse_args()
main(args.foo, args.bar)

and my project is structured like this:

rsearch
├── rsearch
│   └── __main__.py
└── setup.py

Side note: the python script runs as expected:

$ python __main__.py -f foo.txt -b bar/
Searching...
Searching... 
Found item from foo.txt in bar/path/to/target/
Searching...
Done!

So the script doesn't appear to be the problem. This error only occurs when trying to run executable built with setuptools.setup()

I'm using Python 3.7.3 and pip 19.1.1. Any help or suggestions are much appreciated.


UPDATE

Per @techouse's suggestion, I replicated this CLI using click and updated the entry_points assignment in setup.py, following their documentation.

setup(
    name='rsearch',
    version='0.0.1',
    py_modules=['rsearch'],
    install_requires=[
        'Click',
    ],
    entry_points='''
        [console_scripts]
        rsearch=rsearch_script:cli
    ''',
)

I renamed the script to rsearch_script.py and used the click decorators instead of argparse:

@click.command()
@click.option('--foo', '-f')
@click.option('--bar', '-b')
def cli(foo, bar):
    # code code code
    # code code..

I also moved the files (from the docs, might not matter)

rsearch
├── rsearch_script.py
└── setup.py

In this directory I ran pip install -e . and boom!

No errors, working.

$ rsearch -f foo.txt -b bar
Searching...
Searching...

# ...

$ rsearch --help
Usage: rsearch [OPTIONS]

Options:
  -f, --foo TEXT
  -b, --bar TEXT
  --help

Thanks for the tip @techouse - You were right, I'll use Click for developing Python CLIs from now on.

1 Answers

The entry_points will call your main directly - that is the last main in 'rsearch=rsearch.__main__:main'. This means that the code that calls the args parser never gets called, and hence you get the problem with the arguments never being passed in.

Don't change your setup.py but instead move the args = parser.parse_args() line under the main function.

The if __name__ == __main__ condition will only be run if the script is run directly like you have done, and not in pretty much any other circumstance.

Related