Home > Software design >  argparse: unable to get subparser_name after declaring a global argument
argparse: unable to get subparser_name after declaring a global argument

Time:11-24

I've got a parser with some sub-parsers. I setup a global argument to be used on all subparser. Here's the relevant snippet

parser = argparse.ArgumentParser(prog="my_prog", add_help=False)
parser.add_argument('-d', '--debug', action='store_true', help='debug flag')

subparsers =  parser.add_subparsers(dest="subparser_name", help='some help notes')

parser_cmd1 = subparsers.add_parser('cmd1', parents=[parser])
parser_cmd1.add_argument('-f', '-foo', type=str, action=foo, required=False, help='foo command')

parser_cmd2 = subparsers.add_parser('cmd2', parents=[parser])
parser_cmd2.add_argument('-b', '-bar', type=str, action=bar, required=False, help='bar command')

args = parser.parse_args()
parser = args.subparser_name

print(args)

if args.debug:
    logging.basicConfig(level=logging.INFO)

if parser == 'cmd1':
    if args.foo:
        //do foo stuff

if parser == 'cmd2':
    if args.bar:
        //do bar stuff

So you can a command like such my_prog.py cmd1 -d -f inp_str. Here's the problem: subparser_name is None. The output of print(args) looks kind of like this

Namespace(debug=True, foo="inp_str", subparser_name=None)

Before I added the global debug argument, subparser_name would be the name of the command I ran, i.e. 'cmd1' or 'cmd2'. Now, it's 'None'. Even with the parents=[parser] addition in the subparser creation. How can I fix this? How do I know which command was called?

CodePudding user response:

Split out the common args to a separate ArgumentParser, which is then used as parent for the sub parsers. Also your foo and bar options were specified using -foo and -bar whi should be --foo and --bar. Also you didn't have default values for these so e.g. when -f/--foo wasn't specified args.foo correctly didn't exist.

This works better:

import argparse

common_args = argparse.ArgumentParser(prog="my_prog", add_help=False)
common_args.add_argument('-d', '--debug', action='store_true', help='debug flag')

parser = argparse.ArgumentParser(prog="my_prog", add_help=True)

subparsers =  parser.add_subparsers(dest="subparser_name", help='some help notes')

parser_cmd1 = subparsers.add_parser('cmd1', parents=[common_args])
parser_cmd1.add_argument('-f', '--foo', type=str, default='', required=False, help='foo command')

parser_cmd2 = subparsers.add_parser('cmd2', parents=[common_args])
parser_cmd2.add_argument('-b', '--bar', type=str, default='', required=False, help='bar command')

args = parser.parse_args()
parser = args.subparser_name

print(args)

if args.debug:
    logging.basicConfig(level=logging.INFO)

if parser == 'cmd1':
    if args.foo:
        #//do foo stuff
        print( f"foo {args.foo}" )

if parser == 'cmd2':
    if args.bar:
        #//do bar stuff
        print( f"bar {args.bar}" )

run with:

args.py cmd1 -f asd

output:

Namespace(subparser_name='cmd1', debug=False, foo='asd')
foo asd

Update:

If you want to be able to use e.g. args.py -d cmd1 then on the creation of parser, specify parents=[common_args]

parser = argparse.ArgumentParser(prog="my_prog", add_help=True, parents=[common_args])

Next time you ask a question ensure you only post code as a minimal reproducible example - i.e. that can be run without adding anything

CodePudding user response:

The subparser's defaults have priority of any values set by the main parser - default or user input. The main does set the subparser_name to 'cmd1', but the subparser changes it back to the default None.

While not evident in your test case, defining debug at both levels has the same problem. The subparser's default overwrites anything set in the main.

In general, it is not a good idea to use the same dest in the main and subparsers. Flags can be the same, but the dest should be different - at least if you want to see anything set by the main.

And using the main parser as a parent to the sub, is just asking for confusion.

  • Related