Skip to content

Commit 5c007f4

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 5c007f4

File tree

3 files changed

+65
-9
lines changed

3 files changed

+65
-9
lines changed

python-stdlib/argparse/argparse.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,36 @@ class _ArgError(BaseException):
1111

1212

1313
class _Arg:
14-
def __init__(self, names, dest, action, nargs, const, default, help):
14+
def __init__(self, names, dest, action, nargs, const, default, help, type):
1515
self.names = names
1616
self.dest = dest
1717
self.action = action
1818
self.nargs = nargs
1919
self.const = const
2020
self.default = default
2121
self.help = help
22+
self.type = type
23+
24+
def _apply(self, optname, arg):
25+
if self.type:
26+
try:
27+
return self.type(arg)
28+
except Exception as e:
29+
if not isinstance(e, TypeError) and not isinstance(e, ValueError):
30+
raise
31+
raise _ArgError("invalid value for %s: %s (%s)" % (optname, arg, str(e)))
32+
return arg
2233

2334
def parse(self, optname, args):
2435
# parse args for this arg
2536
if self.action == "store":
2637
if self.nargs is None:
2738
if args:
28-
return args.pop(0)
39+
return self._apply(optname, args.pop(0))
2940
else:
3041
raise _ArgError("expecting value for %s" % optname)
3142
elif self.nargs == "?":
32-
if args:
33-
return args.pop(0)
34-
else:
35-
return self.default
43+
return self._apply(optname, args.pop(0) if args else self.default)
3644
else:
3745
if self.nargs == "*":
3846
n = -1
@@ -52,7 +60,7 @@ def parse(self, optname, args):
5260
else:
5361
break
5462
else:
55-
ret.append(args.pop(0))
63+
ret.append(self._apply(optname, args.pop(0)))
5664
n -= 1
5765
if n > 0:
5866
raise _ArgError("expecting value for %s" % optname)
@@ -103,6 +111,12 @@ def add_argument(self, *args, **kwargs):
103111
dest = args[0]
104112
if not args:
105113
args = [dest]
114+
arg_type = kwargs.get("type", None)
115+
if arg_type is not None:
116+
if not callable(arg_type):
117+
raise ValueError("type is not callable")
118+
if default is not None and not isinstance(default, str):
119+
arg_type = None
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 (opt.default is not None) else None
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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,42 @@
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("-h", type=CustomArgType(1), default="1")
105+
args = parser.parse_args([])
106+
assert args.g == 1
107+
assert args.h == 2

0 commit comments

Comments
 (0)