LostInTheFrequencyDomain LostInTheFrequencyDomain - 2 months ago 39
Python Question

Python argparse, positional arguments and subparsers

I have the following snippet where I am using argparse with multiple subparsers

parser = argparse.ArgumentParser(description = "Setup the DB",
add_help=False)
parser.add_argument('action', type = str,
choices=['drop','populate','print','create','print-metadata'],
help = "Specify an action", default = None)
subparsers = parser.add_subparsers()
drop_parser = subparsers.add_parser('drop',parents=[parser])
drop_parser.add_argument('-dataset-name',
required=True,
type = str, help = "Dataset Name",
default = None)
.....
args = parser.parse_args()
.....


When I go to run this I get the following:

python .\populatedb.py drop -dataset-name foo
populatedb.py: error: invalid choice: 'foo' (choose from 'drop', 'populate', 'print', 'create', 'print-metadata')


I am wondering where the code above is going wrong. Note that the "action" argument is a positional argument.

Thank you in advance. This is my first use of subparsers I am probably making an obvious mistake.

Thank you in advance.

Regards,

Ranga

Answer

The generally accepted thing to do here is to not have an argument for the subparser -- it is it's own argument provided by argparse:

parser = argparse.ArgumentParser(description = "Setup the DB",
        add_help=False)
subparsers = parser.add_subparsers()
drop_parser = subparsers.add_parser('drop',parents=[parser])
drop_parser.add_argument('-dataset-name',
        required=True,
        type = str, help = "Dataset Name",
        default = None)
.....
args = parser.parse_args()

Now you don't necessarily know which parser was selected but don't worry, there's a built-in mechanism for this as well. One common use-case is having one function that should be invoked for each subparser...

parser = argparse.ArgumentParser(description = "Setup the DB",
        add_help=False)
subparsers = parser.add_subparsers()

def drop_parser_handler(args):
    ...

drop_parser = subparsers.add_parser('drop',parents=[parser])
drop_parser.add_argument('-dataset-name',
        required=True,
        type = str, help = "Dataset Name",
        default = None)
drop_parser.set_defaults(func=drop_parser_handler)
.....

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

Of course you don't need to do anything this complex -- You could just set a constant value:

drop_parser.set_defaults(subparser_name='drop')
Comments