Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define a mutually exclusive group of two positional arguments?

I would like to use argparse to make some code to be used in the following two ways:

./tester.py all
./tester.py name someprocess

i.e. either all is specified OR name with some additional string.

I have tried to implement as follows:

import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('all', action='store_true', \
        help = "Stops all processes")
group.add_argument('name', \
        help = "Stops the named process")

print parser.parse_args()

which gives me an error

ValueError: mutually exclusive arguments must be optional

Any idea how to do it right? I also would like to avoid sub parsers in this case.

like image 866
Alex Avatar asked Mar 20 '13 17:03

Alex


2 Answers

The question is a year old, but since all the answers suggest a different syntax, I'll give something closer to the OP.

First, the problems with the OP code:

A positional store_true does not make sense (even if it is allowed). It requires no arguments, so it is always True. Giving an 'all' will produce error: unrecognized arguments: all.

The other argument takes one value and assigns it to the name attribute. It does not accept an additional process value.

Regarding the mutually_exclusive_group. That error message is raised even before parse_args. For such a group to make sense, all the alternatives have to be optional. That means either having a -- flag, or be a postional with nargs equal to ? or *. And doesn't make sense to have more than one such positional in the group.

The simplest alternative to using --all and --name, would be something like this:

p=argparse.ArgumentParser()
p.add_argument('mode', choices=['all','name'])
p.add_argument('process',nargs='?')

def foo(args):
    if args.mode == 'all' and args.process:
        pass # can ignore the  process value or raise a error
    if args.mode == 'name' and args.process is None:
        p.error('name mode requires a process')

args = p.parse_args()
foo(args) # now test the namespace for correct `process` argument.

Accepted namespaces would look like:

Namespace(mode='name', process='process1')
Namespace(mode='all', process=None)

choices imitates the behavior of a subparsers argument. Doing your own tests after parse_args is often simpler than making argparse do something special.

like image 197
hpaulj Avatar answered Nov 12 '22 14:11

hpaulj


import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-a','--all', action='store_true', \
        help = "Stops all processes")
group.add_argument('-n','--name', \
        help = "Stops the named process")

print parser.parse_args()

./tester.py -h

usage: zx.py [-h] (-a | -n NAME)

optional arguments:
  -h, --help            show this help message and exit
  -a, --all             Stops all processes
  -n NAME, --name NAME  Stops the named process
like image 1
Smart Manoj Avatar answered Nov 12 '22 14:11

Smart Manoj