Python Command Line Interface Creation Libraries: Click, Docopt, and Invoke

Maxim Manylov
7 min readSep 12, 2019

The review contains code samples for simple commands, usage of the commands, and a short description of the following libraries: Docopt, Click, and Invoke. The standard python library Argparse is not included in the review for the article is written with the main purpose to make a choice of a library to replace the standard one. The article contains a short review of Python programming language argument parser libraries and my personal opinion in the end.

Let’s start with Click (the one I prefer), then there will be Docopt and Invoke, just before my opinion on the libraries in the end.

Tell me what CLI library you prefer to use in the comments.

Click

Description

Click is a python library that is described as “Python composable command-line interface toolkit” on its Github page. To define a parser you must describe the params, options, and everything as decorators for the command being created. With Click you can easily build complex commands with subcommands (see github.com/pallets/click/tree/master/examples/complex).

Click in three points:

  • arbitrary nesting of commands
  • automatic help page generation
  • supports lazy loading of subcommands at runtime

Click also helps validate arguments.

Examples

Basic example

The example code defines a command group (a base command) and two subcommands — hello and goodbye. The command group helps create a set of commands with the same help, arguments, and options. More on command groups here.

click/commands.py

Usage of the command:

$ python click/commands.py — help
Usage: commands.py [OPTIONS] COMMAND [ARGS]…
Options:
— help Show this message and exit.
Commands:
goodbye
hello
$ python click/commands.py hello — help
Usage: commands.py hello [OPTIONS]
Options:
— help Show this message and exit.

Complex (multi-)command example

To create a complex command with click you have to define a special complex command context class for the base aggregate command, and the commands themselves (usually commands are separated from the aggregate in subfolders aka modules). The directory structure could be like the following:

-cli.py
-commands/
--cmd_init.py
--cmd_status.py

In cli.py you have to define a special class, a multi-command class, derived from click.MultiCommand class that has to implement two methods: list_commands() for commands listing and get_command() for commands invocation.

If you want to log commands invocation in custom logs, you also have to define a special commands environment class to use with click.make_pass_decorator() factory function which creates a decorator that passes the environment to the multi-command.

Adding arguments to the command interface

Let’s add an argument name for the hello and the goodbye commands:

click/arguments.py

Usage of the hello command:

$ python click/arguments.py hello Kyle
Hello, Kyle!
$ python click/arguments.py hello — help
Usage: arguments.py hello [OPTIONS] NAME
Options:
— help Show this message and exit.

Version Option ( — version)

Sample command code:


@click.group()
@click.version_option(version='1.0.0')
def greet():

Usage of the command:

$ python click/version.py — version
version.py, version 1.0.0

Improving Help (-h/ — help)

Sample command code:

import clickCONTEXT_SETTINGS = dict(help_option_names=['-h', '— help'])@click.group(context_settings=CONTEXT_SETTINGS)
@greet.command()
@click.argument('name')
@click.option(' — greeting', default='Hello', help='word to use for the greeting')
@click.option(' — caps', is_flag=True, help='uppercase the output')
def hello(**kwargs):
greeter(**kwargs)

Usage of the command:

$ python click/help.py hello -h
Usage: help.py hello [OPTIONS] NAME
Options:
— greeting TEXT word to use for the greeting
— caps
uppercase the output
-h, — help
Show this message and exit.

Built-in error handling

$ python click/final.py hello
Usage: final.py hello [OPTIONS] NAME
Error: Missing argument "name".
$ python click/final.py hello — badoption Kyle
Error: no such option: — badoption
$ python click/final.py hello — caps=notanoption Kyle
Error: — caps option does not take a value

Docopt

The main site is at docopt.org and the project GitHub page is at github.com/docopt/docopt.

Docopt is a python library that aims to create CLI parsing arguments defined in the command docstring.

Examples

Basic example

Sample hello-goodbye command from the previous section.

docopt/commands.py:

"""Greeter.
Usage:
commands.py hello
commands.py goodbye
commands.py -h | — help
Options:
-h — help Show this screen.
"""
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__)

Usage of the command:

$ python docopt/commands.py — help
Greeter.
Usage:
commands.py hello
commands.py goodbye
commands.py -h | — help
Options:
-h — help
Show this screen.
$ python docopt/commands.py hello — help
Greeter.
Usage:
commands.py hello
commands.py goodbye
commands.py -h | — help
Options:
-h — help
Show this screen.

Add arguments example

Sample command code:

"""Greeter.
Usage:
basic.py hello <name>
basic.py goodbye <name>
basic.py (-h | — help)
Options:
-h — help
Show this screen.
"""
from docopt import docopt
def hello(name):
print('Hello, {0}'.format(name))
def goodbye(name):
print('Goodbye, {0}'.format(name))
if __name__ == '__main__':
arguments = docopt(__doc__)
# if an argument called hello was passed, execute the hello logic.
if arguments['hello']:
hello(arguments['<name>'])
elif arguments['goodbye']:
goodbye(arguments['<name>'])

Usage of the command:

$ python docopt/arguments.py hello Kyle
Hello, Kyle
$ python docopt/arguments.py hello — help
Greeter.
Usage:
basic.py hello <name>
basic.py goodbye <name>
basic.py (-h | — help)
Options:
-h — help
Show this screen.

Flags/Options example

Sample command code:

