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)