Tutorial 09: Arguments, Again¶
Adding arguments to the subcommands should look familiar:
@todo
def add():
"""Add an item to the list."""
parser.add_argument(
'item',
default=None,
help='item to add (prompts if not supplied)',
nargs='?',
)
yield
print('item:', args.item)
# ... snip ...
@todo
def done():
"""
Remove an item from the list.
If no arguments are supplied, the current list is printed and
the program prompts for the index of the item to remove.
"""
group = parser.add_mutually_exclusive_group()
group.add_argument(
'-a',
'--all',
action='store_true',
default=False,
help='remove all items from the list',
)
group.add_argument(
'-i',
'--index',
default=None,
help='0-based index of the item to remove',
type=int,
)
yield
print('index:', args.index)
print('all:', args.all)
We use the same parser and args variables to configure and
access arguments in subcommands! This is part of the “magic” provided
by clik. When used in the todo function, parser refers to the
top-level parser for the app. When used in a subcommand, parser
refers to the subparser for that subcommand.
Note
This type of magic will turn off some Pythonistas; it’s totally cool if you feel a little dirty and maybe a little angry right now. I mean, I wrote the thing and I’m still not totally sure how to feel about this part of it. All I can say is: I’ve been using this pattern for a few years now as clik has taken this final shape and, except for the occasional ringing of “explicit is better than implicit” in my head, it’s been quite pleasant.
The todo app now has the UI we sketched out at the very
beginning:
$ ./todo add -h
usage: todo add [-h] [item]
Add an item to the list.
positional arguments:
item item to add (prompts if not supplied)
optional arguments:
-h, --help show this help message and exit
$ ./todo add
item: None
$ ./todo add "Wash the car"
item: Wash the car
$ ./todo done -h
usage: todo done [-h] [-a | -i INDEX]
Remove an item from the list.
optional arguments:
-h, --help show this help message and exit
-a, --all remove all items from the list
-i INDEX, --index INDEX
0-based index of the item to remove
If no arguments are supplied, the current list is printed and the program
prompts for the index of the item to remove.
$ ./todo done
index: None
all: False
$ ./todo done -i 2
index: 2
all: False
$ ./todo done -a
index: None
all: True
$ ./todo done -a -i 2
usage: todo done [-h] [-a | -i INDEX]
todo done: error: argument -i/--index: not allowed with argument -a/--all
To show that parser is, in fact, different for the different
subcommands, let’s try to use an argument in done that is defined
in add:
@todo
def done():
# ... snip ...
yield
print('item:', args.item) # defined in add
In the shell:
$ ./todo done
Traceback (most recent call last):
File "./todo", line 83, in <module>
todo.main()
# ... snip traceback ...
AttributeError: 'Namespace' object has no attribute 'item'
So that’s most of what makes clik clik: parser, args, and
subcommands.
We’re now in the home stretch! Just a couple more steps and the application will be ready to ship.
(Also, I’d like to take this chance to thank you for continuing to read. I didn’t know whether you’d be angry about that magic globals thing, and honestly I was a little afraid to bring it up with you. But now that we have that behind us and we’re all cool, let’s finish up this app shall we?)
Let’s circle back and make the list command print the items loaded from the data file.