How to parse multiple nested sub-commands using python argparse? How to parse multiple nested sub-commands using python argparse? python python

How to parse multiple nested sub-commands using python argparse?


I came up with the same qustion, and it seems i have got a better answer.

The solution is we shall not simply nest subparser with another subparser, but we can add subparser following with a parser following another subparser.

Code tell you how:

parent_parser = argparse.ArgumentParser(add_help=False)                                                                                                  parent_parser.add_argument('--user', '-u',                                                                                                                                   default=getpass.getuser(),                                                                                                                               help='username')                                                                                                                     parent_parser.add_argument('--debug', default=False, required=False,                                                                                                                action='store_true', dest="debug", help='debug flag')                                                                         main_parser = argparse.ArgumentParser()                                                                                                                  service_subparsers = main_parser.add_subparsers(title="service",                                                                                                             dest="service_command")                                                                                                              service_parser = service_subparsers.add_parser("first", help="first",                                                                                                        parents=[parent_parser])                                                                                                             action_subparser = service_parser.add_subparsers(title="action",                                                                                                             dest="action_command")                                                                                                               action_parser = action_subparser.add_parser("second", help="second",                                                                                                         parents=[parent_parser])                                                                                                             args = main_parser.parse_args()   


@mgilson has a nice answer to this question. But problem with splitting sys.argv myself is that i lose all the nice help message Argparse generates for the user. So i ended up doing this:

import argparse## This function takes the 'extra' attribute from global namespace and re-parses it to create separate namespaces for all other chained commands.def parse_extra (parser, namespace):  namespaces = []  extra = namespace.extra  while extra:    n = parser.parse_args(extra)    extra = n.extra    namespaces.append(n)  return namespacesargparser=argparse.ArgumentParser()subparsers = argparser.add_subparsers(help='sub-command help', dest='subparser_name')parser_a = subparsers.add_parser('command_a', help = "command_a help")## Setup options for parser_a## Add nargs="*" for zero or more other commandsargparser.add_argument('extra', nargs = "*", help = 'Other commands')## Do similar stuff for other sub-parsers

Now after first parse all chained commands are stored in extra. I reparse it while it is not empty to get all the chained commands and create separate namespaces for them. And i get nicer usage string that argparse generates.


parse_known_args returns a Namespace and a list of unknown strings. This is similar to the extra in the checked answer.

import argparseparser = argparse.ArgumentParser()parser.add_argument('--foo')sub = parser.add_subparsers()for i in range(1,4):    sp = sub.add_parser('cmd%i'%i)    sp.add_argument('--foo%i'%i) # optionals have to be distinctrest = '--foo 0 cmd2 --foo2 2 cmd3 --foo3 3 cmd1 --foo1 1'.split() # or sys.argvargs = argparse.Namespace()while rest:    args,rest =  parser.parse_known_args(rest,namespace=args)    print args, rest

produces:

Namespace(foo='0', foo2='2') ['cmd3', '--foo3', '3', 'cmd1', '--foo1', '1']Namespace(foo='0', foo2='2', foo3='3') ['cmd1', '--foo1', '1']Namespace(foo='0', foo1='1', foo2='2', foo3='3') []

An alternative loop would give each subparser its own namespace. This allows overlap in positionals names.

argslist = []while rest:    args,rest =  parser.parse_known_args(rest)    argslist.append(args)