Python CLI Tamed — Invoked

Peter Bunyan
4 min readMar 17, 2021
the weather in here
the weather in here

I spent all that money on a graphical user interface, I bought a Macintosh in 1985, and now the latest version wants me to develop with just the 256 icons of ascii.

The terminal is required. Some employers expect a typing speed test and take exception to those with terminal phobia. I am ok with clis. I read the documentation and become proficient with them as tools. But there are so many and as I move from one to the other I forget the arguments and short cuts.

invoke

I have a solution. I have evolved a work practice that includes the package Invoke. Once you have mastered a tool, encapsulate your hard won knowledge in tasks and rule the day. The documentation is excellent, but let me give you a taste.

setup

mkdir tamed
cd tamed
python3.9 -m venv .
. bin/activate
pip install invoke
echo '""" our development tasks """' > tasks.py
echo 'from invoke import task' >> tasks.py

Here we create a directory called tamed, move to that directory and install a virtual environment, which we activate. Then we ask pip to install invoke. Finally, we create a file called tasks.py and place within a comment and import.

after setup
after setup

Calling invoke now will result in:

% invoke --list
No tasks found in collection 'tasks'!

So let’s add some tasks.

tasks

First we need a hello world:

hello task
hello task

You’ll notice the underscore first parameter, that’s the context and we’ll address it later. But for now, let’s see the help for this task:

% inv -h hello

Usage: inv[oke] [--core-opts] hello [--options] [other tasks here ...]

Docstring:
print message

Options:
-m STRING, --message=STRING

Now, let’s call it:

%inv hello world
hello world

Since the terminal is now the window on your world we’ll need an essential tool to know what it’s like in there:

weather task
weather task

Some things to note here: we have a flag for the short format, we’ve added help to our task and we’re using the context. The context allows us to run subprocesses. Here we’re using curl. I’ve imported urllib.parse at the top of the file and quote what is passed in as the location. Short will be a boolean and we can test its truthiness to append the format argument. Now who remembers that curl takes the argument -s for silent mode and not -q for quiet mode - for that matter who knew you could get rid of the progress information? I called up the man page to remember how to get rid of it for this article! Now I no longer have to remember as it’s encapsulated in my task! Oh, and the weather here:

% inv weather 'milford haven' -s
milford haven: ⛅ +13°C

the hard part

I recently was asked to build a python package with sub-packages that could be delivered independently, with their own versions. So I immediately went to the documentation and read up on sub-packages: Packaging namespace packages

From this I chose pkg_resources-style namespace packages as it supports zip-safe and we’d be using resources within the package such as templates and icons. The resultant directory structure looks like this:

starwars package
starwars package

And yet, properly installed, you import starwars.goongas.main. When asked to add another sub-package… I have a task for that!

First we have to create a subdirectory within a package directory within a subdirectory of the package directory. The __init__.py of the embedded package directory needs to declare a namespace and our version resides in the subpackage’s __init__.py . Then we create a config file for bumpversion and a simple setup.py , add our creation to dev.txt , make a directory for the new tests and finally instruct the user to run pip and start testing!

By now you must see that I could not possibly remember all that. But I put it in a task and have used it often on more than one project. It looks like I also use bumpversion.

Another useful parameter to task is pre; a chain of tasks to invoke before invoking this task. For example, a release task might have pre=[clean, lint, test, build]. If any task or ctx.run fails the task exits. So if test fails release will never be invoked. Invoke, also, has a concept of collections that allow you to create groups of tasks, for example: db tasks and documentation tasks. It’s worth a look: Loading collections.

Some do not like the underscore policy of invoke, as in changing them to hyphens, and so there is an adaptation for that: raft

Going forward

I just used this subtitle to annoy my brother. In the Invoke documentation it explains how to write your own command line tool. When you publish it, I will likely have to learn it, and encapsulate my usage of it in my own tasks, but have a look. Using Invoke as a library. I’m afraid Invoke has not cured my terminal phobia, but the cli is now my friend.

--

--