I'd like to parse a command line that has a mutually exclusive group of options. Normally, I'd just use --foo bar
which would produce, in the namespace, args.foo = 'bar'
However, since all of these options are mutually exclusive, and I'm interested in both the option name and the argument passed to the option, and I have several of these options that need to be fed downstream, what I'd really like is to get back args.option_name = 'foo', args.option_value = 'bar'
in my namespace instead of args.foo='bar'
.
What I've done is:
class KeyAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, values)
setattr(namespace, self.dest+'_key', option_string)
frob = parser.add_mutually_exclusive_group()
frob.add_argument('--foo', dest='thing', nargs='?', action=KeyAction)
frob.add_argument('--nar', dest='thing', nargs='?', action=KeyAction)
when run, my namespace will look like:
Namespace(thing_key='--foo', thing='bar')
when --foo=bar
is parsed. Of course, sadly, if --foo or --nar is never passed in, namespace.thing_key
doesn't get defined, so I have to use a getattr()
.
The action override, while functional, doesn't seem really right.
I suspect the brilliant minds behind argparse already got this right somehow, and I'm just missing it in the documentation and from my read of argparse.py.
What's the best, right, pythonic, way to do this? Subparsers? I'm using python 3.5.
So I ended up using data from both of your answers to construct this, which handles the option, it's argument, and sets everything sanely at initialization time.
Thank you very much for the hints, clues, and validation. I'm surprised this hasn't come up in argparse before and become something standardized. It is a corner case, but isn't that much of a corner case when using mutually exclusive options.
class ValueAction(argparse.Action):
"""Override to store both the format type as well as the argument"""
# pylint: disable=too-few-public-methods
def __init__(self, option_strings, dest, **kwargs):
self._dest = dest
dest = dest + '_arguments'
container = kwargs.pop('container')
kwargs['action'] = kwargs.pop('subaction')
action_class = container._pop_action_class(kwargs)
if not callable(action_class):
raise ValueError('unknown action "%s"' % (action_class,))
self._action = action_class(option_strings, dest, **kwargs)
super().__init__(option_strings, dest, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
self._action(parser, namespace, values, option_string)
if isinstance(option_string, str):
while option_string[0] in parser.prefix_chars:
option_string = option_string[1:]
setattr(namespace, self._dest, option_string)