Idea
Add a new method append_parser to argparse._SubParsersAction to
allow the addition of already instantiated parsers as subparsers.
Preferred Feedback
Is this type of functionality, creating a command line parser from
pre-instantiated parsers something useful to other people? Is
it worth posting an issue in python’s github? Did I miss an already
existing, simpler approach?
Although I have been a python user for many years, I am very
new to contributing to any large open source project, so if this
is not the place to discuss this please point me to where I
should do it. If this is actually a good idea to other people,
my next step should be posting an issue into github?
Motivation
Lets assume that we have two scripts (subprog1.py and subprog2.py),
each one with their respective command line parser and we want to unify
them within the same command line interface:
$ python prog.py --help
$ python prog.py subprog1 --help
$ python prog.py subprog2 --help
This interface could be achieved by:
import argparse
parser = argparse.ArgumentParser(description='main program')
subparsers = parser.add_subparsers(dest='subprogram')
subparser1 = subparsers.add_parser('subprog1')
subparser1.add_arguments('foo',default='Hello')
subparser2 = subparsers.add_parser('subprog2')
subparser2.add_arguments('bar',default='World')
The add_parser method does not allow to add an ArgumentParser
instance as argument, and we are forced to either:
- Patch the
add_parserfunction - Separate the subparser definitions from the logic of each subprogram
- Merge both subprograms into a single file
- Write a work around with other stdlib modules, such as subprocess
Each of these options has different degrees of inconvenience, and writing
the patch might be the less inconvenient. I can easily write a wrapper
function, minimally changing the source code of argparse._SubParsersAction.add_parser :
def append_parser(subparsers:argparse._SubParsersAction,
parser:argparse.ArgumentParser,
name:str,
**kwargs) -> argparse.ArgumentParser:
# set prog from the existing prefix
if kwargs.get('prog') is None:
kwargs['prog'] = '%s %s' % (subparsers._prog_prefix, name)
aliases = kwargs.pop('aliases', ())
if name in subparsers._name_parser_map:
raise argparse.ArgumentError(subparsers, f'conflicting subparser: {name}')
for alias in aliases:
if alias in subparsers._name_parser_map:
raise argparse.ArgumentError(subparsers, f'conflicting subparser alias: {alias}')
if 'help' in kwargs:
help = kwargs.pop('help')
choice_action = subparsers._ChoicesPseudoAction(name, aliases, help)
subparsers._choices_actions.append(choice_action)
# add parser to the map
subparsers._name_parser_map[name] = parser
# make parser available under aliases also
for alias in aliases:
subparsers._name_parser_map[alias] = parser
return parser
This function simplifies the logic of my unified command line interface to the point
where now it is possible to:
import argparse
import subprog1
import subprog2
# Assume that append_parser definition is here
parser = argparse.ArgumentParser(description='main program')
subparsers = parser.add_subparsers(dest='subprogram')
append_parser(subparsers,subprog1.parser, 'subprog1')
append_parser(subparsers, subprog2.parser, 'subprog2')
if __name__ == '__main__':
args = parser.parse_args()
match args.subprogram:
case 'subprog1':
subprog1.main(args)
case 'subprog2':
subprog2.main(args)
It is very easy to modify the wrapper function to be a class method of
argparse._SubParsersAction and to avoid clashing with the already
existing add_parser method I believe that it could be implemented
as a new method argparse._SubParsersAction.append_parser