Simple subcommand CLIs with argparse.

multicommand uses only the standard library and is ~100 lines of code (modulo comments and whitespace)


pip install multicommand


Multicommand enables you to easily write CLIs with deeply nested commands using vanilla argparse. You provide it with a package, it searches that package for parsers (ArgumentParser objects), and connects, names, and converts those parsers into subcommands based on the package structure.

        Package                       ->                    CLI

commands/unary/                            mycli unary negate ...
commands/binary/                              mycli binary add ...
commands/binary/             ->            mycli binary divide ...
commands/binary/                         mycli binary multiply ...
commands/binary/                         mycli binary subtract ...

All it needs is for each module to define a module-level parser variable which points to an instance of argparse.ArgumentParser.


I like argparse. It's flexible, full-featured and it's part of the standard library, so if you have python you probably have argparse. I also like the "subcommand" pattern, i.e. one root command that acts as an entrypoint and subcommands to group related functionality. Of course, argparse can handle adding subcommands to parsers, but it's always felt a bit cumbersome, especially when there are many subcommands with lots of nesting.

If you've ever worked with technologies like Next.js or oclif (or even if you haven't) there's a duality between files and "objects". For Next.js each file under pages/ maps to a webpage, in oclif each module under commands/ maps to a CLI command. And that's the basic premise for multicommand: A light-weight package that lets you write one parser per file, pretty much in isolation, and it handles the wiring, exploiting the duality between command structure and file system structure.

Getting Started

See the simple example, or for the impatient:

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

Create the *.py files required for the directories to be packages

touch mypkg/
touch mypkg/parsers/
touch mypkg/parsers/topic/
touch mypkg/parsers/topic/cmd/
touch mypkg/parsers/topic/cmd/subcmd/{,}

Add a parser to

# file: mypkg/parsers/topic/cmd/subcmd/
import argparse

def handler(args):
    greeting = f'Hello, {}!'
    print(greeting.upper() if args.shout else greeting)

parser = argparse.ArgumentParser(
    description='My first CLI with multicommand',
parser.add_argument('name', help='Name to use in greeting')
parser.add_argument('--shout', action='store_true', help='Yell the greeting')

lastly, add an entrypoint:

touch mypkg/

with the following content:

# file: mypkg/
import multicommand
from . import parsers

def main():
    parser = multicommand.create_parser(parsers)
    args = parser.parse_args()
    if hasattr(args, 'handler'):

if __name__ == "__main__":

Try it out!

$ python3 -m mypkg.cli
usage: [-h] {topic} ...

optional arguments:
  -h, --help  show this help message and exit



Take a look at our greet command:

$ python3 -m mypkg.cli topic cmd subcmd greet --help
usage: topic cmd subcmd greet [-h] [--shout] name

My first CLI with multicommand

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"


Want to add the command topic cmd ungreet ... to say goodbye?

Add the module:

touch mypkg/parsers/topic/cmd/

with contents:

# file: mypkg/parsers/topic/cmd/
import argparse

def handler(args):
    print(f'Goodbye, {}!')

parser = argparse.ArgumentParser(description='Another subcommand with multicommand')
parser.add_argument('name', help='Name to use in un-greeting')

The new command is automatically added!:

$ python3 -m mypkg.cli topic cmd --help
usage: cmd [-h] {subcmd,ungreet} ...

optional arguments:
  -h, --help        show this help message and exit



Try it out:

$ python3 -m mypkg.cli topic cmd ungreet "World"
Goodbye, World!