Skip to content

Commit 13541ff

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 13541ff

File tree

3 files changed

+69
-9
lines changed

3 files changed

+69
-9
lines changed

python-stdlib/argparse/argparse.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,41 @@ 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 Exception as e:
33+
if isinstance(e, (ArgumentTypeError, TypeError, ValueError)):
34+
raise _ArgError("invalid value for %s: %s (%s)" % (optname, arg, str(e)))
35+
raise
36+
return arg
2237

2338
def parse(self, optname, args):
2439
# parse args for this arg
2540
if self.action == "store":
2641
if self.nargs is None:
2742
if args:
28-
return args.pop(0)
43+
return self._apply(optname, args.pop(0))
2944
else:
3045
raise _ArgError("expecting value for %s" % optname)
3146
elif self.nargs == "?":
32-
if args:
33-
return args.pop(0)
34-
else:
35-
return self.default
47+
return self._apply(optname, args.pop(0) if args else self.default)
3648
else:
3749
if self.nargs == "*":
3850
n = -1
@@ -52,7 +64,7 @@ def parse(self, optname, args):
5264
else:
5365
break
5466
else:
55-
ret.append(args.pop(0))
67+
ret.append(self._apply(optname, args.pop(0)))
5668
n -= 1
5769
if n > 0:
5870
raise _ArgError("expecting value for %s" % optname)
@@ -103,6 +115,12 @@ def add_argument(self, *args, **kwargs):
103115
dest = args[0]
104116
if not args:
105117
args = [dest]
118+
arg_type = kwargs.get("type", None)
119+
if arg_type is not None:
120+
if not callable(arg_type):
121+
raise ValueError("type is not callable")
122+
if default is not None and not isinstance(default, str):
123+
arg_type = None
106124
list.append(
107125
_Arg(
108126
args,
@@ -112,6 +130,7 @@ def add_argument(self, *args, **kwargs):
112130
const,
113131
default,
114132
kwargs.get("help", ""),
133+
arg_type,
115134
)
116135
)
117136

@@ -176,7 +195,9 @@ def _parse_args(self, args, return_unknown):
176195
arg_vals = []
177196
for opt in self.opt:
178197
arg_dest.append(opt.dest)
179-
arg_vals.append(opt.default)
198+
arg_vals.append(
199+
opt._apply(opt.dest, opt.default) if (opt.default is not None) else None
200+
)
180201

181202
# deal with unknown arguments, if needed
182203
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)