Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle CLI subcommands with argparse

I need to implement a command line interface in which the program accepts subcommands.

For example, if the program is called “foo”, the CLI would look like

foo cmd1 <cmd1-options>
foo cmd2
foo cmd3 <cmd3-options>

cmd1 and cmd3 must be used with at least one of their options and the three cmd* arguments are always exclusive.

I am trying to use subparsers in argparse, but with no success for the moment. The problem is with cmd2, that has no arguments:

if I try to add the subparser entry with no arguments, the namespace returned by parse_args will not contain any information telling me that this option was selected (see the example below). if I try to add cmd2 as an argument to the parser (not the subparser), then argparse will expect that the cmd2 argument will be followed by any of the subparsers arguments.

Is there a simple way to achieve this with argparse? The use case should be quite common…

Here follows what I have attempted so far that is closer to what I need:

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='Functions')
parser_1 = subparsers.add_parser('cmd1', help='...')
parser_1.add_argument('cmd1_option1', type=str, help='...')

parser_2 = subparsers.add_parser(cmd2, help='...')

parser_3 = subparsers.add_parser('cmd3', help='...')
parser_3.add_argument('cmd3_options', type=int, help='...')

args = parser.parse_args()
like image 356
Enea Avatar asked Oct 06 '14 13:10

Enea


People also ask

How do you add an optional argument in Argparse?

To add an optional argument, simply omit the required parameter in add_argument() . args = parser. parse_args()if args.

What is Argparse ArgumentParser ()?

The argparse module provides a convenient interface to handle command-line arguments. It displays the generic usage of the program, help, and errors. The parse_args() function of the ArgumentParser class parses arguments and adds value as an attribute dest of the object.

What is Store_true in Python?

The store_true option automatically creates a default value of False. Likewise, store_false will default to True when the command-line argument is not present.


2 Answers

First of all subparsers are never inserted in the namespace. In the example you posted if you try to run the script as:

$python3 test_args.py cmd1 1
Namespace(cmd1_option1='1')

where test_args.py contain the code you provided (with the import argparse at the beginning and print(args) at the end).

Note that there is no mention to cmd1 only to its argument. This is by design.

As pointed out in the comments you can add that information passing the dest argument to the add_subparsers call.

The usual way to handle these circumstances is to use the set_defaults method of the subparsers:

import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='Functions')
parser_1 = subparsers.add_parser('cmd1', help='...')
parser_1.add_argument('cmd1_option1', type=str, help='...')
parser_1.set_defaults(parser1=True)

parser_2 = subparsers.add_parser('cmd2', help='...')
parser_2.set_defaults(parser2=True)

parser_3 = subparsers.add_parser('cmd3', help='...')
parser_3.add_argument('cmd3_options', type=int, help='...')
parser_3.set_defaults(parser_3=True)

args = parser.parse_args()
print(args)

Which results in:

$python3 test_args.py cmd1 1
Namespace(cmd1_option1='1', parser1=True)
$python3 test_args.py cmd2
Namespace(parser2=True)

In general different subparser will, most of the time, handle the arguments in completely different ways. The usual pattern is to have different functions to run the different commands and use set_defaults to set a func attribute. When you parse the arguments you simply call that callable:

subparsers = parser.add_subparsers()
parser_1 = subparsers.add_parser(...)
parser_1.set_defaults(func=do_command_one)

parser_k = subparsers.add_parser(...)
parser_k.set_defaults(func=do_command_k)

args = parser.parse_args()
if args.func:
    args.func(args)
like image 104
Bakuriu Avatar answered Oct 03 '22 08:10

Bakuriu


The subparser identity can be added to the main Namespace if the add_subparsers command is given a dest.

From the documentation:

However, if it is necessary to check the name of the subparser that was invoked, the dest keyword argument to the add_subparsers() call will work:

>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers(dest='subparser_name')
>>> subparser1 = subparsers.add_parser('1')
>>> subparser1.add_argument('-x')
>>> subparser2 = subparsers.add_parser('2')
>>> subparser2.add_argument('y')
>>> parser.parse_args(['2', 'frobble'])
Namespace(subparser_name='2', y='frobble')

By default the dest is argparse.SUPPRESS, which keeps subparsers from adding the name to the namespace.

like image 32
hpaulj Avatar answered Oct 01 '22 08:10

hpaulj