Python Argparse: Group Sub-Parsers
Python argparse has become a staple package in python in part due to its ease of use.
However, I recently came across an issue while using it on InsteonMQTT which makes extensive use of sub-parsers. Other than sorting, there is no mechanism to organize sub-parser objects to make them more readable.
This seems like a known issue going back to at least 2009 with no indication that it will be solved. Luckily, Steven Bethard was nice enough to propose a patch for argparse that I was able to convert to a module extension very easily.
In short, the following is the module extension argparse_ext.py:
#===========================================================================
#
# Extend Argparse to Enable Sub-Parser Groups
#
# Based on this very old issue: https://bugs.python.org/issue9341
#
# Adds the method `add_parser_group()` to the sub-parser class.
# This adds a group heading to the sub-parser list, just like the
# `add_argument_group()` method.
#
# NOTE: As noted on the issue page, this probably won't work with [parents].
# see http://bugs.python.org/issue16807
#
#===========================================================================
# Pylint doesn't like us access protected items like this
#pylint:disable=protected-access,abstract-method
import argparse
class _SubParsersAction(argparse._SubParsersAction):
class _PseudoGroup(argparse.Action):
def __init__(self, container, title):
sup = super(_SubParsersAction._PseudoGroup, self)
sup.__init__(option_strings=[], dest=title)
self.container = container
self._choices_actions = []
def add_parser(self, name, **kwargs):
# add the parser to the main Action, but move the pseudo action
# in the group's own list
parser = self.container.add_parser(name, **kwargs)
choice_action = self.container._choices_actions.pop()
self._choices_actions.append(choice_action)
return parser
def _get_subactions(self):
return self._choices_actions
def add_parser_group(self, title):
# the formatter can handle recursive subgroups
grp = _SubParsersAction._PseudoGroup(self, title)
self._choices_actions.append(grp)
return grp
def add_parser_group(self, title):
#
grp = _SubParsersAction._PseudoGroup(self, title)
self._choices_actions.append(grp)
return grp
class ArgumentParser(argparse.ArgumentParser):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.register('action', 'parsers', _SubParsersAction)
And the following is a simple test file test.py:
import argparse_ext
parser = argparse_ext.ArgumentParser(prog='PROG')
cmd = parser.add_subparsers(dest='cmd')
grp1 = cmd.add_parser_group('group1:')
grp1.add_parser('a', help='a subcommand help', aliases=['a1','a2'])
grp1.add_parser('b', help='b subcommand help')
grp1.add_parser('c', help='c subcommand help')
grp2 = cmd.add_parser_group('group2:')
grp2.add_parser('d', help='d subcommand help')
grp2.add_parser('e', help='e subcommand help', aliases=['e1'])
parser.print_help()
Which produces this nice command line output:
...$ python test.py
usage: PROG [-h] {a,a1,a2,b,c,d,e,e1} ...
positional arguments:
{a,a1,a2,b,c,d,e,e1}
group1:
a (a1, a2) a subcommand help
b b subcommand help
c c subcommand help
group2:
d d subcommand help
e (e1) e subcommand help
optional arguments:
-h, --help show this help message and exit
Note: There is a warning that this code may not work with parents argument of ArgumentParser, but I can live with that.


The main feature of MisterHouse that sets it apart from other home automation systems is the ability to customize practically every aspect of the system. The following example is a perfect demonstration of this.