This kind of inter argument dependency is easier to implement after parsing.
args = parser.parse_args()
if not namespace.git_user and namespace.clone:
parser.error('"--clone" requires legal git user')
At that point, both git_user
and clone
have been parsed, and have their final values.
As you implemented it, the custom action is run only when there's a --gituser
argument. So I think it will raise the error when you give it --gituser
without --clone
.
You could give --clone
a similar custom action, but it would also have to handle the store_true
details. And what should happen with the --clone --gituser value
sequence? The clone
action will be run before the gituser
value has been parsed. Tests like this run into some tough argument order problems.
A couple of other issues:
your custom action does not store any value, regardless of error not. It's better to customize the store
subclass.
custom actions should raise argparse.ArgumentError
rather than call parser.error
directly.
The unittest file, test/test_argparse.py
has an example of custom actions with mutual tests like this. But it's just a toy, verifying that such code is allowed.
==================
You could, in theory, implement a --clone
action that sets the required
attribute of the --gituser
Action. That way, if --gituser
is not used, the final required
actions test of parse_args
will raise an error. But that requires saving a reference to the Action displayed in out[95]
(or finding that in the parse._actions
list. Feasible but messy.
===================
Here's an example of a pair of interacting custom action classes from test/test_argparse.py
.
class OptionalAction(argparse.Action):
def __call__(self, parser, namespace, value, option_string=None):
try:
# check destination and option string
assert self.dest == 'spam', 'dest: %s' % self.dest
assert option_string == '-s', 'flag: %s' % option_string
# when option is before argument, badger=2, and when
# option is after argument, badger=<whatever was set>
expected_ns = NS(spam=0.25)
if value in [0.125, 0.625]:
expected_ns.badger = 2
elif value in [2.0]:
expected_ns.badger = 84
else:
raise AssertionError('value: %s' % value)
assert expected_ns == namespace, ('expected %s, got %s' %
(expected_ns, namespace))
except AssertionError:
e = sys.exc_info()[1]
raise ArgumentParserError('opt_action failed: %s' % e)
setattr(namespace, 'spam', value)
NS
is a shorthand for argparse.Namespace
.
class PositionalAction(argparse.Action):
def __call__(self, parser, namespace, value, option_string=None):
try:
assert option_string is None, ('option_string: %s' %
option_string)
# check destination
assert self.dest == 'badger', 'dest: %s' % self.dest
# when argument is before option, spam=0.25, and when
# option is after argument, spam=<whatever was set>
expected_ns = NS(badger=2)
if value in [42, 84]:
expected_ns.spam = 0.25
elif value in [1]:
expected_ns.spam = 0.625
elif value in [2]:
expected_ns.spam = 0.125
else:
raise AssertionError('value: %s' % value)
assert expected_ns == namespace, ('expected %s, got %s' %
(expected_ns, namespace))
except AssertionError:
e = sys.exc_info()[1]
raise ArgumentParserError('arg_action failed: %s' % e)
setattr(namespace, 'badger', value)
They are used in
parser = argparse.ArgumentParser()
parser.add_argument('-s', dest='spam', action=OptionalAction,
type=float, default=0.25)
parser.add_argument('badger', action=PositionalAction,
type=int, nargs='?', default=2)
And supposed to work with:
'-s0.125' producing: NS(spam=0.125, badger=2)),
'42', NS(spam=0.25, badger=42)),
'-s 0.625 1', NS(spam=0.625, badger=1)),
'84 -s2', NS(spam=2.0, badger=84)),
This is an example of the kind of cross checking that can be done. But I'll repeat that generally interactions are best handled after parsing, not during.
As to the implementation question - if the user does not give you --gituser
, your custom Action is never called. The Action.__call__
of an optional
is only used when that argument is used. positionals
are always used, but not optionals
.