Basic Usage
We’re going to create a small CLI with a single (deeply-nested) greet command/function:
<mycli> topic cmd subcmd greet ...
<mycli> is the placeholder for the name of the executable (in this case it’s just going to end up being python -m mypkg.cli). topic is a command (we’re already 1 level deep!). cmd is a subcommand of topic. subcmd is a subcommand of cmd (sub-subcommand of topic). Finally, greet is our actual function.
Normally we would need a fair bit of boilerplate to wire this up, but we’ll see how multicommand makes this super easy.
Setup
Create a directory to work in, for example:
mkdir ~/multicommand-sample && cd ~/multicommand-sample
Install multicommand:
python3 -m venv ./venv
source ./venv/bin/activate
python3 -m pip install multicommand
Create the subpackage to house our parsers:
mkdir -p mypkg/parsers/topic/cmd/subcmd
Note: That’s a long path. Feel free to skip over this note and continue, but if you’re perplexed by this directory structure here’s a high-level explanation:
Part of that path should already be looking familiar. (The
topic/cmd/subcmdpart - that’s no coicidence!) Basically,mypkgis the name of what will become our installable package (i.e. we’ll eventually be able toimport mypkg).The folder
mypkg/parsersis going to be a sub-package (i.e it’s going to contain an__init__.pyfile - in fact, all these folders will be sub-packages). This sub-package (mypkg.parsers) will be the thing that we pass tomulticommand, from which we’ll get our configuredargparse.ArgumentParserinstance.As for the remaining folders,
multicommandwill use those to create the command hierarchy that we’re after.
Create the *.py files we’ll need.
touch mypkg/__init__.py
touch mypkg/parsers/__init__.py
touch mypkg/parsers/topic/__init__.py
touch mypkg/parsers/topic/cmd/__init__.py
touch mypkg/parsers/topic/cmd/subcmd/{__init__.py,greet.py}
The code
First, add a parser to greet.py:
import argparse
# using this handler we'll be able to tell this parser how to "handle itself"
def handler(args):
greeting = f'Hello, {args.name}!'
print(greeting.upper() if args.shout else greeting)
parser = argparse.ArgumentParser(
description='Show a greeting',
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('name', help='Name to use in greeting')
parser.add_argument('--shout', action='store_true', help='Yell the greeting')
parser.set_defaults(handler=handler) # link the handler to this parser/command
Second, add an entrypoint (this is the module we’ll run from the command line):
touch mypkg/cli.py
with the following content:
import multicommand
from mypkg import parsers
def main():
# pass the module 'mypkg.parsers' to multicommand for it to make us a parser
parser = multicommand.create_parser(parsers)
args = parser.parse_args()
if hasattr(args, 'handler'):
return args.handler(args)
parser.print_help()
if __name__ == "__main__":
exit(main())
Third, there is no third step! Let’s try it out!
$ python3 -m mypkg.cli
usage: cli.py [-h] [command] ...
optional arguments:
-h, --help show this help message and exit
subcommands:
[command]
topic
Take a look at our greet command:
$ python3 -m mypkg.cli topic cmd subcmd greet --help
usage: cli.py topic cmd subcmd greet [-h] [--shout] name
Show a greeting
positional arguments:
name Name to use in greeting
optional arguments:
-h, --help show this help message and exit
--shout Yell the greeting (default: False)
From this we get:
$ python3 -m mypkg.cli topic cmd subcmd greet "World"
Hello, World!
$ python3 -m mypkg.cli topic cmd subcmd greet --shout "World"
HELLO, WORLD!
Bonus
Want to add the command <mycli> topic cmd ungreet ... to say goodbye?
Add the module:
touch mypkg/parsers/topic/cmd/ungreet.py
Note: Notice that since we want to create the command
<mycli> topic cmd ungreet ...we’re creating the module<sub-pkg-that-we'll-pass-to-multicommand>/topic/cmd/ungreet.py, where in this case<sub-pkg-that-we'll-pass-to-multicommand>ismypkg/parsers/
with contents:
import argparse
def handler(args):
print(f'Goodbye, {args.name}!')
parser = argparse.ArgumentParser(description='Show an un-greeting')
parser.add_argument('name', help='Name to use in un-greeting')
parser.set_defaults(handler=handler)
The new command is automatically added!:
$ python3 -m mypkg.cli topic cmd --help
usage: cli.py topic cmd [-h] [command] ...
optional arguments:
-h, --help show this help message and exit
subcommands:
[command]
subcmd
ungreet Show an un-greeting
Try it out:
$ python3 -m mypkg.cli topic cmd ungreet "World"
Goodbye, World!