Torilla
Torilla

Reputation: 403

Python Argparse subparsers

I am using the Argparse Module to parse command line options. I have a main script which calls either subscript A or subscript B. I now have a a subparser instance for A and B and the main parser instance which should contain info about variables that A and B need. Something like:

def __init__(self):
    parser = argparse.ArgumentParser("Parser")
    parser.addArgument('--input') #this should be availabe for A and B

    subparsers = parser.add_subparsers('Description')
    A_parser = subparsers.add_parser(A)
    A_paser.add_argument('--a_option')

    B_parser = parser.add_subparser('Description')
    B_parser.add_arguemnt('--b_option')

    args = parser.parse_args()

But if I use it like this, I can only change args.options via the --input option if I do not specify A or B on program call. Execution of

program.py A --input ./

or

program.py --input ./ A

both fail.

Upvotes: 2

Views: 7705

Answers (1)

Adrian W
Adrian W

Reputation: 5026

I think you messed up the example usage from the python documentation. I attempted to edit your code example, but there were so many typos that I gave up. Expecially the usage of add_subparsers() and add_parser() was mixed up.

The cleaned up (and self-contained) version of the code looks like this:

import argparse


parser = argparse.ArgumentParser("Parser")
parser.add_argument('--input') #this should be available for A and B

subparsers = parser.add_subparsers(help='Description')
A_parser = subparsers.add_parser('A')
A_parser.add_argument('--a_option')

B_parser = subparsers.add_parser('B')
B_parser.add_argument('--b_option')

Now you can call

args = parser.parse_args(['--input', 'foo'])

but also

args = parser.parse_args(['--input', 'bar', 'A', '--a_option', 'a_option_arg'])

and

args = parser.parse_args(['--input', 'baz', 'B', '--b_option', 'b_option_arg'])

Update

In the comments, you ask:

Is it possible that I can also pass a unknown number of arguments for the --input option and still have the A option after that? Something like: program.py --input ./ ../ ../../ A -a_option

If you want to pass an unknown number of arguments to the --input option you need to pass nargs='*' to the argument definition of --input. However, you cannot use tags like A and B anymore to select your subparsers, because these would be taken as further arguments to --input.

You can resort to parent parsers in that case. With parent parsers, you can merge the options of several parsers into one:

import argparse


A_parser = argparse.ArgumentParser(add_help=False)
A_parser.add_argument('--a_option')

B_parser = argparse.ArgumentParser(add_help=False)
B_parser.add_argument('--b_option')

parser = argparse.ArgumentParser("Parser", parents=[A_parser, B_parser])
parser.add_argument('--input', nargs='*')  # this should be available for A and B

Now all options are available every time:

args = parser.parse_args(['--input', 'foo'])
args = parser.parse_args(['--input', 'foo' , 'bar', '--a_option', 'a_option_arg'])
args = parser.parse_args(['--input', 'baz', '--b_option', 'b_option_arg'])

Note that there is no more 'A' and 'B' tag. Selecting 'A' occurs by choosing one of the options defined by A_parser. It is even allowed to use both:

args = parser.parse_args(['--input', 'foo' ,'--a_option', 'a_option_arg', '--b_option', 'b_option_arg'])

If you don't want this, you would need to implement a check for conflicting parameters.

Upvotes: 2

Related Questions