1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
import typing
def check_type(attr_name: str, value: typing.Any, typeinfo: type) -> None:
"""
This function checks if the provided value is an instance of typeinfo
and raises a TypeError otherwise.
The following types from the typing package have specialized support:
- Union
- Tuple
- IO
"""
# If we realize that we need to extend this list substantially, it may make sense
# to use typeguard for this, but right now it's not worth the hassle for 16 lines of code.
e = TypeError("Expected {} for {}, but got {}.".format(
typeinfo,
attr_name,
type(value)
))
typename = str(typeinfo)
if typename.startswith("typing.Union"):
try:
types = typeinfo.__args__
except AttributeError:
# Python 3.5.x
types = typeinfo.__union_params__
for T in types:
try:
check_type(attr_name, value, T)
except TypeError:
pass
else:
return
raise e
elif typename.startswith("typing.Tuple"):
try:
types = typeinfo.__args__
except AttributeError:
# Python 3.5.x
types = typeinfo.__tuple_params__
if not isinstance(value, (tuple, list)):
raise e
if len(types) != len(value):
raise e
for i, (x, T) in enumerate(zip(value, types)):
check_type("{}[{}]".format(attr_name, i), x, T)
return
elif typename.startswith("typing.Sequence"):
try:
T = typeinfo.__args__[0]
except AttributeError:
# Python 3.5.0
T = typeinfo.__parameters__[0]
if not isinstance(value, (tuple, list)):
raise e
for v in value:
check_type(attr_name, v, T)
elif typename.startswith("typing.IO"):
if hasattr(value, "read"):
return
else:
raise e
elif not isinstance(value, typeinfo):
raise e
def get_arg_type_from_constructor_annotation(cls: type, attr: str) -> typing.Optional[type]:
"""
Returns the first type annotation for attr in the class hierarchy.
"""
for c in cls.mro():
if attr in getattr(c.__init__, "__annotations__", ()):
return c.__init__.__annotations__[attr]
|