2
votes

I've read through quite a few tutorials, questions and documentation and could not figure out how to achieve this:

prog.py [-h] [-d DEVICE -l | (-m M -s S -p P)]

When using the -l argument, prog.py reads from a device interface. When providing -m, -s and/or -p, prog.py writes to that interface. Reading and writing is not possible at the same time, so reading and writing arguments are mutually exclusive.

Once DEVICE has been specified, either -l or at least one of -m, -s or -p needs to be provided, it should also be possible to provide any combination of those three.

So far I approached this problem from various angles:

  • I tried mutually exclusive groups which do not work as only single arguments can mutually exclude each other (please correct me if I'm wrong)
  • I tried to set up sub parsers but I failed...

This is what I tried last:

import argparse
import sys

parser = argparse.ArgumentParser()
parser.add_argument("-d","--device",
        default="system",
        help="foo")

subparsers = parser.add_subparsers(help='foo')

read_parser = subparsers.add_parser('read', help='foo')
read_parser.add_argument("-l", "--list",
        action="store_true")

write_parser = subparsers.add_parser("write", help="bar")
write_parser.add_argument("-m","--mode",
        type=str,
        choices=["on","off","auto"],
        help="foo")
write_parser.add_argument("-s", "--season",
        type=str,
        choices=["winter","summer"],
        help="foo")
write_parser.add_argument("-p","--present",
        type=int,
        choices=[0,1],
        help="foo")
parser.parse_args()

This is not even close as with subparsers argparse expects either read or write. What I need to do is add groups that are mutually exclusive. Anyone has an idea of how to solve this?

1
I might replace -l with a subcommand read and add a (seemingly redundant) subcommand write. IMO, the best way to handle the requirement that at least one of a group of options be used is to just raise an error after parsing if mode, season and present are all None. Otherwise, you might define the write parser to take one more more "tagged" positional arguments from m:on, m:off, m:auto, s:winter, s:summer ,p0, and p1 rather than strictly optional options. - chepner
I tried adding a subgroup to a mutually exclusive group, by calling group.add_argument_group(). That didn't work either. I'm not even sure if argparse has a concept of subgroups, or if groups are even supposed to have an add_argument_group method. - user2357112 supports Monica
argument_group is only used in the help display. mutually_exclusivegroup` is used during parsing, and in the usage formatting. A MEG maybe placed inside an argument_group for help purposes. But putting an AG in a MEG doesn't do anything useful. MEG implement a simple XOR test, and nothing more complicted. There's no nesting of groups beyond what I just described, nor any fancier logic. - hpaulj

1 Answers

1
votes

You can check on your own (with basic argparse functionalities) if correct arguments combination was passed:

parser = argparse.ArgumentParser()

parser.add_argument("-d","--device",
    default="system",
    help="foo")
parser.add_argument("-l", "--list",
    action="store_true")
parser.add_argument("-m","--mode",
    type=str,
    choices=["on","off","auto"],
    help="foo")
parser.add_argument("-s", "--season",
    type=str,
    choices=["winter","summer"],
    help="foo")
parser.add_argument("-p","--present",
    type=int,
    choices=[0,1],
    help="foo")
args = parser.parse_args()

# if user defines device but not any read/write argument raise exception
if args.device is not None and not args.list and all(arg is None for arg in (args.season, args.present, args.mode)):
    parser.error('Wrong arguments passed')

# if user defines read and write arguments at the same time raise exception
if args.list and not all(arg is None for arg in (args.season, args.present, args.mode)):
    parser.error('Wrong arguments passed')