A CLI application is a command line utility to be used through a text interface such as grep or cURL. In this article we’ll be building our own CLI application with Python and the Click package. We’ll also dig into multiple commands and options. You can then learn how to deploy it to a package repository by reading Distributing Python Packages.
You can find the source for this post here.
To get the relevant code in GitLab go into
repository and on the branch
dropdown choose the tag corresponding to this post’s name.
Creating the Script
Let’s start with a simple script that displays the Fibonacci sequence up to the ‘n’ amount. Here’s the equation that we’ll be using for our script:
What we need next is a directory for our project and within this directory
1 2 3 mkdir fibocli cd fibocli touch fibocli.py
I like using virtualenvwrapper to maintain dependencies so let’s also configure an environment.
1 2 mkvirtualenv fibocli touch requirements.txt
fibocli.py paste the following code and try it out. At the function call
__name__ block the integer parameter is the number of iterations
that the sequence should run.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def fibo(num): """Fibonacci sequence""" n = int(num) l = [0, 1] if n == 0: result = l elif n == 1: result = l a = 0 b = 1 for i in range(0, n-1): b += a a = b - a l.append(b) result = l print('result') if __name__ == '__main__': fibo(10) # Credits for the fibo function: http://stackoverflow.com/questions/494594/how-to-write-the-fibonacci-sequence-in-python
Adding the Click package
To add command line behavior to our script we’ll use the Click package. Here’s a quick description from the official site:
Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. It’s the “Command Line Interface Creation Kit”.
pip and freeze update the requirements file.
1 2 pip install click pip freeze > requirements.txt
Now in our script we need to import
click and add some decorators. Click is
based on declaring commands through decorators.
1 2 3 4 5 6 7 8 import click @click.command() @click.option('--num', default=10, prompt='Sequence iterations.') ... # Substitute print('result') with: click.echo('result') ...
Just by adding
@click.command() we are converting a function into a Click
@click.option decorator will enable parameters which are going to
be used as flags such as
--verbose. The option our script has uses a default
and also a prompt which will ask you for the number of iterations and fallback to
10 if there is no input; no need to keep the parameter on our main function call
by the way.
Another useful change we added to our script is the use of
Now run the script with
python fibocli.py and you’ll get a prompt to add the
number of iterations for the Fibonacci sequence. You can also use the
flag to skip the prompt as well as the
--help flag to view some documentation.
Bundle with Setuptools
Using setuptools allows us to make our application distributable. Here’s a few reasons from the official docs:
Setuptools automatically generates executable wrappers for Windows so your command line utilities work on Windows too.
Setuptools scripts work with virtualenv on Unix without the virtualenv having to be activated. This is a very useful concept which allows you to bundle your scripts with all requirements into a virtualenv.
Packaging also allows us to call on the application through the application’s name, no need of calling python or using the script’s path.
To get started add a
setup.py file in the same directory and include the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from setuptools import setup setup( name='fibocli', version='0.1', py_modules=['fibocli'], install_requires=[ 'Click', ], entry_points=''' [console_scripts] fibocli=fibocli:fibo ''', )
You can change the name, version and module name for your current release but
the really important section is under
entry_points. On the left side of the
equals sign (=) we have the name of the script that should be generated, the
right side is the import path followed by a colon (:) with the Click command.
Testing our script
Let’s make sure this is working as expected. All we need is to install our package.
1 pip install --editable .
Now let’s try our application with no flags to get the prompt, later with a
prompt and lastly with a
help flag to view documentation.
1 2 3 fibocli fibocli --num=32 fibocli --help
To use multiple commands we need to attach them to a
group. This is a simple
task which requires a central function (group) and explicitly adding them to
that group through a
To make this happen we will create a new function called cli which will be the group and we’ll also add a hello world function to test this out.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @click.group() def cli(): pass ... @click.command() def hello(): """Say Hi""" click.echo('hello world') cli.add_command(fibo) cli.add_command(hello) ...
Let’s try this out. First we’ll call the
fibocli command to view the suggested
format. Then we will call it again with each command we have in our application
1 2 3 4 5 6 # Get usage suggestion through docs fibocli # Run the fibo command fibocli fibo --num=25 # Run the hello command fibocli hello
The other feature that we’ll test out is adding arguments to our commands. An important thing to note is that Click will not add documentation so you are expected to do so manually. We’ll change the hello world command slightly so that we can add an argument to it and additionally we will also change the directory structure to include commands from another script which is much closer to what you’ll be building.
Let’s start by adding a
__init__.py file to the root of our project which will
let Python treat the directory as containing packages. Also include a
scripts directory with another
__init__.py file inside of it. We’ll also
hello_world.py script under the
scripts directory to test importing
Now let’s remove the
hello() function with it’s decorator from the
script and in
hello_world we can add something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import click @click.command() @click.option('--count', default=1, help='number of greetings') @click.argument('name', nargs=-1) def hello(count, name): """ Name: Hello\n Description: Say Hi\n Arguments:\n - NAME:\n - Everyone you want to greet.\n - Takes multiple strings, i.e. "Jane" "John" """ for n in name: for x in range(count): click.echo('Hello %s!' % n)
Notice the new
@click.argument decorator. This will take a
name argument for
hello command, it will actually take unlimited arguments thanks to the
Before testing the script we need to import it and modify how we are adding it
1 2 3 4 import scripts.hello_world as hel ... # Substitute cli.add_command(hello) with: cli.add_command(hel.hello)
You are now ready to test the final script, don’t forget to try out the multiple
argument option for our
1 fibocli hello "Jane" "Joe" "John" --count=3
We’ve gone from a simple script to a CLI application. We are now using multiple commands and parameters. We can also work with documentation and bundle our script with Setuptools, our next step is distribution. I started digging into this topic due to some heavy use of Python scripts in security and monitoring which was getting out of control, one of the biggest advantages was centralizing and scoping functionality and development in one tool alone but there are plenty other benefits from using CLI applications such as nested and scoped commands, better error handling and some really cool utilities. Make sure to visit the docs to get the most out of this package.