Reputation: 749
I want to have an option which takes one or two arguments, specifically a timestamp and an optional tolerance.
I understand that I should just use nargs='+'
and error out if I get more than two values, which is what I am doing.
I am also using metavar=('timestamp', 'tolerance')
so the two values can be named.
However, the help message still looks like this:
usage: foo.py [-h] [-t timestamp [tolerance ...]]
Which incorrectly implies that -t
can take more than two arguments.
How can I get it to just say [-t timestamp [tolerance]]
? My actual code is below:
import argparse
import sys
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--timestamp', nargs='+', metavar=('timestamp', 'tolerance'))
args = parser.parse_args()
if args.timestamp and len(args.timestamp) > 2:
sys.exit('Argument --timestamp takes one or two values')
Upvotes: 8
Views: 4157
Reputation: 40773
My approach is to create a function which split the value. If there are more than 2 values, or less than 1, throw a ValueError
so argparse
can complain in my behalf.
I also use a name space to name the stamp and tolerance, making it easy for the caller.
#!/usr/bin/env python3
import argparse
def stamp_and_tolerance(value=None):
if value is None:
return argparse.Namespace(stamp=None, tolerance=None)
tokens = value.split(",")
if len(tokens) == 1:
return argparse.Namespace(stamp=tokens[0], tolerance=None)
elif len(tokens) == 2:
return argparse.Namespace(stamp=tokens[0], tolerance=tokens[1])
raise ValueError()
parser = argparse.ArgumentParser()
parser.add_argument(
"-t",
"--timestamp",
type=stamp_and_tolerance,
default=stamp_and_tolerance(),
metavar="stamp[,tolerance]",
help="Timestamp and optional tolerance",
)
args = parser.parse_args()
print(f"Stamp: {args.timestamp.stamp}")
print(f"Tolerance: {args.timestamp.tolerance}")
Sample runs
./my.py -h
usage: my.py [-h] [-t stamp[,tolerance]]
options:
-h, --help show this help message and exit
-t stamp[,tolerance], --timestamp stamp[,tolerance]
Timestamp and optional tolerance
./my.py
Stamp: None
Tolerance: None
./my.py -t mystamp
Stamp: mystamp
Tolerance: None
./my.py -t mystamp,mytolerance
Stamp: mystamp
Tolerance: mytolerance
Notes
if args.timestamp != None
SimpleNamespace
so the arguments are named, making it nicer to accessUpvotes: 0
Reputation: 745
I realize I am really late to the party on this, but I had to accomplish the same thing for a work project. Below is a much simplified version of what I did.
FULL DISCLOSURE: This is obviously hacky as it relies on a private function; this was the only way I saw to do this as there does not appear to be built in support. My solution was part of an application that was packaged to include the specific version of python (3.x) that I needed (as I access a private API to accomplish this) and my project has significant automated testing to catch any breakage in the future. You have been warned.
import argparse
import re as _re
class CustomParser(argparse.ArgumentParser):
def _match_argument(self, action, arg_strings_pattern):
if action.dest == 'name':
# Account for flexible number of arguments. The pattern is copied from the parent class'
# _get_nargs_pattern() function.
narg_pattern = '(-*A{1,2})'
match = _re.match(narg_pattern , arg_strings_pattern)
if match:
return len(match.group(1))
else:
raise argparse.ArgumentError(action, "expected {} or {} arguments".format(1, 2))
else:
return super()._match_argument(action, arg_strings_pattern)
if __name__ == '__main__':
parser = CustomParser("Flexible argument number test")
# nargs must be 2 so that the help output properly formats the metavar argument.
# Notice that I added '[]' around the optional argument to be consistent with argparse.
parser.add_argument("--name", nargs=2, metavar=("FIRST", "[LAST]"),
help="Your name: FIRST LAST. The last name is optional.")
args = parser.parse_args()
print(args)
Example output:
SCRIPT --help
usage: Flexible argument number test [-h] [--name FIRST [LAST]]
optional arguments:
-h, --help show this help message and exit
--name FIRST [LAST] Your name: FIRST LAST. The last name is optional.
SCRIPT --name John
Namespace(name=['John'])
SCRIPT --name John Smith
Namespace(name=['John', 'Smith'])
SCRIPT --name
usage: Flexible argument number test [-h] [--name FIRST [LAST]]
Flexible argument number test: error: argument --name: expected 1 or 2 arguments
Obviously there are things that you would probably want to address such as:
Upvotes: 0