Building your first Python command-line app

Building your own Python app and deploying it to PyPi can seem daunting. It’s not! If you know Python and have a a little familiarity with the command line, you can get an app out the door in a day.

I recently built a small tool to solve for a simple use case: I want an easy way to create new AWS Budgets from the command line. I’ve left some AWS services running that 1.) were more expensive than I thought or 2.) that I’d forgotten to turn off. Budgets make sure this never happens.

I’ll be using code from my app below.

Since this my first time deploying an app on PyPi, there may be mistakes and misunderstanding lurking in the article. Please comment on those below if you find any.

I want to build a command-line app. How do I start?

I’ve used argparse in the past to build Python command line apps, but click seems to be popular in 2018. It facilitates the process of creating CLI apps (less code than argparse), and includes more bells and whistles (styling of output, etc.).

If you’ve ever used Flask, click’s API for tying command line options to code (using function decorators) should feel familiar:

This code can be run like so:

$ python hello.py --count=3
Your name: John
Hello John!
Hello John!
Hello John!

Out of the box, this script also supports the --help option, which prints usage information.

I’ve written my app. Now I want to deploy it to PyPi.

This process was much easier than I expected. I do not want to duplicate the already amazing documentation for packaging and deploying your app to PyPi, so I’ll cover the high level and talk about some of the gotchas I encountered.

The process:

  • Structure your code / files in the right format.
  • Write a setup.py file to describe the package metadata (most of my time was spent here).
  • Create accounts on PyPi’s test and production servers. You’ll use these accounts to upload your code to PyPi.
  • Upload your code to PyPi’s test server. Once you’re 👍, deploy it to production.

I didn’t spend enough time reading through the documentation for the setup.py file, so struggled to get a few things working there.

setup.py learnings and gotchas

I’ll be referring to my setup.py file on GitHub below.

One README for all documentation

If you’re using a README for your documentation elsewhere (e.g. GitHub), you can also use that as the documentation for your PyPi package!

Just read the contents of your README as a string:

with open(“README.md”, “r”) as fh: 
long_description = fh.read()

and within the setup() function, set the long_description to that string, making sure to also set the long_description_content_type to the appropriate type (Markdown, RST, etc.):

long_description=long_description, long_description_content_type=’text/markdown’,

Include sub-packages in the main package

I wanted to break out code into smaller sub-packages. For instance, I’ve written a CloudFormationClient class to manage interaction with AWS CloudFormation, and I’ve broken that out into its own package:

$ ls CloudFormationClient
__init__.py budgets.template.yaml

In order to package this code with your larger application, you’ve got to keep your code in a structure like this, with an __init__.py file at its root.

Next, make sure to import the find_packages function:

from setuptools import setup, find_packages

Then, add the following line in your setup.py file:

packages=find_packages(),

More about the find_packages function here.

Include data / config files within your package

I assumed every file in my package would be included in that package. That’s not the case.

For instance, that budgets.template.yaml file in the CloudFormationClient directory was critical to the tool, but it didn’t get packaged with the code initially.

I had to set this flag in my setup.py file:

include_package_data=True,

From the docs:

If set to True, this tells setuptools to automatically include any data files it finds inside your package directories that are specified by your MANIFEST.in file.

So I had to enumerate everything I wanted to include in that MANIFEST file:

include README.md
include CloudFormationClient/budgets.template.yaml

Packaging a click command-line app

Click includes some helpful docs on packaging your command-line app with setuptools. The entry_points argument to the setup() function must point to the function you want to run when the user runs your CLI app:

entry_points='''
[console_scripts]
awsbudget=awsbudget:cli
''',

That’s it!

Both click and setuptools are clearly documented, so it should be easy for someone with a little Python knowledge to dive right in.

Take a look at some of the setup.py files for your favorite Python package — they can get very complex!

Let me know if you’ve written your own Python package and learned anything worth sharing in the comments below.