Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to have sub-parser arguments in separate namespace with argparse?

I have the following test-code

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", default = 0, type=int)

subparsers = parser.add_subparsers(dest = "parser_name")

parser_lan = subparsers.add_parser('car')
parser_lan.add_argument("--boo")
parser_lan.add_argument("--foo")

parser_serial = subparsers.add_parser('bus')
parser_serial.add_argument("--fun")

print parser.parse_args()

which defines two sub-parsers, having a different set of arguments. When I call the testcode as

tester.py  --verbose 3 car --boo 1 --foo 2

I get the expected result

Namespace(boo='1', foo='2', parser_name='car', verbose=3)

What I want to have instead is the values from each subparser in a separate namespace or dict, something like

Namespace(subparseargs={boo:'1', foo:'2'}, parser_name='car', verbose=3)

so that the arguments from each subparser are logical separated from the arguments from the main parser (as verbose in this example).

How can I achieve this, with the arguments for each subparser in the same namespace (subparseargs in the example).

like image 695
Alex Avatar asked Apr 03 '13 09:04

Alex


2 Answers

You need to go into the bowels of argparse a bit but changing your script to the following should do the trick:

import argparse
from argparse import _HelpAction, _SubParsersAction

class MyArgumentParser(argparse.ArgumentParser):
    def parse_args(self, *args, **kw):
        res = argparse.ArgumentParser.parse_args(self, *args, **kw)
        from argparse import _HelpAction, _SubParsersAction
        for x in parser._subparsers._actions:
            if not isinstance(x, _SubParsersAction):
                continue
            v = x.choices[res.parser_name] # select the subparser name
            subparseargs = {}
            for x1 in v._optionals._actions: # loop over the actions
                if isinstance(x1, _HelpAction): # skip help
                    continue
                n = x1.dest
                if hasattr(res, n): # pop the argument
                    subparseargs[n] = getattr(res, n)
                    delattr(res, n)
            res.subparseargs = subparseargs
        return res

parser = MyArgumentParser()
parser.add_argument("--verbose", default = 0, type=int)

subparsers = parser.add_subparsers(dest = "parser_name")

parser_lan = subparsers.add_parser('car')
parser_lan.add_argument("--boo")
parser_lan.add_argument("--foo")

parser_serial = subparsers.add_parser('bus')
parser_serial.add_argument("--fun")

print parser.parse_args()
like image 99
Anthon Avatar answered Nov 16 '22 20:11

Anthon


I have started to develop a different approach (but similar to the suggestion by Anthon) and come up with a much shorter code. However, I am not sure my approach is a general solution for the problem.

To similar what Anthon is proposing, I define a new method which creates a list of 'top-level' arguments which are kept in args, while all the other arguments are returned as an additional dictionary:

class MyArgumentParser(argparse.ArgumentParser):
    def parse_subargs(self, *args, **kw):
        # parse as usual
        args = argparse.ArgumentParser.parse_args(self, *args, **kw)

        # extract the destination names for top-level arguments
        topdest = [action.dest for action in parser._actions]

        # loop over all arguments given in args
        subargs = {}
        for key, value in args.__dict__.items():

            # if sub-parser argument found ...
            if key not in topdest:

                # ... remove from args and add to dictionary
                delattr(args,key)
                subargs[key] = value

        return args, subargs

Comments on this approach welcome, especially any loopholes I overlooked.

like image 21
Alex Avatar answered Nov 16 '22 21:11

Alex