Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I define global options with sub-parsers in python argparse?

I'm trying to figure out how to add global option in a sub-parser scenario with pythons arparse library.

Right now my code looks like this:

def parseArgs(self):
    parent_parser = argparse.ArgumentParser(add_help=False)
    parent_parser.add_argument('--debug', default=False, required=False,
        action='store_true', dest="debug", help='debug flag')

    main_parser = argparse.ArgumentParser()
    main_parser.add_argument('--debug', default=False, required=False,
        action='store_true', dest="debug", help='debug flag')

    service_subparsers = main_parser.add_subparsers(title="category",
        dest="category")
    agent_parser = service_subparsers.add_parser("agent",
        help="agent commands", parents=[parent_parser])
    return main_parser.parse_args()

This works for the command line ./test --help and the --debug option is listed as global:

usage: test [-h] [--debug] {agent} ...

optional arguments:
  -h, --help  show this help message and exit
  --debug     debug flag

category:
  {agent}
    agent     agent commands

However when I trigger the agent sub-parser with the command line ./test agent --help the --debug option is now no longer listed as a global option but as an option for the sub-parser. Also it must now specified as ./test agent --debug and ./test --debug agent no longer works:

usage: test agent [-h] [--debug]

optional arguments:
  -h, --help  show this help message and exit
  --debug     debug flag

What I'd like to be able to do is define --debug is global so that it can always be specified for all sub-parsers and appropriately listed as such in the help output.

like image 439
Dennis Jacobfeuerborn Avatar asked Jun 20 '16 23:06

Dennis Jacobfeuerborn


1 Answers

main_parser fills in the defaults into namespace (False for debug); if it encounters --debug it changes debug to True. When it sees the agent string, it calls the subparser, passing it the remaining argument strings, and the namespace that it has been using.

Now the subparser does the normal parser things - if fills in the defaults for its arguments, setting default to False. If it encounters --debug in the remaining strings, it changes that to True. Otherwise it leaves it as is. Once it is done, it passes the namespace back to the main parser, which then returns it to your code.

So for

myprog.py --debug agent --debug

namespace(debug=False) has flipped from False to True to False and back to True.

This a consequence of sharing the same dest for both the main parser (I don't like the use of 'global' in this context), and the subparser.

There was a bug/issue that tried to change the behavior a bit, passing the subparser a 'virgin' namespace, and then somehow merging its result with the main one. But that produced some backward compatibility issues. I could look it up if needed.

For now, trying to define the same optional in both the main and subparser is bound to create confusion for you and your user.

If I change the parent to

parent_parser.add_argument('--Debug', action='store_true', help='debug flag')

(no need for default, or the dest if it is sames as the option flag)

the resulting namespace will look like

1721:~/mypy$ python stack37933480.py --debug agent --Debug
Namespace(Debug=True, category='agent', debug=True)

Or I could define

parent_parser.add_argument('--debug', dest='debug1', action='store_true', help='debug flag')

and get:

1724:~/mypy$ python stack37933480.py --debug agent --debug
Namespace(category='agent', debug=True, debug1=True)

Same flag in both places, but different entries in the namespace. After parsing I could do something like:

args.debug = args.debug or args.debug1

to unify the two flags. Your user will see '--debug' regardless of which help asks for.

Sorry if the description is a bit long winded, but I think it's important to understand the behavior first. Then solutions become more apparent.

In this case the use of a parent doesn't complicate the issue. I assume you are using it just to add this debug to all subparsers.

Another option is to just define debug for the main parser. Yes, it will be missing from the subparsers help, but you can always add a note in the description.

===================

The subparser definition takes a prog parameter. If not given it is defined base on the main prog.

If I add prog as:

agent_parser = service_subparsers.add_parser("agent",
    prog='myprog.py [--debug] agent',
    help="agent commands", parents=[parent_parser])

subparser usage becomes:

1824:~/mypy$ python3 stack37933480.py agent -h
usage: myprog.py [--debug] agent [-h] [--debug]

or I can add that prog to the add_subparsers definition

service_subparsers = main_parser.add_subparsers(title="category",
    prog='myprog.py [--debug]',
    dest="category")

Check the code for that method to see how it constructs the default usage prefix. It includes main positionals, but not optionals.

http://bugs.python.org/issue9351 - in this patch the original developer thought that users would expect the subparser definition of an argument should override the main parser's values and actions. You were, on the other hand, expecting the main definition to have priority.

http://bugs.python.org/issue24251 - but the correction proposed in 9351 caused problems for other users. That's why I think it is better not to define the same dest in the main and sub. It is hard to satisfy everyone's expectations.

like image 147
hpaulj Avatar answered Oct 18 '22 23:10

hpaulj