1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
|
##############################################################################
# Copyright (c) 2020 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
##############################################################################
import itertools
from types import FunctionType
from zope.interface import Interface
from zope.interface import classImplements
from zope.interface.interface import InterfaceClass
from zope.interface.interface import _decorator_non_return
from zope.interface.interface import fromFunction
__all__ = [
# Nothing public here.
]
# pylint:disable=inherit-non-class,
# pylint:disable=no-self-argument,no-method-argument
# pylint:disable=unexpected-special-method-signature
class optional:
# Apply this decorator to a method definition to make it
# optional (remove it from the list of required names), overriding
# the definition inherited from the ABC.
def __init__(self, method):
self.__doc__ = method.__doc__
class ABCInterfaceClass(InterfaceClass):
"""
An interface that is automatically derived from a
:class:`abc.ABCMeta` type.
Internal use only.
The body of the interface definition *must* define
a property ``abc`` that is the ABC to base the interface on.
If ``abc`` is *not* in the interface definition, a regular
interface will be defined instead (but ``extra_classes`` is still
respected).
Use the ``@optional`` decorator on method definitions if
the ABC defines methods that are not actually required in all cases
because the Python language has multiple ways to implement a protocol.
For example, the ``iter()`` protocol can be implemented with
``__iter__`` or the pair ``__len__`` and ``__getitem__``.
When created, any existing classes that are registered to conform
to the ABC are declared to implement this interface. This is *not*
automatically updated as the ABC registry changes. If the body of the
interface definition defines ``extra_classes``, it should be a
tuple giving additional classes to declare implement the interface.
Note that this is not fully symmetric. For example, it is usually
the case that a subclass relationship carries the interface
declarations over::
>>> from zope.interface import Interface
>>> class I1(Interface):
... pass
...
>>> from zope.interface import implementer
>>> @implementer(I1)
... class Root(object):
... pass
...
>>> class Child(Root):
... pass
...
>>> child = Child()
>>> isinstance(child, Root)
True
>>> from zope.interface import providedBy
>>> list(providedBy(child))
[<InterfaceClass __main__.I1>]
However, that's not the case with ABCs and ABC interfaces. Just
because ``isinstance(A(), AnABC)`` and ``isinstance(B(), AnABC)``
are both true, that doesn't mean there's any class hierarchy
relationship between ``A`` and ``B``, or between either of them
and ``AnABC``. Thus, if ``AnABC`` implemented ``IAnABC``, it would
not follow that either ``A`` or ``B`` implements ``IAnABC`` (nor
their instances provide it)::
>>> class SizedClass(object):
... def __len__(self): return 1
...
>>> from collections.abc import Sized
>>> isinstance(SizedClass(), Sized)
True
>>> from zope.interface import classImplements
>>> classImplements(Sized, I1)
None
>>> list(providedBy(SizedClass()))
[]
Thus, to avoid conflicting assumptions, ABCs should not be
declared to implement their parallel ABC interface. Only concrete
classes specifically registered with the ABC should be declared to
do so.
.. versionadded:: 5.0.0
"""
# If we could figure out invalidation, and used some special
# Specification/Declaration instances, and override the method
# ``providedBy`` here, perhaps we could more closely integrate with ABC
# virtual inheritance?
def __init__(self, name, bases, attrs):
# go ahead and give us a name to ease debugging.
self.__name__ = name
extra_classes = attrs.pop('extra_classes', ())
ignored_classes = attrs.pop('ignored_classes', ())
if 'abc' not in attrs:
# Something like ``IList(ISequence)``: We're extending
# abc interfaces but not an ABC interface ourself.
InterfaceClass.__init__(self, name, bases, attrs)
ABCInterfaceClass.__register_classes(
self, extra_classes, ignored_classes,
)
self.__class__ = InterfaceClass
return
based_on = attrs.pop('abc')
self.__abc = based_on
self.__extra_classes = tuple(extra_classes)
self.__ignored_classes = tuple(ignored_classes)
assert name[1:] == based_on.__name__, (name, based_on)
methods = {
# Passing the name is important in case of aliases,
# e.g., ``__ror__ = __or__``.
k: self.__method_from_function(v, k)
for k, v in vars(based_on).items()
if isinstance(v, FunctionType) and
not self.__is_private_name(k) and
not self.__is_reverse_protocol_name(k)
}
methods['__doc__'] = self.__create_class_doc(attrs)
# Anything specified in the body takes precedence.
methods.update(attrs)
InterfaceClass.__init__(self, name, bases, methods)
self.__register_classes()
@staticmethod
def __optional_methods_to_docs(attrs):
optionals = {k: v for k, v in attrs.items() if isinstance(v, optional)}
for k in optionals:
attrs[k] = _decorator_non_return
if not optionals:
return ''
docs = "\n\nThe following methods are optional:\n - " + "\n-".join(
f"{k}\n{v.__doc__}" for k, v in optionals.items()
)
return docs
def __create_class_doc(self, attrs):
based_on = self.__abc
def ref(c):
mod = c.__module__
name = c.__name__
if mod == str.__module__:
return "`%s`" % name
if mod == '_io':
mod = 'io'
return f"`{mod}.{name}`"
implementations_doc = "\n - ".join(
ref(c)
for c in sorted(self.getRegisteredConformers(), key=ref)
)
if implementations_doc:
implementations_doc = (
"\n\nKnown implementations are:\n\n - " + implementations_doc
)
based_on_doc = (based_on.__doc__ or '')
based_on_doc = based_on_doc.splitlines()
based_on_doc = based_on_doc[0] if based_on_doc else ''
doc = """Interface for the ABC `{}.{}`.\n\n{}{}{}""".format(
based_on.__module__, based_on.__name__,
attrs.get('__doc__', based_on_doc),
self.__optional_methods_to_docs(attrs),
implementations_doc
)
return doc
@staticmethod
def __is_private_name(name):
if name.startswith('__') and name.endswith('__'):
return False
return name.startswith('_')
@staticmethod
def __is_reverse_protocol_name(name):
# The reverse names, like __rand__,
# aren't really part of the protocol. The interpreter has
# very complex behaviour around invoking those. PyPy
# doesn't always even expose them as attributes.
return name.startswith('__r') and name.endswith('__')
def __method_from_function(self, function, name):
method = fromFunction(function, self, name=name)
# Eliminate the leading *self*, which is implied in
# an interface, but explicit in an ABC.
method.positional = method.positional[1:]
return method
def __register_classes(self, conformers=None, ignored_classes=None):
# Make the concrete classes already present in our ABC's registry
# declare that they implement this interface.
conformers = (
conformers if conformers is not None
else self.getRegisteredConformers()
)
ignored = (
ignored_classes if ignored_classes is not None
else self.__ignored_classes
)
for cls in conformers:
if cls in ignored:
continue
classImplements(cls, self)
def getABC(self):
"""
Return the ABC this interface represents.
"""
return self.__abc
def getRegisteredConformers(self):
"""
Return an iterable of the classes that are known to conform to
the ABC this interface parallels.
"""
based_on = self.__abc
# The registry only contains things that aren't already
# known to be subclasses of the ABC. But the ABC is in charge
# of checking that, so its quite possible that registrations
# are in fact ignored, winding up just in the _abc_cache.
try:
registered = (
list(based_on._abc_registry) + list(based_on._abc_cache)
)
except AttributeError:
# Rewritten in C in CPython 3.7.
# These expose the underlying weakref.
from abc import _get_dump
data = _get_dump(based_on)
registry = data[0]
cache = data[1]
registered = [x() for x in itertools.chain(registry, cache)]
registered = [x for x in registered if x is not None]
return set(itertools.chain(registered, self.__extra_classes))
def _create_ABCInterface():
# It's a two-step process to create the root ABCInterface, because without
# specifying a corresponding ABC, using the normal constructor gets us a
# plain InterfaceClass object, and there is no ABC to associate with the
# root.
abc_name_bases_attrs = ('ABCInterface', (Interface,), {})
instance = ABCInterfaceClass.__new__(
ABCInterfaceClass, *abc_name_bases_attrs,
)
InterfaceClass.__init__(instance, *abc_name_bases_attrs)
return instance
ABCInterface = _create_ABCInterface()
|