Let’s Create A CLI With Python — Part 1

Stav Shamir
5 min readApr 3, 2019

--

In this post we will start building together a CLI program with python — by the end of the series you will be able to build an easy to use CLI wrapping a public API, install it in bash, and have auto-completion for it!

Motivation

I have previously written a post about becoming a more efficient programmer by practicing:

  1. Writing a developer journal
  2. Automating repeating tasks with scripts
  3. Sharing the results with your team

Building a good CLI (emphasis on the good) can be the culmination of the three points above — it packs your scripts into one useful tool that is easy to share, and can function as a living document for journal entries describing important flows.

A few more words on what to expect before we start — we will be using python 3.7 with the lovely python click library for building CLIs. I will be using python type annotations (because I find them useful), but you don’t have to understand them to follow this series and may ignore them if you like.

In this post we will focus on building the skeleton for our CLI with click, and in the following posts we will implement the functions, learn how to distribute the CLI (in a somewhat less conventional way), and finally add auto-completion.

Here’s a teaser for the final product:

Let’s Start!

I looked for a public API to use for this series and found a nice repository with a collection of public APIs. It turns out that this collection has its own public API, and I could not pass the opportunity of using something so meta, so we will be creating a CLI wrapping the Public API for Public APIs.

For this type of tasks I find a top-down approach to be most fitting — we will look at the endpoints provided by the API, decide which ones we want to have in our CLI, and create the skeleton — meaning, we will create all the CLI commands and options, but they will not do anything yet.

Our chosen API provides a way to explore a collection of public APIs (you’re encouraged to take a peek at its readme to get a sense of what it contains) — so I think the name “apis” fits nicely as the name of our command.

The following endpoints are provided:

  • GET /entries — List all entries currently cataloged in the project (we can provide parameters to narrow down the list)
  • GET /random — List a single entry selected at random
  • GET /categories — List all categories
  • GET /health — Check health of the running service

I think it makes sense that we provide commands for the first three endpoints, but use the fourth one only internally to handle errors.

Creating the main command “apis”

Now with some of the design in our head we can finally start coding!
Create a new project called apis_cli, initialize a git repository, and add a file with name apis_cli.py.

I should mention I am working with PyCharm and venv, so I will not go into how to install external dependencies. I recommend using venv as well so you could follow along with the future post about how to distribute your CLI.

With that being said, install click and write the following code:

This is all we need to have our main command “apis” up and running. If we run it we can see that we get a helpful description using the docstring:

Using the prog_name argument allows you to customize the name of the command appearing in the Usage line of the the description. Remove this attribute to see the default name.

By the way, you may notice that I’m using the “apis” command directly — if you also want to use it like that, simply create an alias for “python apis_cli.py” (or whatever the path and python your are using). Later we will learn how to use the command name directly without an alias.

Adding the sub-commands

Now let’s add sub-commands. We can use the apis function as a decorator for other functions, which will make them sub-commands:

Now add the functions for random and categories.

If you did take a peek at the readme, you might have seen that the “entries” and “random” endpoints take the following parameters:

  • title
  • description
  • auth
  • https
  • cors
  • category

What parameters should we support? Title and category are obvious ones. Regarding the others, I think auth can benefit us as well, since it’s much easier to work with APIs with no auth required, so it makes sense to give the user option to filter out APIs requiring auth. However we don’t care about the type of auth, only if it is required or not, so instead of taking a string like the endpoint does, we will use auth as a boolean flag.
With that in mind, let’s add our “entries” and “random” sub-commands.

Boolean option “auth”

The first two arguments to the decorator define how the options is used with the command. The value of the long option ( — no-auth) also defines the name of the argument in the function to which the option’s value will be injected. In this case:

And two string options “title” and “category”:

Add the same decorators and arguments to the “random” function.
Now try running the script with “random — help” (every command built with click has a builtin — help option). You are supposed to see something like this:

You can check out the complete apis_cli.py in this repository. Make sure your on branch “part1” as other branches will be added with the next parts of this series.

Summary

We got the skeleton of our CLI — great job! Let’s summarize what we’ve done:
1. Examined the API’s endpoints and decide which ones to support
2. Mapped the endpoints the click commands
3. Decided which parameters to support for each command
4. Add the parameters with click.option decorators and command function arguments

Next time we will create a wrapper for the API and have our CLI actually do something!

--

--