Reputation: 291
I am writing a Python program using argparse. I have an argument for an ID value. The user can specify an ID value to be processed in the program. Or they can specify -a to specify that all IDs should be processed.
So, both of the following should be valid:
myprog 5
myprog -a
But if you haven't specified a specific ID, then -a is required and it should throw an error.
I have played around with a mutually exclusive group:
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-a', action='store_true', help=argparse.SUPPRESS)
group.add_argument("ID", action='store', nargs='?')
Which works but my parsed args end up being two arguments:
{'a': True, 'ID': None}
and if I try to add a similar group after that, say for another argument "max" that can be a max value or -i to mean ignore a max value:
group2 = parser.add_mutually_exclusive_group(required=True)
group2.add_argument('-i', action='store_true', help=argparse.SUPPRESS)
group2.add_argument("max", action='store', nargs='?')
Then if I try to parse arguments ['-a', '2'] it throws an error saying:
usage: args_exclusive_group.py [-h] [ID] [max]
args_exclusive_group.py: error: argument ID: not allowed with argument -a
Because it is treating the 2 as ID instead of as max. Is there something really easy that I am missing that would just allow a specified positional argument (ID or max) to also take a string that happens to "look like" an optional because it starts with "-"?
Upvotes: 0
Views: 1424
Reputation: 19404
If you want to keep it as 2 positional arguments, one approach might be to encapsulate the -a
and -i
flags inside their respective arguments and do some post-processing. Problem with that is that argparse
will automatically consider strings starting with -
as arguments:
positional arguments may only begin with
-
if they look like negative numbers and there are no options in the parser that look like negative numbers.
So if you change your keywords to say, all
and ign
, you can do something like:
parser = argparse.ArgumentParser()
parser.add_argument("ID")
parser.add_argument("max")
args = parser.parse_args()
if args.ID == 'all':
print("processing all")
elif args.ID.isdigit():
print(f"processing {args.ID}")
else:
parser.error("ID must be a number or 'all' to use all IDs")
if args.max == 'ign':
print("ignoring max")
elif args.max.isdigit():
print(f"max is {args.max}")
else:
parser.error("max must be a number or 'ign' to disable max")
And some run examples will be:
>>> tests.py 5 ign
processing 5
ignoring max
>>> tests.py all 7
processing all
max is 7
>>> tests.py blah 7
tests.py: error: ID must be a number or 'all' to use all IDs
>>> tests.py 5 blah
tests.py: error: max must be a number or 'ign' to disable max
If you really really must use -a
and -i
:
you can insert the pseudo-argument
'--'
which tellsparse_args()
that everything after that is a positional argument
Just change the parsing line to:
import sys
...
args = parser.parse_args(['--'] + sys.argv[1:])
Upvotes: 1
Reputation: 530970
The simplest thing would be to have just a single positional argument, whose value is either a special token like all
or the number of a particular process. You can handle this with a custom type.
def process_id(s):
if s == "all":
return s
try:
return int(s)
except ValueError:
raise argparse.ArgumentTypeError("Must be 'all' or an integer")
p = argparse.ArgumentParser()
p.add_argument("ID", type=process_id)
args = p.parse_args()
if args.ID == "all":
# process everything
else:
# process just args.ID
Upvotes: 0