Using setuptools to package your Python app

August 5, 2019 ยท 3 minute read

In our previous post, we made a Hello World! app to get us started with Click. You might have noticed though that we needed to do python hello-world.py to run it. Since we’re building CLI apps, that’s just not cool ๐Ÿ˜… and it’s not how CLI apps work anyway. We need to be able to run a single command, something like:

(venv) $ hello
Hello World!

That’s where setuptools comes in. Setuptools helps us bundle our script so we can install it and run it like an app ($ hello), instead of like a script ($ python hello-world.py).

Setuptools is a collection of enhancements to the Python distutils that allow developers to more easily build and distribute Python packages, especially ones that have dependencies on other packages. - Docs

Ok, enough chit-chat. Let’s get this working. The first thing we’ll need to do is add a new file to our project and call it setup.py. We’ll also rename hello-world.py to helloworld.py (remove the dash). I’ll explain why this is necessary when we get to the setup() section. Our project structure should now look like this:

hello-world-cli
    |- helloworld.py  # renamed from hello-world.py
    |- setup.py  # new file

In the helloworld.py, remove the if __name__ == '__main__': section. The code should now look like this:

import click

@click.command()
def hello():
    click.echo('Hello World!')

In the setup.py file, add this code:

from setuptools import setup

setup(
    name='hello-world-cli',
    py_modules=['helloworld'],
    install_requires=[
        'Click',
    ],
    entry_points='''
        [console_scripts]
        hello=helloworld:hello
    ''',
)

Let’s examine what each line of code does.

from setuptools import setup

We need to import setup from setuptools in order to use it.

Next, we call the setup() function and pass in a number of parameters:

name='hello-world-cli'

This’ll be the name of our CLI app.

py_modules=['helloworld']

This tells the setup() where to find our main module to execute. Our main file here is helloworld.py so we set helloworld as the module. We can keep it that simple since we only have one file for now, but as our app grows we’ll replace py_modules with packages=find_packages(). More on that later.

install_requires=['Click']

Our app has Click as a dependancy, so it needs to be installed for it to work. setup() needs to know about this so it can include it as it packages our project. install_requires accepts a list of values, so if we had more dependencies, we would list all of them here.

entry_points='''
          [console_scripts]
          hello=helloworld:hello
      '''

This is the part that saves us the hassle of having to run the app with python ... everytime, and enables us to just execute hello to run it.

entry_points parameters

It’s important to note that if we maintained our previous name (hello-world.py), the setup would have failed at entry_points. This is because python module names shouldn’t have dashes.

We can now install our app in our environment and run it. To install it, run:

(venv) $ pip install --editable .

Then execute the created command:

(venv) $ hello
Hello World!

Pretty neat, right? ๐Ÿ˜ƒ

In the next post, we’ll take a closer look at Click Commands, Options & Arguments, and how we can use them to make our app even better.