Create Your First CLI Application With Python Click
A simple guide to building command-line interfaces in Python

Life at the terminal is sweet. It’s fast powering up tasks, makes multitasking feel remarkably easy and allows you to keep resources that would otherwise be used by a fancy, but unnecessary, GUI.
Think about having to load up a graphical interface Version Control tool every time you want to track a change in your file — it’s scary how time-wasting that is. That’s our need for command-line interfaces on applications. When you get the itch to create a CLI for an application for the use of you or your team, you scratch it, of course, by building one!
In this article, well explore Python Click for creating command-line interfaces. We’ll familiarize ourselves with Click’s most useful features and see them at play. To do so, we have to get our hands dirty by creating a media file convertor CLI application — a little dirt never hurt!
If you would like to jump right into the complete project first, the code is here.
In the first part of our Python click tutorial, we cover a general but pretty cool Command Line Interface features undirectly related to click. If you’re about to create your first CLI app, you’ll find this bit useful. But for a direct dive, go straight to the section Bringing Click into the Picture.
So, Why a Command-Line Library?
Click is a Python library. It’s possible to create your own CLI from scratch. Take a look at this prompt:
$ >>> Make the world go round?
A little [default]
Yes
No
It’s a simple prompt that asks the user to select from three available choices. We can simply have this with the command below:
user_choice = input('Make the world go round?\n' +
'A little [default]\n' + 'Yes\n' + 'No\n')
Just like that, we can interact with the user at the command line. We’re taking a leap of faith and hoping that the user gives us a response from the available choices — which may not happen. This takes us to the issue of parsing user inputs.
We have to be ready for the user who replies “spin it” or something fancier like “I’m not sure”.
By looping with that condition, we restrict our user to giving us one of the three options. That should do it.
But…remember we wanted “A little” to be our default option if the user doesn’t specify anything. We tweak our code a bit to accommodate this. We should end up with something along these lines:
It might lessen the excitement, noticing how much patience it takes to parse a single user input. Most of the effort has gone into handling side cases of users who don’t like to follow instructions.
It would also be better if the app could specify the error in their input, rather than just repeat the same line — another consideration.
It’s possible to do this for a complete CLI app — parse each prompt, have descriptive error messages, give the user frequent feedback (we hate guessing — remember the last time you used dd). Importantly, we should be asked if we’re sure when we’re about to do something that seems silly.
We’re getting our hands dirty. We should be focusing on feature delivery. Command-line libraries are here to take away this pain so we can focus on the cool part. Remember, dirt doesn’t hurt — as long as it’s just a little dirt!
Jumping Into Argument Vectors (Argv)
In the example spinning the world, we used input
to interact with the user. However, it would make more sense to allow the user to supply their input directly when launching the application.
To run what we currently have, we would use:
>>> python spin_world_v2.pyMake the world go round?
A little [default]
Yes
No: # User gives input
The user has to wait for the app to run to give their desired input. What we think would be a well-behaved script is:
>>> python spin_world_v2.py 'a little'# Then the world spins. Of course, a little
That way, we won’t have to wait for an input prompt to ask the user if they want the world to spin. We use argv
to do this. Shall we tweak our first script a little more?
Running the script produces the following results:
Option 1 - Known argument
>>> python spin_world_v2.py 'a little'
# Output [spin_world_v3.py 'a little'] # Our argv list Spinning this thing a little
Option 2 - Unknown argument
>>> python spin_world_v2.py 'some more'# Output [spin_world_v3.py 'a little'] # Our argv list
Use with: a little yes, or no. Cool?
We’ve created a command-line script. All command-line apps use the argv concept when requiring the user to supply an argument while starting the application. Now, it will be sure to click when we have a look at the same concept, but using click library.
Interface Options
We use the shell ls
all the time. We can learn some tips about interfaces by having a look at it.
Running ls
in a terminal window:
ls
It’s a simple tool. We can use it without calling an argument and be happy.
But would the use of an argument make us happier? Let’s see.
ls -l

