Skip to content

Commit df811c0

Browse files
committed
argparse: Add support for custom argument types.
This commit adds support for optional custom argument type validation to argparse.ArgumentParser, allowing for shorter argument validation code for both simple builtins and complex types. For example, assuming that a particular command line argument must be an integer, using "parser.add_argument('-a', type=int)" will make sure that any value passed to that argument that cannot be converted into an integer will trigger an argument validation error. Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
1 parent a7c805c commit df811c0

File tree

3 files changed

+70
-9
lines changed

3 files changed

+70
-9
lines changed

python-stdlib/argparse/argparse.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,39 @@ class _ArgError(BaseException):
1010
pass
1111

1212

13+
class ArgumentTypeError(BaseException):
14+
pass
15+
16+
1317
class _Arg:
14-
def __init__(self, names, dest, action, nargs, const, default, help):
18+
def __init__(self, names, dest, action, nargs, const, default, help, type):
1519
self.names = names
1620
self.dest = dest
1721
self.action = action
1822
self.nargs = nargs
1923
self.const = const
2024
self.default = default
2125
self.help = help
26+
self.type = type
27+
28+
def _apply(self, optname, arg):
29+
if self.type:
30+
try:
31+
return self.type(arg)
32+
except (ArgumentTypeError, TypeError, ValueError) as e:
33+
raise _ArgError("invalid value for %s: %s (%s)" % (optname, arg, str(e)))
34+
return arg
2235

2336
def parse(self, optname, args):
2437
# parse args for this arg
2538
if self.action == "store":
2639
if self.nargs is None:
2740
if args:
28-
return args.pop(0)
41+
return self._apply(optname, args.pop(0))
2942
else:
3043
raise _ArgError("expecting value for %s" % optname)
3144
elif self.nargs == "?":
32-
if args:
33-
return args.pop(0)
34-
else:
35-
return self.default
45+
return self._apply(optname, args.pop(0) if args else self.default)
3646
else:
3747
if self.nargs == "*":
3848
n = -1
@@ -52,7 +62,7 @@ def parse(self, optname, args):
5262
else:
5363
break
5464
else:
55-
ret.append(args.pop(0))
65+
ret.append(self._apply(optname, args.pop(0)))
5666
n -= 1
5767
if n > 0:
5868
raise _ArgError("expecting value for %s" % optname)
@@ -103,6 +113,10 @@ def add_argument(self, *args, **kwargs):
103113
dest = args[0]
104114
if not args:
105115
args = [dest]
116+
arg_type = kwargs.get("type", None)
117+
if arg_type is not None:
118+
if not callable(arg_type):
119+
raise ValueError("type is not callable")
106120
list.append(
107121
_Arg(
108122
args,
@@ -112,6 +126,7 @@ def add_argument(self, *args, **kwargs):
112126
const,
113127
default,
114128
kwargs.get("help", ""),
129+
arg_type,
115130
)
116131
)
117132

@@ -176,7 +191,9 @@ def _parse_args(self, args, return_unknown):
176191
arg_vals = []
177192
for opt in self.opt:
178193
arg_dest.append(opt.dest)
179-
arg_vals.append(opt.default)
194+
arg_vals.append(
195+
opt._apply(opt.dest, opt.default) if isinstance(opt.default, str) else opt.default
196+
)
180197

181198
# deal with unknown arguments, if needed
182199
unknown = []

python-stdlib/argparse/manifest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
metadata(version="0.4.0")
1+
metadata(version="0.4.1")
22

33
# Originally written by Damien George.
44

python-stdlib/argparse/test_argparse.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,47 @@
6666
args, rest = parser.parse_known_args(["a", "b", "c", "-b", "2", "--x", "5", "1"])
6767
assert args.a == ["a", "b"] and args.b == "2"
6868
assert rest == ["c", "--x", "5", "1"]
69+
70+
71+
class CustomArgType:
72+
def __init__(self, add):
73+
self.add = add
74+
75+
def __call__(self, value):
76+
return int(value) + self.add
77+
78+
79+
parser = argparse.ArgumentParser()
80+
parser.add_argument("-a", type=int)
81+
args = parser.parse_args(["-a", "123"])
82+
assert args.a == 123
83+
parser.add_argument("-b", type=str)
84+
args = parser.parse_args(["-b", "string"])
85+
assert args.b == "string"
86+
parser.add_argument("-c", type=CustomArgType(1))
87+
args = parser.parse_args(["-c", "123"])
88+
assert args.c == 124
89+
try:
90+
parser.add_argument("-d", type=())
91+
assert False
92+
except ValueError as e:
93+
assert "not callable" in str(e)
94+
parser.add_argument("-d", type=int, nargs="+")
95+
args = parser.parse_args(["-d", "123", "124", "125"])
96+
assert args.d == [123, 124, 125]
97+
parser.add_argument("-e", type=CustomArgType(1), nargs="+")
98+
args = parser.parse_args(["-e", "123", "124", "125"])
99+
assert args.e == [124, 125, 126]
100+
parser.add_argument("-f", type=CustomArgType(1), nargs="?")
101+
args = parser.parse_args(["-f", "123"])
102+
assert args.f == 124
103+
parser.add_argument("-g", type=CustomArgType(1), default=1)
104+
parser.add_argument("-i", type=CustomArgType(1), default="1")
105+
args = parser.parse_args([])
106+
assert args.g == 1
107+
assert args.i == 2
108+
parser.add_argument("-j", type=CustomArgType(1), default=1)
109+
args = parser.parse_args(["-j", "3"])
110+
assert args.g == 1
111+
assert args.i == 2
112+
assert args.j == 4

0 commit comments

Comments
 (0)