1

Here's the minimal code for what I want to do,

import argparse

parser = argparse.ArgumentParser( prog="test")

subparsers = parser.add_subparsers(title='sub-commands')
num  = subparsers.add_parser("num")
num.add_argument("-n")
numr = num.add_argument_group("Required arguments")

onr  =numr.add_mutually_exclusive_group(required=True)
onr.add_argument("-x")
onr2 = onr.add_argument_group("new")
onr2.add_argument("-y")
onr2.add_argument("-z")

So what I want is that, the user must provide either x or both y,z, so thought of adding a mutually exclusive group of one argument and one group. And this must be under the sub command num. This code gives this output

usage: test num [-h] [-n N] -x X [-y Y] [-z Z]

optional arguments:
  -h, --help  show this help message and exit
  -n N

Required arguments:
  -x X

No info about y,z , also giving both y,z doesn't work

python test.py num -y 9 -z 10
usage: test num [-h] [-n N] -x X [-y Y] [-z Z]
test num: error: one of the arguments -x is required

How do I achieve this using argparse, or is it even possible?

Eular
  • 1,707
  • 4
  • 26
  • 50
  • How about: https://stackoverflow.com/questions/44247099/click-command-line-interfaces-make-options-required-if-other-optional-option-is/44349292#44349292 ? – Stephen Rauch Jan 04 '19 at 05:33
  • `argument_group` is only used for group arguments in the `help`, as you do with `numr`. It ok to put `onr` in `numr` for labeling purpose. `onr2` doesn't do anything for you. You need to do your own testing for '-x', '-y', '-z'. There isn't a mechanism that does a `x xor (y and z)`. – hpaulj Jan 04 '19 at 05:59
  • What should the usage look like? `usage: test num [-h] [-n N] (-x X | ([-y Y] ??? [-z Z]))`. `argparse` uses the vertical bar to mark the mutually exclusive `xor` test, `(x | y | z)`. But what would convey a 'both' or a 'any' relation? I have explored generalizing the `xor` test, but generalizing the `usage` display was too messy. – hpaulj Jan 04 '19 at 06:07
  • Thanks for clearing this up. Then I will try to post process the arguments with a `xor` after getting the arguments from argparse. – Eular Jan 04 '19 at 06:09
  • I expected it to look like this `usage: test num [-h] [-n N] (-x X | (-y Y -z Z))` – Eular Jan 04 '19 at 06:11

1 Answers1

1

I recommend removing the fancy testing on x,y,z, and doing it yourself after parsing. The logic of trying to do this in argparse itself is too complex. Testing isn't too bad, but a good user interface is harder.

import argparse

parser = argparse.ArgumentParser( prog="test")

subparsers = parser.add_subparsers(title='sub-commands')
num  = subparsers.add_parser("num")
num.add_argument("-n")
numr = num.add_argument_group("Required arguments")

#onr  =numr.add_mutually_exclusive_group(required=True)
numr.add_argument("-x")
#onr2 = onr.add_argument_group("new")
numr.add_argument("-y")
numr.add_argument("-z")

args = parser.parse_args()
print(args)

if args.x is None:
    if args.y is None or args.z is None:
        parser.error('both y and z are required')

sample session:

1122:~/mypy$ python3 stack54033455.py -h
usage: test [-h] {num} ...

optional arguments:
  -h, --help  show this help message and exit

sub-commands:
  {num}
2212:~/mypy$ python3 stack54033455.py num -h
usage: test num [-h] [-n N] [-x X] [-y Y] [-z Z]

optional arguments:
  -h, --help  show this help message and exit
  -n N

Required arguments:
  -x X
  -y Y
  -z Z
2212:~/mypy$ python3 stack54033455.py num -x foo
Namespace(n=None, x='foo', y=None, z=None)
2212:~/mypy$ python3 stack54033455.py num -y foo
Namespace(n=None, x=None, y='foo', z=None)
usage: test [-h] {num} ...
test: error: both y and z are required
2212:~/mypy$ python3 stack54033455.py num -y foo -z bar
Namespace(n=None, x=None, y='foo', z='bar')

I explored allowing nested groups with generalized logic in https://bugs.python.org/issue11588 (Add "necessarily inclusive" groups to argparse). My current thinking is that it would be nice to give you access to the seen_actions set, so you can do testing for usage without depending on the is None test. The current usage formatter is too brittle to generalize.

Using:

num  = subparsers.add_parser("num", usage="test num [-h] [-n N] (-x X | (-y Y -z Z))")

works.

hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • It would have been perfect if I just could add `usage="test num [-h] [-n N] (-x X | (-y Y -z Z))"` in the subparser – Eular Jan 04 '19 at 06:31
  • `add_parser` accepts a `usage` parameter (and most of the parameters that `ArgumentParser` takes). – hpaulj Jan 04 '19 at 06:38