Tutorial 07: Subcommands¶
Note
This step is a bit longer and more involved than the others because it tries to tie together the core concepts in clik, and emphasize the pattern that underlies the library.
The important code updates are all in the first listing. The rest of the step explains what’s going on using silly, verbose examples for the sake of illustration.
To this point, everything we have done would be just as easy using stock argparse. clik really starts to shine when we introduce subcommands:
@app
def todo():
# ... snip ...
# The following lines have been deleted from the example.
# for item in item_list:
# print('*', item)
@todo
def add():
"""Add an item to the list."""
yield
print('hello from add')
@todo(name='list')
def list_():
"""Show the items on the list."""
yield
print('hello from list')
@todo
def done():
"""Remove an item from the list."""
yield
print('hello from done')
Poking around at the application:
$ ./todo -h
usage: todo [-h] [-f FILE] {add,list,done} ...
Command-line application for managing a todo list.
optional arguments:
-h, --help show this help message and exit
-f FILE, --file FILE file in which to store data (default: todo.json)
subcommands:
{add,list,done}
add Add an item to the list.
list Show the items on the list.
done Remove an item from the list.
The list is stored on disk as a simple JSON file containing an array of
strings. The file path is controlled by the -f/--file argument (see
documentation for that argument for more information).
$ ./todo
usage: todo [-h] [-f FILE] {add,list,done} ...
todo: error: the following arguments are required: {add,list,done}
$ ./todo add -h
usage: todo add [-h]
Add an item to the list.
optional arguments:
-h, --help show this help message and exit
$ ./todo add
hello from add
$ ./todo list -h
usage: todo list [-h]
Show the items on the list.
optional arguments:
-h, --help show this help message and exit
$ ./todo list
hello from list
$ ./todo done -h
usage: todo done [-h]
Remove an item from the list.
optional arguments:
-h, --help show this help message and exit
$ ./todo done
hello from done
Neat-o! Gluing all that together with argparse would have been straightforward, but would have involved quite a bit of ceremony and boilerplate.
Subcommands look a lot like the app we’ve been working on to this
point. (There is a reason for this – under the covers they’re actually
the same thing. clik.app.App is a subclass of
clik.command.Command!)
The function decorated by app (todo in our case) can itself be
used as a decorator to register a subcommand:
@todo
def xyz():
# ... do subcommand stuff ...
Note
Once a single subcommand has been registered, it is no longer valid for end users to invoke the application without a subcommand. (Unless a “bare” subcommand has been registered – more on that later.)
Subcommands can also be used as decorators to register sub-subcommands. It’s “turtles all the way down.” An example, with a slew of dummy sub- (and sub-sub- and sub-sub-sub-) commands:
@todo
def foo():
yield
@foo
def spam():
yield
print('hai from foo spam')
@foo
def ham():
yield
print('hai from foo ham')
@foo
def eggs():
yield
@eggs
def alpha():
yield
print('hai from foo eggs alpha')
@eggs
def bravo():
yield
print('hai from foo eggs bravo')
@eggs
def charlie():
yield
print('hai from foo eggs charlie')
Poking around in the shell:
$ ./todo foo -h
usage: todo foo [-h] {spam,ham,eggs} ...
optional arguments:
-h, --help show this help message and exit
subcommands:
{spam,ham,eggs}
spam
ham
eggs
$ ./todo foo
usage: todo foo [-h] {spam,ham,eggs} ...
todo foo: error: the following arguments are required: {spam,ham,eggs}
$ ./todo foo spam
hai from foo spam
$ ./todo foo ham
hai from foo ham
$ ./todo foo eggs
usage: todo foo eggs [-h] {alpha,bravo,charlie} ...
todo foo eggs: error: the following arguments are required: {alpha,bravo,charlie}
$ ./todo foo eggs -h
usage: todo foo eggs [-h] {alpha,bravo,charlie} ...
optional arguments:
-h, --help show this help message and exit
subcommands:
{alpha,bravo,charlie}
alpha
bravo
charlie
$ ./todo foo eggs alpha -h
usage: todo foo eggs alpha [-h]
optional arguments:
-h, --help show this help message and exit
$ ./todo foo eggs alpha
hai from foo eggs alpha
$ ./todo foo eggs bravo
hai from foo eggs bravo
$ ./todo foo eggs charlie
hai from foo eggs charlie
Like the app, the name for the subcommand defaults to the name of
the function being decorated and can be overridden by passing the
name parameter to the decorator.
This is useful for our list command since it’s a bad idea to
redefine built-in functions (which list is). We use list_ as
the function name, and pass "list" to clik as the name it should
use:
@todo(name='list')
def list_():
"""Show the items on the list."""
yield
print('hello from list')
The app, of course, works the same as it did before:
$ ./todo -h
usage: todo [-h] [-f FILE] {add,list,done} ...
Command-line application for managing a todo list.
optional arguments:
-h, --help show this help message and exit
-f FILE, --file FILE file in which to store data (default: todo.json)
subcommands:
{add,list,done}
add Add an item to the list.
list Show the items on the list.
done Remove an item from the list.
The list is stored on disk as a simple JSON file containing an array of
strings. The file path is controlled by the -f/--file argument (see
documentation for that argument for more information).
$ ./todo list -h
usage: todo list [-h]
Show the items on the list.
optional arguments:
-h, --help show this help message and exit
$ ./todo list
hello from list
As you’ve probably noticed, help messages are taken from docstrings.
Like the app, content before the blank line is the description
and everything after is the epilog. As an example, let’s “lorem
ipsum” the help for add:
@todo
def add():
"""
Add an item to the list.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In
congue porttitor ornare. Aenean ac diam ipsum. Sed sit amet
libero ut ligula pretium consectetur eu quis justo. Integer
sollicitudin velit et nunc suscipit laoreet.
"""
yield
print('hello from add')
Predictably, the help text is:
$ ./todo -h
usage: todo [-h] [-f FILE] {add,list,done} ...
Command-line application for managing a todo list.
optional arguments:
-h, --help show this help message and exit
-f FILE, --file FILE file in which to store data (default: todo.json)
subcommands:
{add,list,done}
add Add an item to the list.
list Show the items on the list.
done Remove an item from the list.
The list is stored on disk as a simple JSON file containing an array of
strings. The file path is controlled by the -f/--file argument (see
documentation for that argument for more information).
$ ./todo add -h
usage: todo add [-h]
Add an item to the list.
optional arguments:
-h, --help show this help message and exit
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In congue porttitor
ornare. Aenean ac diam ipsum. Sed sit amet libero ut ligula pretium
consectetur eu quis justo. Integer sollicitudin velit et nunc suscipit
laoreet.
Nice! We are moving right along. Next we’ll take a quick look at aliases before circling back to arguments for our subcommands.