Reputation: 44385
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.
Upvotes: 17
Views: 11882
Reputation: 5851
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
Upvotes: 3
Reputation: 1
This is probably what you're looking for:
group.add_argument('--all', dest=is_all, action='store_true')
group.add_argument('--name', dest=names, nargs='+')
Passing --name will then require at list one value and store them as a list.
Upvotes: -2
Reputation: 231615
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.
Upvotes: 16
Reputation: 9117
I would agree that this looks exactly like a sub-parser problem, and that if you don't want to make it an optional argument by using --all
and --name
, one suggestion from me would be just to ignore the all
and name
altogether, and use the following semantics:
tester.py
is called without any arguments, stop all process.tester.py
is called with some arguments, stop only those processes.Which can be done using:
import argparse, sys
parser = argparse.ArgumentParser()
parser.add_argument('processes', nargs='*')
parsed = parser.parse(sys.argv[1:])
print parsed
which will behave as follows:
$ python tester.py Namespace(processes=[]) $ python tester.py proc1 Namespace(processes=['proc1'])
Or, if you insist on your own syntax, you can create a custom class. And actually you're not having a "mutually exclusive group" case, since I assume if all
is specified, you will ignore the rest of the arguments (even when name
is one of the other arguments), and when name
is specified, anything else after that will be regarded as processes' name.
import argparse
import sys
class AllOrName(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if len(values)==0:
raise argparse.ArgumentError(self, 'too few arguments')
if values[0]=='all':
setattr(namespace, 'all', True)
elif values[0]=='name':
if len(values)==1:
raise argparse.ArgumentError(self, 'please specify at least one process name')
setattr(namespace, 'name', values[1:])
else:
raise argparse.ArgumentError(self, 'only "all" or "name" should be specified')
parser = argparse.ArgumentParser()
parser.add_argument('processes', nargs='*', action=AllOrName)
parsed = parser.parse_args(sys.argv[1:])
print parsed
with the following behaviour:
$ python argparse_test.py name usage: argparse_test.py [-h] [processes [processes ...]] argparse_test.py: error: argument processes: please specify at least one process name $ python argparse_test.py name proc1 Namespace(name=['proc1'], processes=None) $ python argparse_test.py all Namespace(all=True, processes=None) $ python argparse_test.py host usage: argparse_test.py [-h] [processes [processes ...]] argparse_test.py: error: argument processes: only "all" or "name" should be specified $ python argparse_test.py usage: argparse_test.py [-h] [processes [processes ...]] argparse_test.py: error: argument processes: too few arguments
Upvotes: 0
Reputation: 1048
"OR name with some additional string."
positional argument cannot take additional string
I think the best solution for you is (named test.py):
import argparse
p = argparse.ArgumentParser()
meg = p.add_mutually_exclusive_group()
meg.add_argument('-a', '--all', action='store_true', default=None)
meg.add_argument('-n', '--name', nargs='+')
print p.parse_args([])
print p.parse_args(['-a'])
print p.parse_args('--name process'.split())
print p.parse_args('--name process1 process2'.split())
print p.parse_args('--all --name process1'.split())
$ python test.py
Namespace(all=None, name=None)
Namespace(all=True, name=None)
Namespace(all=None, name=['process'])
Namespace(all=None, name=['process1', 'process2'])
usage: t2.py [-h] [-a | -n NAME [NAME ...]]
t2.py: error: argument -n/--name: not allowed with argument -a/--all
Upvotes: 0