If my code calls a function which returns an ArgumentParser
that already has some subparsers defined, how can I add more subparsers?
I'd like to do something this:
parser_with_subparsers = build_parser()
additional_subparsers = parser_with_subparsers.add_subparsers()
extra_subparser = additional_subparsers.add_parser("extra", help="extra help"))
But when I attempt that in the interpreter:
>>> parser_with_subparsers = build_parser()
>>> additional_subparsers = parser_with_subparsers.add_subparsers()
usage: [-h] {original_subparser} ...
: error: cannot have multiple subparser arguments
Note that I do not have access to the internals of the build_parser()
function.
The only solution I can think of is using parents
, but this has the side effect of grouping the subparsers in a strange way:
>>> child_parser = argparse.ArgumentParser(parents=[build_parser()])
>>> additional_subparsers = child_parser.add_subparsers()
>>> extra_subparser = additional_subparsers.add_parser("extra", help="extra help")
>>> extra_subparser2 = additional_subparsers.add_parser("extra2", help="extra2 help")
>>> child_parser.print_help()
usage: [-h] {original_subparser} ... {extra,extra2} ...
positional arguments:
{original_subparser}
original_subparser original_subparser help
{extra,extra2}
extra extra help
extra2 extra2 help
optional arguments:
-h, --help show this help message and exit
The reason I want to do this is that I want to add extra 'commands' to an Argument Parser
that I have inherited.
What kind of behavior are you expecting from the added subparsers?
sp = parser.add_subparsers(...)
add_subparsers
creates a positional argument of _SubParsersAction
class, and makes a note of it in a parser._subparsers
attribute. It raises that error if that attribute has already been set. sp
is that Action
object.
Logically it doesn't make sense to have more than one 'subparsers' action. When the main parser encounters of a subparser command, it delegates the parsing to that subparser. Apart from some cleanup error checking the main parser does not resume parsing. So it couldn't handle a second subparser command.
(the terminology for what add_subparsers
creates and what add_parser
does can be a confusing. One is a positional argument/Action, the other a parser (instance of ArgumentParser
).
It should be possible to add new parsers to an existing subparsers
Action (I'd have to experiment to find a clean way of doing this). It also possible to add nest subparsers, that is, define add_subparsers
for a subparser.
That parents
approach bypasses thismultiple subparser arguments
test, probably because it doesn't set the parser._subparsers
attribute the first time (when copying Actions from the parent). So that in effect creates two subparser arguments
. That's what the help
shows. But my guess is that the parsing will not be meaningful. It will still expect 'original_subparser' and not look for 'extra' or 'extra2'.
The parents
mechanism is not robust. There are straight forward uses of it, but it's easily broken.
In [2]: parser = argparse.ArgumentParser(prog='main')
In [3]: a1 = parser.add_argument('--foo')
In [4]: parser._subparsers # initial value None
In [5]: sp = parser.add_subparsers(dest='cmd')
In [6]: parser._subparsers # new non-None value
Out[6]: <argparse._ArgumentGroup at 0xaf780f0c>
The sp
object, an Action subclass:
In [8]: sp
Out[8]: _SubParsersAction(option_strings=[], dest='cmd', nargs='A...', const=None, default=None, type=None, choices=OrderedDict(), help=None, metavar=None)
In [9]: sp1 = sp.add_parser('cmd1')
sp
is an Action (positional argument); sp1
is a parser.
In [10]: parser.print_help()
usage: main [-h] [--foo FOO] {cmd1} ...
positional arguments:
{cmd1}
optional arguments:
-h, --help show this help message and exit
--foo FOO
The _actions
attribute is a list of actions. Note the help
, and foo
In [12]: parser._subparsers._actions
Out[12]:
[_HelpAction(option_strings=['-h', '--help'], dest='help',...),
_StoreAction(option_strings=['--foo'], dest='foo',....),
_SubParsersAction(option_strings=[], dest='cmd', ...]
So the last item in this list is the sp
object
In [13]: parser._subparsers._actions[-1]
Out[13]: _SubParsersAction(option_strings=[], dest='cmd', nargs='A...', const=None, default=None, type=None, choices=OrderedDict([('cmd1', ArgumentParser(prog='main cmd1', usage=None, description=None, formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True))]), help=None, metavar=None)
So we can added a new subparser with
In [14]: parser._subparsers._actions[-1].add_parser('extra')
...
In [15]: parser.print_help()
usage: main [-h] [--foo FOO] {cmd1,extra} ...
positional arguments:
{cmd1,extra}
optional arguments:
-h, --help show this help message and exit
--foo FOO
So if you want just add extra
and extra2
to original_subparser
, this should work.
So as long as we don't try further parser.add_argument(...)
grabbing the last _actions
item should work. If it isn't the last, we'd have to do a more sophisticate search.
Judging from my rep change, someone found this earlier exploration of the _subparsers
attribute:
How to obtain argparse subparsers from a parent parser (to inspect defaults)