Python CLI Made Easy
Since the day I started learning and working with Python, argparse was the de-facto module to write CLI (Command Line Interface) scripts. Recently, however, I came across another Python package which helps in creating CLI scripts in a very simple way: Click (Command Line Interface Creation Kit).
What this article isn’t about
This is not a comparison between argparse and click, or any of the other modules which enables command line interface implementation in Python.
What this article is about
We are going to see how to build a simple command line interface using click’s features.
Let’s go!
Installation
Business as usual. Just pip install click
and you are good to go.
It’s generally a good practice to use a virtual environment to have a nice separation between your different Python environments, and not to clutter a single environment with tons of packages that you might not need in all applications.
First, run pip install virtualenv
to install the package. Then make a new folder for our little script and create a virtual environment venv
inside:
$ mkdir clickdemo
$ cd clickdemo
$ virtualenv venv
And now simply activate the new environment. On Mac OS X or Linux:
$ . venv/bin/activate
On Windows:
$ venv\scripts\activate
Now you will see that the environment’s name is indicated in your terminal
(venv) $
we can then install click inside our new environment by running pip install click
.
Basic Example
Let’s implement a script clickdemo.py
that simply says “Hello!”
#clickdemo.py
import click
@click.command()
def greet():
print("Hello!")
if __name__ == '__main__':
greet()
Now we run it and check the output:
(venv) $ python clickdemo.py
Hello!
Easy-peasy.
One of the cool things about click, is that it automatically generates a help message for your script:
(venv) $ python clickdemo.py --help
Usage: clickdemo.py [OPTIONS]
Options:
--help Show this message and exit.
And updating the help message is just as easy, simply add a docstring to your command function:
@click.command()
def greet():
"""Greetings stranger!"""
print("Hello")
Now let’s try again:
(venv) $ python clickdemo.py --help
Usage: clickdemo.py [OPTIONS]
Greetings stranger!
Options:
--help Show this message and exit.
Cool! We have a basic understanding now on how to create a simple command line script. How about adding some parameters?
Optional Parameters
A.k.a.. well.. Options.
We want users to optionally provide their name and age.
@click.command()
@click.option('--name', '-n', default='Stranger',
type=click.STRING, help="Your name", show_default=True)
@click.option('-a', '--age', default=None, type=int, help="Your age")
def greet(name, age):
"""Greetings stranger!"""
print(f"Hello {name}!")
if age is not None:
print(f"You are {age} years old.")
A few things to notice here:
- You can specify a default value for your option, as well as a type
- Setting the
show_default
flag will show the default value in the help message - We need to create a new Python argument in the decorated function for each option we give to the command line
When it comes to naming those arguments, there are some points to keep in mind:
- If a name is not prefixed, it is used as the Python argument name and not treated as an option name on the command line.
- If there is at least one name prefixed with two dashes, the first one given is used as the name.
- The first name prefixed with one dash is used otherwise.
Mmm okay, what does that mean exactly? Let’s look at some examples:
@click.option('-n', '--name') -> Function argument name: `name`
@click.option('-n') -> Function argument name: `n`
@click.option('-n', '--name', 'username') -> Function argument name: `username`
@click.option('--UserName') -> Function argument name: `username`
@click.option('--name', '--alternative') -> Function argument name: `name`
@click.option('-n', '-nm') -> Function argument name: `n`
@click.option('---name') -> Function argument name: `_name`
We can add single value boolean flags by setting is_flag=True
:
@click.command()
@click.option('--name', '-n', default='Stranger',
type=click.STRING, help="Your name", show_default=True)
@click.option('-a', '--age', default=None, type=int, help="Your age")
@click.option('-j', 'jedi', is_flag=True, default=False,
help="Are you a Jedi Master?")
def greet(name, age, jedi):
"""Greetings stranger!"""
greeting = f"Hello Master {name}!" if jedi else f"Hello {name}!"
print(greeting)
if age is not None:
print(f"You are {age} years old.")
There is another way of specifying boolean flags by defining two flags in the same time separated by a /
. So this last example would look like this:
@click.command()
@click.option('--name', '-n', default='Stranger',
type=click.STRING, help="Your name", show_default=True)
@click.option('-a', '--age', default=None, type=int, help="Your age")
@click.option('--jedi/--not-jedi', default=False,
help="Are you a Jedi Master?")
def greet(name, age, jedi):
"""Greetings stranger!"""
greeting = f"Hello Master {name}!" if jedi else f"Hello {name}!"
print(greeting)
if age is not None:
print(f"You are {age} years old.")
And this is how we would use it:
(venv) $ python clickdemo.py -n Anakin --not-jedi
Hello Anakin!
There is a lot of other features that you can use, such as multiple options, counting, choices, etc.. I encourage you to check them out in the options documentation.
Mandatory Parameters
A.k.a Arguments.
Sometimes we want to have mandatory positional arguments in our script. For instance, writing the output to a file. Let’s see how to achieve this:
@click.command()
@click.option('--name', '-n', default='Stranger',
type=click.STRING, help="Your name", show_default=True)
@click.option('-a', '--age', default=None, type=int, help="Your age")
@click.option('--jedi/--not-jedi', default=False,
help="Are you a Jedi Master?")
@click.argument('filename', type=click.STRING)
def greet(name, age, jedi, filename):
"""Greetings stranger!"""
greeting = f"Hello Master {name}!" if jedi else f"Hello {name}!"
print(greeting)
if age is not None:
print(f"You are {age} years old.")
print(f"The output will be saved to {filename})
The argument can then be specified on the command line as follows:
(venv) $ python clickdemo.py -n Anakin --not-jedi output.txt
Hello Anakin!
Your output will be saved to output.txt
Click handles reading and writing to files for us through the click.File
type. We just need to specify whether we want to open the file for reading r
or writing w
.
@click.command()
@click.option('--name', '-n', default='Stranger', type=click.STRING, help="Your name", show_default=True)
@click.option('-a', '--age', default=None, type=int, help="Your age")
@click.option('--jedi/--not-jedi', default=False, help="Are you a Jedi Master?")
@click.argument('input', type=click.File('r'))
@click.argument('output', type=click.File('w'))
def greet(name, age, jedi, input, output):
"""Greetings stranger!"""
greeting = f"Hello Master {name}!" if jedi else f"Hello {name}!"
print(greeting)
if age is not None:
print(f"You are {age} years old.")
info = input.read()
output.write(info)
Testing it on the command line:
python3 clickdemo.py -n Anakin --not-jedi -- - output.txt
Hello Anakin!
test one line
and another one
Here we are accepting input from the command line by specifying -
as the input argument, and writing it back to output.txt
. Note that we have defined the command line parameters in a different way here, by using the --
string to separate between the list of options and the list of arguments.
Read more about Arguments in the arguments documentation.
Click has a lot of other cool capabilities that make CLI scripts very easy to implement. I recommend reading the official documentation and try experimenting with the different features.
And that’s a wrap. Thank you for making it all the way to the end.
Till next time!