Let’s Create A CLI With Python — Part 2

Stav Shamir
4 min readApr 11, 2019

--

This part is mostly about how to use the requests library to wrap an API with python functions.

In the last part we’ve created a skeleton for a CLI wrapper for the public API of public APIs and achieve a program that does the following:

But it doesn’t actually do what it promises yet. Let’s fulfill these promises — we will use the popular requests package to make REST calls to the API. So install it if you need and add import requests to apis_cli.py and let’s continue.

Categories

We will start with the “categories” command since it’s the easiest to implement.

If we look at the readme again we’ll see that there is a “base url” section with the base url of the API. When we add the endpoint (/categories) to it we get https://api.publicapis.org/categories. Go on and click it — the browser actually makes a GET request to that url.

So now we can make this request programmatically in our program with requests.get. This function return a response object which has an attribute text, so we’ll print it to get the actual list of categories:

You can now run “apis categories” (if you’re still using the alias from part 1). You should see that the categories are printed, but they are not presented in a user-friendly way. Well, we can and should fix that — a CLI utility has to be convenient to use if you want people other than yourself to use it.

Conveniently, the returned text in the response is actually a JSON object — and conveniently the response objects has a json() method which we can use. Please try it instead of text, and see that it’s still not what we want. It actually parses the text response to an array, and if we want to print it pretty we can do something like this:

Printing it like this has the nice benefit of facilitating the creation of an auto-complete script, but more on this on the next part.

Great, our categories command is done! Well, we can do a little bit better — you may have already realized that the base url will be used by all the commands, so it is better to extract it to a variable:

Looks better. But when we run it we get this nasty thing:

You may have caught the reason — during the refactoring I accidentally dropped the “c” from the “categories”. You don’t want the user to be exposed to the traceback of exceptions — it’s something that has to be handled gracefully. In this case the problem is that the server did return a response, but since “ategories” is not a valid endpoint it returned with a status code of 404 (not found), and a text which is not a JSON object. Whenever you are using requests, you should check if the response status is 200 (OK) before using the response as if the request was successful:

Now when you run the command you should get a nicely printed list of the categories. Remove the “c” from “categories” again, and see how it behaves now. Better, isn’t it?

Entries

Let’s implement the entries command — let’s begin with something similar to the previous command, without handling the parameter title, category and auth yet:

I have added some code to have the results printed prettily and not as JSON objects ). Try running the command “apis entries” now, and see how it works. If you have trouble understanding this code (specifically line 10) read about tuple comprehensions of dictionaries.

If you ran the command you’ve seen that we get over 600 results — it is not convenient. Let’s handle the parameters now so the command becomes useful for the user. The requests functions receive an argument params which is a dictionary mapping the expected parameters name to their value. With title and category it’s a no-brainer:

You can now run “apis entries -c vehicle” or “apis entries -t cat” or use the long options and any other combination and see how the responses change. Nice, right?

The auth parameter is a bit more complicated:

auth type of entry (can only be values matching in project or null)

We decided we want the auth parameter to be boolean — we either don’t care about which auth type it is or we want only APIs where there is no auth. Since the value sent to the API must be a valid one or null, we can’t simply map ‘auth’ to the value received from the user (which is True or False). We need to add the mapping ‘auth’: ‘null’ conditionally:

And now you can check out the command with the “-a” flag, and I believe our entries command is fully implemented.

Since the random command is very similar, I will leave it as an exercise for you to implement — the full code is available at branch part2.

Conclusion

We implemented the categories and entries commands. We’ve seen how to use requests and how to perform some minimal error handling. We’ve also seen how to print the JSON result nicely and how to add parameters to the request.

In the next part we will see how to complement your program with a bash auto-completion script.

--

--