I would like to implement the following command line arguments parsing scenario:
I have 4 arguments: -g
, -wid
, -w1
, and -w2
.
-w1
and -w2
always appear together
-wid
and (-w1 -w2)
are mutually exclusive, but one or the other is required
-g
is optional; if it is not specified only (-w1 -w2)
can appear, but not -wid
Is there an elegant way to implement this using argparse, but without sub-commands (I am already in a sub-command)?
I was thinking of custom actions, but then in the body of the action I'd need to know when it is called for the last time (i.e. when the last argument is being parsed), which I do not know how to do since the number and the order of the arguments may vary.
Some more explanation just in case: the tool I am writing creates a gadget using a widget and a gadget parameter -g
. The widget is either an existing widget - and then it is referred to by its id -wid
, or a new widget that is created using parameters -w1
and -w2
. If -g
is not specified then the tool will just create and store a new widget using (-w1 -w2)
without creating the gadget.
Thank you in advance.
If you choose your defaults correctly, you can easily test for logical combinations of the argument values in the 'namespace' after 'parse_args'. You can also issue a parser error at that time. There is a bug report asking for mutually inclusive groups. There I suggest a mechanism for adding generalized combination testing. But it still requires your own logical tests.
http://bugs.python.org/issue11588 Add "necessarily inclusive" groups to argparse
The key to the solution I propose in that issue is making a seen_non_default_actions
variable available to the programmer. This is a list (set actually) of actions which have been seen (taking into account that optional-positionals are always 'seen'). I would like to see more discussion of how implement a mixture of inclusive and exclusive groupings.
You specify:
I have 4 arguments: -g, -wid, -w1, and -w2. -w1 and -w2 always appear together -wid and (-w1 -w2) are mutually exclusive, but one or the other is required -g is optional; if it is not specified only (-w1 -w2) can appear, but not -wid
Which I would attempt to summarize as:
complex_group('g', required_next_exclusive_group('wid', inclusive_group('w1','w2)))
w1
,w2
could be replaced with a '--w1w2',nargs=2
. A simple 'mutually_inclusive_group' would work here. But argparse
cannot handle nested groups.
wid
and w1w2
could be put in a required mutually_exclusive_group
.
-g
requires a test like if args.g is None and args.wid is None: error()
Here's a script which behaves as you require (I think), using my latest patch in Issue11588. act_w1
etc are the actual action objects, which may appear in the seen_actions
list. The test functions are registered with the subparser, and performed near the end of its parse_know_args()
.
parser = argparse.ArgumentParser(usage='custom usage')
sp = parser.add_subparsers(dest='cmd')
sp.required = True
spp = sp.add_parser('cmd1')
act_g = spp.add_argument('-g')
act_wid = spp.add_argument('--wid')
act_w1 = spp.add_argument('--w1')
act_w2 = spp.add_argument('--w2')
@spp.crosstest # decorator to register this function with spp
def test1(spp, seen_actions, *args):
# seen_actions - list of actions that were seen by parser
if 1==len({act_w1, act_w2}.intersection(seen_actions)):
# error if only one of these was seen
parser.error('-w1 and -w2 always appear together')
@spp.crosstest
def test2(spp, seen_actions, *args):
# required mutually exclusive wid and (w1,w2 group)
if act_wid in seen_actions:
if act_w1 in seen_actions or act_w2 in seen_actions:
parser.error('-wid and (-w1 -w2) are mutually exclusive')
elif act_w1 not in seen_actions:
parser.error('wid or (w1 and w2) required')
@spp.crosstest
def test3(spp, seen_actions, *args):
# is this the simplest logical way of expressing this?
if act_g not in seen_actions and act_wid in seen_actions:
parser.error('not g, so not wid')
args = parser.parse_args()
In this example I save and test for the presence of the action objects. Testing could also be done using the dest
strings. I'm exploring ways of making this testing more intuitive and user friendly. An expanded set of decorators seems most promising.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With