argparse subcommands with nested namespaces

If the focus is on just putting selected arguments in their own namespace, and the use of subparsers (and parents) is incidental to the issue, this custom action might do the trick.

class GroupedAction(argparse.Action):    
    def __call__(self, parser, namespace, values, option_string=None):
        group,dest = self.dest.split('.',2)
        groupspace = getattr(namespace, group, argparse.Namespace())
        setattr(groupspace, dest, values)
        setattr(namespace, group, groupspace)

There are various ways of specifying the group name. It could be passed as an argument when defining the Action. It could be added as parameter. Here I chose to parse it from the dest (so namespace.filter.filter1 can get the value of filter.filter1.

# Main parser
main_parser = argparse.ArgumentParser()
main_parser.add_argument("-common")

filter_parser = argparse.ArgumentParser(add_help=False)
filter_parser.add_argument("--filter1", action=GroupedAction, dest="filter.filter1", default=argparse.SUPPRESS)
filter_parser.add_argument("--filter2", action=GroupedAction, dest="filter.filter2", default=argparse.SUPPRESS)

subparsers = main_parser.add_subparsers(help='sub-command help')

parser_a = subparsers.add_parser('command_a', help="command_a help", parents=[filter_parser])
parser_a.add_argument("--foo")
parser_a.add_argument("--bar")
parser_a.add_argument("--bazers", action=GroupedAction, dest="anotherGroup.bazers", default=argparse.SUPPRESS)
...
namespace = main_parser.parse_args()
print namespace

I had to add default=argparse.SUPPRESS so a bazers=None entry does not appear in the main namespace.

Result:

>>> python PROG command_a --foo bar --filter1 val --bazers val
Namespace(anotherGroup=Namespace(bazers="val"), 
    bar=None, common=None, 
    filter=Namespace(filter1='val'), 
    foo='bar')

If you need default entries in the nested namespaces, you could define the namespace before hand:

filter_namespace = argparse.Namespace(filter1=None, filter2=None)
namespace = argparse.Namespace(filter=filter_namespace)
namespace = main_parser.parse_args(namespace=namespace)

result as before, except for:

filter=Namespace(filter1='val', filter2=None)

Leave a Comment