Here, we made the tool a little noisy by requesting details. It throws at us the file permissions, the owner, the group, size, time, and name. It worked! In this case, -l
is an option. Let’s see docs on its use:
>>> ls --help
Usage: ls [OPTION]... [FILE]...# Run this to see the complete [OPTIONS] list
We see ls
takes only two arguments — the option, and an optional path. We can vary the taste of information we get on our files and sub-directories by playing around with the listed options.
Interface Commands
ls
has options. However, we can say it has no commands. The use of commands is to group related options of a CLI.
tool command --option
We can have hundreds of options in hundreds of commands. Why not place everything in a single list of options? That’s like filling your house with traps so trap admirers can come and visit you! Maintenance of your application will be difficult and users will find it hard to work with more complex features.
Let’s go back to our world spinning example and see something cool about commands.
The spinning of the world is in our control. Perhaps we can specify when the spinning occurs? Our script could have an interface command that allows us to set the time the spinning should take place.
python spin_world_v3.py time 0900 1700
Here, we set the time interval for which we want the world to spin. From 9 am (0900 hrs) because that’s when we take breakfast, to 5 pm (1700hrs) when we should be leaving work. Since these are working hours, we might want the world to spin faster than usual during this period.
So we give our application a spin command where we can specify the spinning speed.
python spin_world_v3.py time 0900 1700 --speed 1500m/h
This has given us a simple understanding of the CLI commands concept.
Bringing Click into the Picture
We’ve looked at how command-line tools work and the basics of creating a command-line interface.
Now, let’s create our media-convertor-CLI app. Our application should allow us to convert a media file to a format of our choice. We often use video to audio-conversion tools— it will be exciting to have one that we can tweak ourselves.
App setup
Our directory structure will look like this:
| audioConvertor |-convertor
|- __init__.py
|- utils
|- __init__.py
|- cli.py|- tests
|- __init__.py- setup.py
We create the interface of our app in the file cli.py
.
Adding usage options
To convert a file, we need to know its location. So we need to allow the user to tell our application where to get this file. We start building our app by creating a simple script that does that.
We give our users an option --input_directory
to specify which file to convert. We can run the script as follows:
$ python cli.py --input_directory Videos/musicVideo.mp4# Output
Videos/musicVideo.mp4
Our script echoes back the file the user has specified. It works!
How will our users know how to interact with the app? A short help menu works a charm. With Click, we get this easy and for free. Let’s check it out by running:
$ python cli.py --help# OutputUsage: cli.py [OPTIONS]audioConvertor is a command-line tool that helps convert video files to audio file formats.example: python cli.py -i input/file/path -o output/pathOptions:
-i, --input_directory TEXT Location of the media file to be converted
--help Show this message and exit.
Note the convenience. -i
is also interchangeable with --input_directory
. The help example says an output path is necessary too.
example: python cli.py -i input/file/path -o output/path
We can get it working this way by adding the following lines to our script:
The added lines are marked ++
. Adding an option is as easy as calling @click.option
and passing our string to the decorated function. Remember to pass your options to main
in order of creation.
Parsing user options
It would be good manners to confirm the existence of a file before we attempt to convert it. We can do so like this:
The script should now terminate if a user provides a non-existent media file. But with Click, parsing and validation are handled for us. The snippet of code below serves the same purpose as what we’ve just done above:
The magic happens at line six. By supplying the type
parameter to Click’s options command, we can tell Click to ensure the user gives as --input_directory
as a path, and that it exists.
Click gives us plenty of arguments to use in an option. We get to see more of them in play as we progress.
Adding commands
Using commands will allow us to isolate different features of our application for the convenience of users. It also makes it easier to add new options. For example, we have two options — it would make sense to nest them in a command that describes what they do.
python cli.py convert -i input/file/path -o output/path
We want to bundle our options in a convert
command, so our app can be used as shown. What’s something else we would want our app to do besides converting? If we could play our converted songs, that would be nice. So, if we use it as shown below, something should pop out of our speakers:
python cli.py play --playlist path/to/audio
We know how we want our app to work. Let’s get to it.
Grouping the commands
The first thing we do is add the commands play
and convert
to our app.
Let’s go through this script.
We have three functions: — main
, load_files
, and load_audio
. The function main
is helping us group our two commands with Click by decorating it with @click.group
. This is our first step.
After that, notice how easy it is to add a command — we call @main.command
then specify the options we think will make us happy.
There’s this line hovering around:
@click.pass_context
Whenever we want to use an argument specified in command but in a different function, we pass its context. This is done by storing it in the click context object dictionary. See what we do in line 21:
ctx.obj[‘VERBOSE’] = verbose
This allows us to access the value of VERBOSE
in any other function with a click decorator by passing in ctx
. (Remember to call main with the obj
argument as in line 52). For example:
def load_files(ctx, input_directory, output):
"""
: Convert video file input to audio.
"""
if ctx.obj.get('VERBOSE'):
# shout a lot
else:
# convert quietly
We introduced some new arguments to our command options too. Here’s what they help us achieve:
required
: mandatory input.multiple
: allow users to add many parameters e.g convert -i video_1 -i video_2.nargs
: almost same as multiple e.g convert -i video_1 video_2 video_3
In our case, nargs
would help us specify multiple input files.
is_flag
: A boolean option that doesn’t need a parameter`.
Convert — option
Let’s list out all our decorators for function (load_files)
running the convert command.
That’s a list of options for one command on our CLI. Now let’s dive into processing the parameters received from the user. For easier maintenance, we will isolate all our processing functionality to a new file, in a class called Convertor
.
| audioConvertor|-convertor
|- __init__.py
|- utils
|- __init__.py
|- cli.py
|- formats.py|- tests
|- __init__.py- setup.py
Since our focus is on the CLI, we won’t dwell on the Convertor
class. But I made an effort to document it quite nicely, so don’t worry about it.
Let’s add these lines to the top of our cli.py
:
import click
from formats import Convertor # +convertor_instance = Convertor() # +
Let’s toy around a bit on our interface with the above options:
$ python3 convertor/cli.py convert -i /root/Videos/# Output
/root/Videos/ is a directory. --recursive Needed for a directory
Let’s point our app to a file and attempt to convert it again:
python3 convertor/cli.py convert -i Videos/snoring_noises.avi
Input specified as file name.
.
Conversion Complete
saved: snoring_noises.mp3
The app should prompt us to install ffmpeg
, which is the media convertor library we shall use. This is getting exciting!
Next, let’s allow the user to convert multiple video files at once by running:
python3 convertor/cli.py convert -i /root/Videos/* -o /root/converted_music
This command should convert for us all video files in the Videos folder to audio and save the output to the directory converted_music
. Adding the following code to the load_files
function should do it:
Play — option
The play command decorates the load_audio
function:
The app should now allow us to load a playlist of our choice.
Chaining Commands
Whenever we want to convert a file then play its audio, our CLI application restricts us from running the convert and play command individually. That’s good enough, but wouldn’t it be better if we could use the interface as follows?
python convertor/cli.py convert -i my_video_file.mp4 play
To use more than one command at a go, we pass the chain
argument to our multi-command:
@click.group(invoke_without_command=True, chain=True)
That’s just the first step. We are invoking convert
first. When we run play
later, the location of our converted file shall need to be known. For every successful conversion, our interface should know the path of the saved file(s).
ctx.obj[‘PLAYLIST’] = convertor_instance.get_file_save_path()
We add the PLAYLIST
key as a context accessible to all our sub-commands. get_file_save_path
is the function that does this. The above line shall be added in two places in the load_files
function:
Some Additional Considerations
Individual and multiple file conversion
To differentiate between the single and multiple file inputs, one tweak would be allowing the user to set a recursive option. The recursive
flag, during processing, will let us know if we are to convert a single or more than one file.
Additionally, to check if the input is a directory, we recurse through all its children and find any valid file matching a video format.
if os.path.isfile(input_directory) and not recursive:
# convert single file
else:
# convert multiple files
That solves it for us. We can even match input files recursively using the wild-card, *
:
python convertor/cli.py convert -i Videos/snoring*.mp4 play
Optional arguments
If we attempted to run the above command, we might get an output such as this:
python convertor/cli.py convert -i Videos/snoring*.mp4 playUsage: cli.py play [OPTIONS]
Try "cli.py play --help" for help.Error: Missing option "--playlist" / "-p".
It means playlist
is a required argument. To make it optional, required set to False
as follows:
@click.option('--playlist', '-p', required=False, type=click.Path(exists=True),
help="Folder containing audio files to be played")
Conclusion
That’s it, folks.
We’re now familiar with command-line interfaces and have learned how to interface an application to the terminal, using Click. I hope this Python Click tutorial has helped you gain something exciting and useful. Remember you can have a tour of the complete project on its GitHub repo.
You are awesome. Make your next CLI better.