22interface
33---------
44"""
5- from functools import wraps
6- import inspect
5+ from collections import defaultdict
76from operator import attrgetter , itemgetter
87from textwrap import dedent
98from weakref import WeakKeyDictionary
109
1110from .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
1314from .typecheck import compatible
1415from .typed_signature import TypedSignature
1516from .utils import is_a , unique
1819getname = attrgetter ('__name__' )
1920
2021
21- class IncompleteImplementation (TypeError ):
22+ class InvalidImplementation (TypeError ):
2223 """
2324 Raised when a class intending to implement an interface fails to do so.
2425 """
@@ -47,6 +48,37 @@ def static_get_type_attr(t, name):
4748 raise AttributeError (name )
4849
4950
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+
5082class InterfaceMeta (type ):
5183 """
5284 Metaclass for interfaces.
@@ -55,6 +87,7 @@ class InterfaceMeta(type):
5587 """
5688 def __new__ (mcls , name , bases , clsdict ):
5789 signatures = {}
90+ defaults = {}
5891 for k , v in keyfilter (is_interface_field_name , clsdict ).items ():
5992 try :
6093 signatures [k ] = TypedSignature (v )
@@ -69,7 +102,17 @@ def __new__(mcls, name, bases, clsdict):
69102 )
70103 raise_from (TypeError (errmsg ), e )
71104
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+
72114 clsdict ['_signatures' ] = signatures
115+ clsdict ['_defaults' ] = defaults
73116 return super (InterfaceMeta , mcls ).__new__ (mcls , name , bases , clsdict )
74117
75118 def _diff_signatures (self , type_ ):
@@ -129,9 +172,20 @@ def verify(self, type_):
129172 -------
130173 None
131174 """
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+
133186 if not any ((missing , mistyped , mismatched )):
134- return
187+ return defaults_to_use
188+
135189 raise self ._invalid_implementation (type_ , missing , mistyped , mismatched )
136190
137191 def _invalid_implementation (self , t , missing , mistyped , mismatched ):
@@ -176,7 +230,7 @@ def _invalid_implementation(self, t, missing, mistyped, mismatched):
176230 I = getname (self ),
177231 mismatched_methods = self ._format_mismatched_methods (mismatched ),
178232 )
179- return IncompleteImplementation (message )
233+ return InvalidImplementation (message )
180234
181235 def _format_missing_methods (self , missing ):
182236 return "\n " .join (sorted ([
@@ -231,18 +285,31 @@ def __new__(mcls, name, bases, clsdict, interfaces=empty_set):
231285 return newtype
232286
233287 errors = []
234- for iface in newtype .interfaces ():
288+ default_impls = {}
289+ default_providers = defaultdict (list )
290+ for iface in sorted (newtype .interfaces (), key = getname ):
235291 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 :
238297 errors .append (e )
239298
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+
240307 if not errors :
241308 return newtype
242309 elif len (errors ) == 1 :
243310 raise errors [0 ]
244311 else :
245- raise IncompleteImplementation ( " \n \n " .join (map (str , errors )))
312+ raise InvalidImplementation ( " \n " .join (map (str , errors )))
246313
247314 def __init__ (mcls , name , bases , clsdict , interfaces = empty_set ):
248315 mcls ._interfaces = interfaces
@@ -308,7 +375,7 @@ def implements(*interfaces):
308375 ordered_ifaces = tuple (sorted (interfaces , key = getname ))
309376 iface_names = list (map (getname , ordered_ifaces ))
310377
311- name = "Implements{I }" .format (I = "_" .join (iface_names ))
378+ name = "Implements{}" .format ("_" .join (iface_names ))
312379 doc = dedent (
313380 """\
314381 Implementation of {interfaces}.
@@ -336,5 +403,6 @@ def implements(*interfaces):
336403 return result
337404 return implements
338405
406+
339407implements = _make_implements ()
340408del _make_implements
0 commit comments