8
8
from textwrap import dedent
9
9
from weakref import WeakKeyDictionary
10
10
11
+ from .functional import complement , keyfilter
11
12
from .typecheck import compatible
13
+ from .typed_signature import TypedSignature
12
14
from .utils import is_a , unique
13
15
14
16
first = itemgetter (0 )
@@ -21,19 +23,50 @@ class IncompleteImplementation(TypeError):
21
23
"""
22
24
23
25
26
+ CLASS_ATTRIBUTE_WHITELIST = frozenset ([
27
+ '__doc__' ,
28
+ '__module__' ,
29
+ '__name__' ,
30
+ '__qualname__' ,
31
+ '__weakref__' ,
32
+ ])
33
+
34
+ is_interface_field_name = complement (CLASS_ATTRIBUTE_WHITELIST .__contains__ )
35
+
36
+
37
+ def static_get_type_attr (t , name ):
38
+ """
39
+ Get a type attribute statically, circumventing the descriptor protocol.
40
+ """
41
+ for type_ in t .mro ():
42
+ try :
43
+ return vars (type_ )[name ]
44
+ except KeyError :
45
+ pass
46
+ raise AttributeError (name )
47
+
48
+
24
49
class InterfaceMeta (type ):
25
50
"""
26
51
Metaclass for interfaces.
27
52
28
- Supplies a ``_signatures`` attribute and a ``check_implementation`` method .
53
+ Supplies a ``_signatures`` attribute.
29
54
"""
30
55
def __new__ (mcls , name , bases , clsdict ):
31
56
signatures = {}
32
- for k , v in clsdict .items ():
57
+ for k , v in keyfilter ( is_interface_field_name , clsdict ) .items ():
33
58
try :
34
- signatures [k ] = inspect .signature (v )
35
- except TypeError :
36
- pass
59
+ signatures [k ] = TypedSignature (v )
60
+ except TypeError as e :
61
+ errmsg = (
62
+ "Couldn't parse signature for field "
63
+ "{iface_name}.{fieldname} of type {attrtype}." .format (
64
+ iface_name = name ,
65
+ fieldname = k ,
66
+ attrtype = getname (type (v )),
67
+ )
68
+ )
69
+ raise TypeError (errmsg ) from e
37
70
38
71
clsdict ['_signatures' ] = signatures
39
72
return super ().__new__ (mcls , name , bases , clsdict )
@@ -49,23 +82,33 @@ def _diff_signatures(self, type_):
49
82
50
83
Returns
51
84
-------
52
- missing, mismatched : list[str], dict[str -> signature]
53
- ``missing`` is a list of missing method names.
54
- ``mismatched `` is a dict mapping method names to incorrect
55
- signatures.
85
+ missing, mistyped, mismatched : list[str], dict[str -> type], dict[str -> signature] # noqa
86
+ ``missing`` is a list of missing interface names.
87
+ ``mistyped `` is a list mapping names to incorrect types.
88
+ ``mismatched`` is a dict mapping names to incorrect signatures.
56
89
"""
57
90
missing = []
91
+ mistyped = {}
58
92
mismatched = {}
59
93
for name , iface_sig in self ._signatures .items ():
60
94
try :
61
- f = getattr (type_ , name )
95
+ # Don't invoke the descriptor protocol here so that we get
96
+ # staticmethod/classmethod/property objects instead of the
97
+ # functions they wrap.
98
+ f = static_get_type_attr (type_ , name )
62
99
except AttributeError :
63
100
missing .append (name )
64
101
continue
65
- impl_sig = inspect .signature (f )
66
- if not compatible (impl_sig , iface_sig ):
102
+
103
+ impl_sig = TypedSignature (f )
104
+
105
+ if not issubclass (impl_sig .type , iface_sig .type ):
106
+ mistyped [name ] = impl_sig .type
107
+
108
+ if not compatible (impl_sig .signature , iface_sig .signature ):
67
109
mismatched [name ] = impl_sig
68
- return missing , mismatched
110
+
111
+ return missing , mistyped , mismatched
69
112
70
113
def verify (self , type_ ):
71
114
"""
@@ -85,16 +128,16 @@ def verify(self, type_):
85
128
-------
86
129
None
87
130
"""
88
- missing , mismatched = self ._diff_signatures (type_ )
89
- if not missing and not mismatched :
131
+ missing , mistyped , mismatched = self ._diff_signatures (type_ )
132
+ if not any (( missing , mistyped , mismatched )) :
90
133
return
91
- raise self ._invalid_implementation (type_ , missing , mismatched )
134
+ raise self ._invalid_implementation (type_ , missing , mistyped , mismatched )
92
135
93
- def _invalid_implementation (self , t , missing , mismatched ):
136
+ def _invalid_implementation (self , t , missing , mistyped , mismatched ):
94
137
"""
95
138
Make a TypeError explaining why ``t`` doesn't implement our interface.
96
139
"""
97
- assert missing or mismatched , "Implementation wasn't invalid."
140
+ assert missing or mistyped or mismatched , "Implementation wasn't invalid."
98
141
99
142
message = "\n class {C} failed to implement interface {I}:" .format (
100
143
C = getname (t ),
@@ -111,6 +154,17 @@ def _invalid_implementation(self, t, missing, mismatched):
111
154
missing_methods = self ._format_missing_methods (missing )
112
155
)
113
156
157
+ if mistyped :
158
+ message += dedent (
159
+ """
160
+
161
+ The following methods of {I} were implemented with incorrect types:
162
+ {mismatched_types}"""
163
+ ).format (
164
+ I = getname (self ),
165
+ mismatched_types = self ._format_mismatched_types (mistyped ),
166
+ )
167
+
114
168
if mismatched :
115
169
message += dedent (
116
170
"""
@@ -129,6 +183,17 @@ def _format_missing_methods(self, missing):
129
183
for name in missing
130
184
]))
131
185
186
+ def _format_mismatched_types (self , mistyped ):
187
+ return "\n " .join (sorted ([
188
+ " - {name}: {actual!r} is not a subtype "
189
+ "of expected type {expected!r}" .format (
190
+ name = name ,
191
+ actual = getname (bad_type ),
192
+ expected = getname (self ._signatures [name ].type ),
193
+ )
194
+ for name , bad_type in mistyped .items ()
195
+ ]))
196
+
132
197
def _format_mismatched_methods (self , mismatched ):
133
198
return "\n " .join (sorted ([
134
199
" - {name}{actual} != {name}{expected}" .format (
0 commit comments