Home Sharing Command-line Options in Python Argparse
Post
Cancel

Sharing Command-line Options in Python Argparse

This is probably no big news that argparse makes it easy to write user-friendly command-line interfaces. It comes out-of-the-box and is therefore the standard tool if you create a CLI tool in Python.

Basic usage of argparse

The following snippet shows a basic usage example of argparse with a single param argument that can be passed to the command line.

1
2
3
4
5
6
7
8
9
10
11
import argparse

def main(argv):
  print(argv.param)

if __name__ == '__main__':
  parser = argparse.ArgumentParser()
  parser.add_argument('--param', type=int, default=64,
                      help='The param value. Default is 64.')
  args = parser.parse_args()
  main(args)

This optional argument can then be used used by appending --param VALUE when executing script.

1
$ python argparse_example.py --param 128

Introducing subparsers

I recently used subparsers for the first time that might not be very commonly known. Many CLI tool’s split up their functionality into a number of sub-commands. For instance, the git program can invoke sub-commands like git init, git clone, or git pull. And this is exactly where subparsers come in handy.

Basic usage of subparsers

The following code snippet shows a basic usage example of subparsers by defining a single command.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import argparse

def command_main(argv):
    print(argv.param, argv.verbose)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--verbose', type=bool, default=True,
                        action=argparse.BooleanOptionalAction,
                        help='Enable or disable verbosity. Default is True')
    parser.set_defaults(func=lambda argv: parser.print_help())
    subparsers = parser.add_subparsers()

    command = subparsers.add_parser('command')
    command.add_argument('--param', type=int, default=64,
                         help='The param value. Default is 64.')
    command.set_defaults(func=command_main)

    args = parser.parse_args()
    args.func(args)

This adds a single sub-command called command that has one optional --param parameter. Using the parser.set_defaults(…) on the root parser, we define that the help page is printed when no command is given at all. Furthermore, we also define a --verbose parameter, which needs to be specified before the command. Consequently, a usage example could look like the following.

1
$ python subparsers_example.py --no-verbose command --param 128

Sharing arguments between subparsers

The same way as above, you can obviously add multiple commands.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import argparse

def init_main(argv):
    print(argv.param, argv.verbose)

def start_main(argv):
    print(argv.param, argv.verbose)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.set_defaults(func=lambda argv: parser.print_help())
    subparsers = parser.add_subparsers()

    init = subparsers.add_parser('init')
    init.add_argument('--param', type=str, required=True,
                      help='The required param value.')
    init.set_defaults(func=init_main)

    start = subparsers.add_parser('start')
    start.add_argument('--param', type=str, required=True,
                       help='The required param value.')
    start.set_defaults(func=start_main)

    args = parser.parse_args()
    args.func(args)

This basically enables you to have multiple entry points to your script. Great!

However, as you can see, we ended up with redundantly defining a single param used in both commands twice. Well, you might think we could simply move it to the root parser. However, this would have the effect that the user needs to define this parameter before the command, such as python shared_args_example.py --param value init. But what if you would like to have these params after the command as in python shared_args_example.py init --param value?

Fortunately, this is possible by using a shared argument parser as shown in the following snipped.

1
2
3
4
5
6
7
8
9
10
11
subparsers = parser.add_subparsers()

shared_parser = argparse.ArgumentParser(add_help=False)
shared_parser.add_argument('--param', type=str, required=True,
                            help='The required param value.')

init = subparsers.add_parser('init', parents=[shared_parser])
init.set_defaults(func=init_main)

start = subparsers.add_parser('start', parents=[shared_parser])
start.set_defaults(func=start_main)

By defining a separate argument parser via argparse.ArgumentParser(add_help=False), you can define common parameters and share these between different subparser commands using the parents param.

Putting everything together

Finally, putting everything togehter, the following can be used as a copy-paste template for any new script or CLI tool that you are building using Python.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import argparse

def first_command(argv):
    pass

def second_command(argv):
    pass

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--root', type=bool, default=False,
                        action=argparse.BooleanOptionalAction,
                        help='Enable or disable the root param. Default is False')
    parser.set_defaults(func=lambda argv: parser.print_help())
    subparsers = parser.add_subparsers()

    shared_parser = argparse.ArgumentParser(add_help=False)
    shared_parser.add_argument('--shared', type=str, required=True,
                               help='The required shared param value.')

    cmd1 = subparsers.add_parser('first-command', parents=[shared_parser])
    cmd1.add_argument('--local-int', type=int, default=64,
                      help='The local int param value. Default is 64.')
    cmd1.set_defaults(func=first_command)

    cmd2 = subparsers.add_parser('second-command', parents=[shared_parser])
    cmd2.add_argument('--local-float', type=float, default=1.23,
                      help='The local float param value. Default is 1.23.')
    cmd2.set_defaults(func=second_command)

    args = parser.parse_args()
    args.func(args)

It follows a usage example of the template above.

1
$ python cli_template.py --no-root second-command --shared value --local-float 2.34

I hope this turns out to be useful to you some day.

This post is licensed under CC BY 4.0 by the author.