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