4
4
"""
5
5
from functools import wraps
6
6
import inspect
7
- from operator import itemgetter
7
+ from operator import attrgetter , itemgetter
8
8
from textwrap import dedent
9
9
from weakref import WeakKeyDictionary
10
10
11
+ from .utils import is_a , unique
12
+
11
13
first = itemgetter (0 )
14
+ getname = attrgetter ('__name__' )
15
+
16
+
17
+ class IncompleteImplementation (TypeError ):
18
+ """
19
+ Raised when a class intending to implement an interface fails to do so.
20
+ """
12
21
13
22
14
23
def compatible (meth_sig , iface_sig ):
@@ -66,9 +75,9 @@ def _diff_signatures(self, type_):
66
75
mismatched [name ] = f_sig
67
76
return missing , mismatched
68
77
69
- def check_conforms (self , type_ ):
78
+ def verify (self , type_ ):
70
79
"""
71
- Check whether a type implements our interface .
80
+ Check whether a type implements ``self`` .
72
81
73
82
Parameters
74
83
----------
@@ -96,26 +105,30 @@ def _invalid_implementation(self, t, missing, mismatched):
96
105
assert missing or mismatched , "Implementation wasn't invalid."
97
106
98
107
message = "\n class {C} failed to implement interface {I}:" .format (
99
- C = t . __name__ ,
100
- I = self . __name__ ,
108
+ C = getname ( t ) ,
109
+ I = getname ( self ) ,
101
110
)
102
111
if missing :
103
112
message += dedent (
104
113
"""
105
114
106
- The following methods were not implemented:
115
+ The following methods of {I} were not implemented:
107
116
{missing_methods}"""
108
- ).format (missing_methods = self ._format_missing_methods (missing ))
117
+ ).format (
118
+ I = getname (self ),
119
+ missing_methods = self ._format_missing_methods (missing )
120
+ )
109
121
110
122
if mismatched :
111
123
message += (
112
- "\n \n The following methods were implemented but had invalid"
124
+ "\n \n The following methods of {I} were implemented with invalid"
113
125
" signatures:\n "
114
126
"{mismatched_methods}"
115
127
).format (
128
+ I = getname (self ),
116
129
mismatched_methods = self ._format_mismatched_methods (mismatched ),
117
130
)
118
- return TypeError (message )
131
+ return IncompleteImplementation (message )
119
132
120
133
def _format_missing_methods (self , missing ):
121
134
return "\n " .join (sorted ([
@@ -140,92 +153,123 @@ class Interface(metaclass=InterfaceMeta):
140
153
"""
141
154
142
155
156
+ empty_set = frozenset ([])
157
+
158
+
143
159
class ImplementsMeta (type ):
144
160
"""
145
161
Metaclass for implementations of particular interfaces.
146
162
"""
147
- def __new__ (mcls , name , bases , clsdict , base = False ):
163
+ def __new__ (mcls , name , bases , clsdict , interfaces = empty_set ):
164
+ assert isinstance (interfaces , frozenset )
165
+
148
166
newtype = super ().__new__ (mcls , name , bases , clsdict )
149
167
150
- if base :
168
+ if interfaces :
151
169
# Don't do checks on the types returned by ``implements``.
152
170
return newtype
153
171
172
+ errors = []
154
173
for iface in newtype .interfaces ():
155
- iface .check_conforms (newtype )
174
+ try :
175
+ iface .verify (newtype )
176
+ except IncompleteImplementation as e :
177
+ errors .append (e )
156
178
157
- return newtype
179
+ if not errors :
180
+ return newtype
181
+ elif len (errors ) == 1 :
182
+ raise errors [0 ]
183
+ else :
184
+ raise IncompleteImplementation ("\n \n " .join (map (str , errors )))
158
185
159
- def __init__ (mcls , name , bases , clsdict , base = False ):
186
+ def __init__ (mcls , name , bases , clsdict , interfaces = empty_set ):
187
+ mcls ._interfaces = interfaces
160
188
super ().__init__ (name , bases , clsdict )
161
189
162
190
def interfaces (self ):
163
- """
164
- Return a generator of interfaces implemented by this type.
191
+ yield from unique (self ._interfaces_with_duplicates ())
165
192
166
- Yields
167
- ------
168
- iface : Interface
169
- """
170
- for base in self .mro ():
171
- if isinstance (base , ImplementsMeta ):
172
- yield base .interface
193
+ def _interfaces_with_duplicates (self ):
194
+ yield from self ._interfaces
195
+ for t in filter (is_a (ImplementsMeta ), self .mro ()):
196
+ yield from t ._interfaces
197
+
198
+
199
+ def format_iface_method_docs (I ):
200
+ iface_name = getname (I )
201
+ return "\n " .join ([
202
+ "{iface_name}.{method_name}{sig}" .format (
203
+ iface_name = iface_name ,
204
+ method_name = method_name ,
205
+ sig = sig ,
206
+ )
207
+ for method_name , sig in sorted (list (I ._signatures .items ()), key = first )
208
+ ])
173
209
174
210
175
- def weakmemoize_implements (f ):
176
- "One-off weakmemoize implementation for ``implements``."
211
+ def _make_implements ():
177
212
_memo = WeakKeyDictionary ()
178
213
179
- @wraps (f )
180
- def _f (I ):
214
+ def implements (* interfaces ):
215
+ """
216
+ Make a base for classes that implement ``*interfaces``.
217
+
218
+ Parameters
219
+ ----------
220
+ I : Interface
221
+
222
+ Returns
223
+ -------
224
+ base : type
225
+ A type validating that subclasses must implement all interface
226
+ methods of I.
227
+ """
228
+ if not interfaces :
229
+ raise TypeError ("implements() requires at least one interface" )
230
+
231
+ interfaces = frozenset (interfaces )
181
232
try :
182
- return _memo [I ]
233
+ return _memo [interfaces ]
183
234
except KeyError :
184
235
pass
185
- ret = f (I )
186
- _memo [I ] = ret
187
- return ret
188
- return _f
189
236
237
+ for I in interfaces :
238
+ if not issubclass (I , Interface ):
239
+ raise TypeError (
240
+ "implements() expected an Interface, but got %s." % I
241
+ )
242
+
243
+ ordered_ifaces = tuple (sorted (interfaces , key = getname ))
244
+ iface_names = list (map (getname , ordered_ifaces ))
245
+
246
+ name = "Implements{I}" .format (I = "_" .join (iface_names ))
247
+ doc = dedent (
248
+ """\
249
+ Implementation of {interfaces}.
250
+
251
+ Methods
252
+ -------
253
+ {methods}"""
254
+ ).format (
255
+ interfaces = ', ' .join (iface_names ),
256
+ methods = "\n " .join (map (format_iface_method_docs , ordered_ifaces )),
257
+ )
190
258
191
- @weakmemoize_implements
192
- def implements (I ):
193
- """
194
- Make a base for classes that implement ``I``.
195
-
196
- Parameters
197
- ----------
198
- I : Interface
199
-
200
- Returns
201
- -------
202
- base : type
203
- A type validating that subclasses must implement all interface
204
- methods of I.
205
- """
206
- if not issubclass (I , Interface ):
207
- raise TypeError (
208
- "implements() expected an Interface, but got %s." % I
259
+ result = ImplementsMeta (
260
+ name ,
261
+ (object ,),
262
+ {'__doc__' : doc },
263
+ interfaces = interfaces ,
209
264
)
210
265
211
- name = "Implements{I}" .format (I = I .__name__ )
212
- doc = dedent (
213
- """\
214
- Implementation of {I}.
266
+ # NOTE: It's important for correct weak-memoization that this is set is
267
+ # stored somewhere on the resulting type.
268
+ assert result ._interfaces is interfaces , "Interfaces not stored."
215
269
216
- Methods
217
- -------
218
- {methods}"""
219
- ).format (
220
- I = I .__name__ ,
221
- methods = "\n " .join (
222
- "{name}{sig}" .format (name = name , sig = sig )
223
- for name , sig in sorted (list (I ._signatures .items ()), key = first )
224
- )
225
- )
226
- return ImplementsMeta (
227
- name ,
228
- (object ,),
229
- {'__doc__' : doc , 'interface' : I },
230
- base = True ,
231
- )
270
+ _memo [interfaces ] = result
271
+ return result
272
+ return implements
273
+
274
+ implements = _make_implements ()
275
+ del _make_implements
0 commit comments