2
2
interface
3
3
---------
4
4
"""
5
- from functools import wraps
6
- import inspect
5
+ from collections import defaultdict
7
6
from operator import attrgetter , itemgetter
8
7
from textwrap import dedent
9
8
from weakref import WeakKeyDictionary
10
9
11
10
from .compat import raise_from , with_metaclass
12
- from .functional import complement , keyfilter
11
+ from .default import default , warn_if_defaults_use_non_interface_members
12
+ from .formatting import bulleted_list
13
+ from .functional import complement , keyfilter , valfilter
13
14
from .typecheck import compatible
14
15
from .typed_signature import TypedSignature
15
16
from .utils import is_a , unique
18
19
getname = attrgetter ('__name__' )
19
20
20
21
21
- class IncompleteImplementation (TypeError ):
22
+ class InvalidImplementation (TypeError ):
22
23
"""
23
24
Raised when a class intending to implement an interface fails to do so.
24
25
"""
@@ -47,6 +48,37 @@ def static_get_type_attr(t, name):
47
48
raise AttributeError (name )
48
49
49
50
51
+ def _conflicting_defaults (typename , conflicts ):
52
+ """Format an error message for conflicting default implementations.
53
+
54
+ Parameters
55
+ ----------
56
+ typename : str
57
+ Name of the type for which we're producing an error.
58
+ conflicts : dict[str -> list[Interface]]
59
+ Map from strings to interfaces providing a default with that name.
60
+
61
+ Returns
62
+ -------
63
+ message : str
64
+ User-facing error message.
65
+ """
66
+ message = "\n class {C} received conflicting default implementations:" .format (
67
+ C = typename ,
68
+ )
69
+ for attrname , interfaces in conflicts .items ():
70
+ message += dedent (
71
+ """
72
+
73
+ The following interfaces provided default implementations for {attr!r}:
74
+ {interfaces}"""
75
+ ).format (
76
+ attr = attrname ,
77
+ interfaces = bulleted_list (sorted (map (getname , interfaces ))),
78
+ )
79
+ return InvalidImplementation (message )
80
+
81
+
50
82
class InterfaceMeta (type ):
51
83
"""
52
84
Metaclass for interfaces.
@@ -55,6 +87,7 @@ class InterfaceMeta(type):
55
87
"""
56
88
def __new__ (mcls , name , bases , clsdict ):
57
89
signatures = {}
90
+ defaults = {}
58
91
for k , v in keyfilter (is_interface_field_name , clsdict ).items ():
59
92
try :
60
93
signatures [k ] = TypedSignature (v )
@@ -69,7 +102,17 @@ def __new__(mcls, name, bases, clsdict):
69
102
)
70
103
raise_from (TypeError (errmsg ), e )
71
104
105
+ if isinstance (v , default ):
106
+ defaults [k ] = v
107
+
108
+ warn_if_defaults_use_non_interface_members (
109
+ name ,
110
+ defaults ,
111
+ set (signatures .keys ())
112
+ )
113
+
72
114
clsdict ['_signatures' ] = signatures
115
+ clsdict ['_defaults' ] = defaults
73
116
return super (InterfaceMeta , mcls ).__new__ (mcls , name , bases , clsdict )
74
117
75
118
def _diff_signatures (self , type_ ):
@@ -129,9 +172,20 @@ def verify(self, type_):
129
172
-------
130
173
None
131
174
"""
132
- missing , mistyped , mismatched = self ._diff_signatures (type_ )
175
+ raw_missing , mistyped , mismatched = self ._diff_signatures (type_ )
176
+
177
+ # See if we have defaults for missing methods.
178
+ missing = []
179
+ defaults_to_use = {}
180
+ for name in raw_missing :
181
+ try :
182
+ defaults_to_use [name ] = self ._defaults [name ].implementation
183
+ except KeyError :
184
+ missing .append (name )
185
+
133
186
if not any ((missing , mistyped , mismatched )):
134
- return
187
+ return defaults_to_use
188
+
135
189
raise self ._invalid_implementation (type_ , missing , mistyped , mismatched )
136
190
137
191
def _invalid_implementation (self , t , missing , mistyped , mismatched ):
@@ -176,7 +230,7 @@ def _invalid_implementation(self, t, missing, mistyped, mismatched):
176
230
I = getname (self ),
177
231
mismatched_methods = self ._format_mismatched_methods (mismatched ),
178
232
)
179
- return IncompleteImplementation (message )
233
+ return InvalidImplementation (message )
180
234
181
235
def _format_missing_methods (self , missing ):
182
236
return "\n " .join (sorted ([
@@ -231,18 +285,31 @@ def __new__(mcls, name, bases, clsdict, interfaces=empty_set):
231
285
return newtype
232
286
233
287
errors = []
234
- for iface in newtype .interfaces ():
288
+ default_impls = {}
289
+ default_providers = defaultdict (list )
290
+ for iface in sorted (newtype .interfaces (), key = getname ):
235
291
try :
236
- iface .verify (newtype )
237
- except IncompleteImplementation as e :
292
+ defaults_from_iface = iface .verify (newtype )
293
+ for name , impl in defaults_from_iface .items ():
294
+ default_impls [name ] = impl
295
+ default_providers [name ].append (iface )
296
+ except InvalidImplementation as e :
238
297
errors .append (e )
239
298
299
+ # The list of providers for `name`, if there's more than one.
300
+ duplicate_defaults = valfilter (lambda ifaces : len (ifaces ) > 1 , default_providers )
301
+ if duplicate_defaults :
302
+ errors .append (_conflicting_defaults (newtype .__name__ , duplicate_defaults ))
303
+ else :
304
+ for name , impl in default_impls .items ():
305
+ setattr (newtype , name , impl )
306
+
240
307
if not errors :
241
308
return newtype
242
309
elif len (errors ) == 1 :
243
310
raise errors [0 ]
244
311
else :
245
- raise IncompleteImplementation ( " \n \n " .join (map (str , errors )))
312
+ raise InvalidImplementation ( " \n " .join (map (str , errors )))
246
313
247
314
def __init__ (mcls , name , bases , clsdict , interfaces = empty_set ):
248
315
mcls ._interfaces = interfaces
@@ -308,7 +375,7 @@ def implements(*interfaces):
308
375
ordered_ifaces = tuple (sorted (interfaces , key = getname ))
309
376
iface_names = list (map (getname , ordered_ifaces ))
310
377
311
- name = "Implements{I }" .format (I = "_" .join (iface_names ))
378
+ name = "Implements{}" .format ("_" .join (iface_names ))
312
379
doc = dedent (
313
380
"""\
314
381
Implementation of {interfaces}.
@@ -336,5 +403,6 @@ def implements(*interfaces):
336
403
return result
337
404
return implements
338
405
406
+
339
407
implements = _make_implements ()
340
408
del _make_implements
0 commit comments