"""Greeter.
Usage:
basic.py hello <name> [ — caps] [ — greeting=<str>]
basic.py goodbye <name> [ — caps] [ — greeting=<str>]
basic.py (-h | — help)
Options:
-h — help
Show this screen.
— caps
Uppercase the output.
— greeting=<str> Greeting to use [default: Hello].
Commands:
hello
Say hello
goodbye Say goodbye
"""
from docopt import docopt
HELLO = """usage: basic.py hello [options] [<name>]
-h — help
Show this screen.
— caps
Uppercase the output.
— greeting=<str> Greeting to use [default: Hello].
"""
GOODBYE = """usage: basic.py goodbye [options] [<name>]
-h — help
Show this screen.
— caps
Uppercase the output.
— greeting=<str> Greeting to use [default: Goodbye].
"""
def greet(args):
output = '{0}, {1}!'.format(args[' — greeting'],
args['<name>'])
if args[' — caps']:
output = output.upper()
print(output)
if __name__ == '__main__':
arguments = docopt(__doc__, options_first=True)
if arguments['<command>'] == 'hello':
greet(docopt(HELLO))
elif arguments['<command>'] == 'goodbye':
greet(docopt(GOODBYE))
else:
exit("{0} is not a command. \
See 'options.py — help'.".format(arguments['<command>']))

Usage of the command:

$ python docopt/options.py — help
usage: greet [ — help] <command> [<args>…]
options:
-h — help
Show this screen.
commands:
hello
Say hello
goodbye Say goodbye
$ python docopt/options.py hello — help
usage: basic.py hello [options] [<name>]
-h — help
Show this screen.
— caps
Uppercase the output.
— greeting=<str> Greeting to use [default: Hello].
$ python docopt/options.py hello Kyle
Hello, Kyle!
$ python docopt/options.py goodbye Kyle
Goodbye, Kyle!

Invoke

The main site is at www.pyinvoke.org and the project GitHub page is at https://github.com/pyinvoke/invoke. Invoke documentation is at http://docs.pyinvoke.org/en/1.3.

What exactly is Invoke? As stated on their main page:

Invoke is a Python (2.7 and 3.4+) task execution tool & library, drawing inspiration from various sources to arrive at a powerful & clean feature set.

So, Invoke isn’t just another argument parser it’s a task runner.

Examples

Basic example

Code:

from invoke import task@task
def clean(c, docs=False, bytecode=False, extra=''):
patterns = ['build']
if docs:
patterns.append('docs/_build')
if bytecode:
patterns.append('**/*.pyc')
if extra:
patterns.append(extra)
for pattern in patterns:
c.run("rm -rf {}".format(pattern))
@task
def build(c, docs=False):
c.run("python setup.py build")
if docs:
c.run("sphinx-build docs docs/_build")

Usage:

$ invoke clean build

Example from the article of using default params values (see SOURCES#1)

Code:

from invoke import taskdef greet(name, greeting, caps):
output = '{0}, {1}!'.format(greeting, name)
if caps:
output = output.upper()
print(output)
@task
def hello(name, greeting='Hello', caps=False):
greet(name, greeting, caps)
@task
def goodbye(name, greeting='Goodbye', caps=False):
greet(name, greeting, caps)

Usage:

$ invoke hello Kyle
Hello, Kyle!
$ invoke hello — greeting=Wazzup Kyle
Wazzup, Kyle!
$ invoke hello — greeting=Wazzup — caps Kyle
WAZZUP, KYLE!
$ invoke hello — caps Kyle
HELLO, KYLE!

Help Documentation

Code:


HELP = {
'name': 'name of the person to greet',
'greeting': 'word to use for the greeting',
'caps': 'uppercase the output'
}
@task(help=HELP)
def hello(name, greeting='Hello', caps=False):
"""
Say hello.
"""
greet(name, greeting, caps)
@task(help=HELP)
def goodbye(name, greeting='Goodbye', caps=False):
"""
Say goodbye.
"""
greet(name, greeting, caps)

Usage:

$ invoke — help hello
Usage: inv[oke] [ — core-opts] hello [ — options] [other tasks here …]
Docstring:
Say hello.
Options:
-c, — caps
uppercase the output
-g STRING, — greeting=STRING word to use for the greeting
-n STRING, — name=STRING
name of the person to greet
-v, — version

A real-world example used in Fabric library

See example at https://github.com/fabric/fabric/blob/2.5/fabric/main.py#L9 (see the code listing below).

The Conclusion

In my opinion, even if Docopt has a declarative method of defining parser using docstrings, the library is too ambiguous in comparison to other libraries (click and invoke) and has no syntax validation (linting) supported by the most popular IDEs used for work with Python (VS Code and PyCharm). click seems to be a very nice library with a declarative approach to defining parser for python CLI scripts, unlike invoke, which has a functional approach. Both click and invoke, on the other hand, are libraries which definitions of a command-line interface could be linted with an IDE and fixed with ease.

Personally, I prefer Click library. I’ve written complex CLI utility with the Click library for the national airline company’s website and I’d say that’s not very easy to figure out how to compose complex nested commands with Click, using the code samples provided by Click’s team in the Github repo, though as you write one you most likely will find the library very handy.

Sources:

  1. Main source article of CLI parsing utils comparison- https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click
  2. Invoke implementation in Fabric project — https://github.com/fabric/fabric/blob/2.5/fabric/main.py#L9
  3. Invoke main page — http://www.pyinvoke.org
  4. Invoke GitHub page — https://github.com/pyinvoke/invoke
  5. Invoke documentation page — http://docs.pyinvoke.org/en/1.3
  6. Docopt main page — http://docopt.org
  7. Docopt GitHub page — https://github.com/docopt/docopt
  8. Click main page — http://docopt.org
  9. Click GitHub page — https://github.com/pallets/click

--

--