• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / zope.interface / 16098775349

04 Mar 2025 10:16PM UTC coverage: 99.073% (+0.05%) from 99.022%
16098775349

push

github

web-flow
Merge pull request #338 from zopefoundation/config-with-c-code-template-3c1c588c

Apply latest meta templates, drop support for Python 3.8

2436 of 2464 branches covered (98.86%)

Branch coverage included in aggregate %.

12209 of 12318 relevant lines covered (99.12%)

6.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.54
/src/zope/interface/common/__init__.py
1
##############################################################################
2
# Copyright (c) 2020 Zope Foundation and Contributors.
3
# All Rights Reserved.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE.
11
##############################################################################
12

13
import itertools
7✔
14
from types import FunctionType
7✔
15

16
from zope.interface import Interface
7✔
17
from zope.interface import classImplements
7✔
18
from zope.interface.interface import InterfaceClass
7✔
19
from zope.interface.interface import _decorator_non_return
7✔
20
from zope.interface.interface import fromFunction
7✔
21

22

23
__all__ = [
7✔
24
    # Nothing public here.
25
]
26

27
# pylint:disable=inherit-non-class,
28
# pylint:disable=no-self-argument,no-method-argument
29
# pylint:disable=unexpected-special-method-signature
30

31

32
class optional:
7✔
33
    # Apply this decorator to a method definition to make it
34
    # optional (remove it from the list of required names), overriding
35
    # the definition inherited from the ABC.
36
    def __init__(self, method):
7✔
37
        self.__doc__ = method.__doc__
7✔
38

39

40
class ABCInterfaceClass(InterfaceClass):
7✔
41
    """
42
    An interface that is automatically derived from a
43
    :class:`abc.ABCMeta` type.
44

45
    Internal use only.
46

47
    The body of the interface definition *must* define
48
    a property ``abc`` that is the ABC to base the interface on.
49

50
    If ``abc`` is *not* in the interface definition, a regular
51
    interface will be defined instead (but ``extra_classes`` is still
52
    respected).
53

54
    Use the ``@optional`` decorator on method definitions if
55
    the ABC defines methods that are not actually required in all cases
56
    because the Python language has multiple ways to implement a protocol.
57
    For example, the ``iter()`` protocol can be implemented with
58
    ``__iter__`` or the pair ``__len__`` and ``__getitem__``.
59

60
    When created, any existing classes that are registered to conform
61
    to the ABC are declared to implement this interface. This is *not*
62
    automatically updated as the ABC registry changes. If the body of the
63
    interface definition defines ``extra_classes``, it should be a
64
    tuple giving additional classes to declare implement the interface.
65

66
    Note that this is not fully symmetric. For example, it is usually
67
    the case that a subclass relationship carries the interface
68
    declarations over::
69

70
        >>> from zope.interface import Interface
71
        >>> class I1(Interface):
72
        ...     pass
73
        ...
74
        >>> from zope.interface import implementer
75
        >>> @implementer(I1)
76
        ... class Root(object):
77
        ...     pass
78
        ...
79
        >>> class Child(Root):
80
        ...     pass
81
        ...
82
        >>> child = Child()
83
        >>> isinstance(child, Root)
84
        True
85
        >>> from zope.interface import providedBy
86
        >>> list(providedBy(child))
87
        [<InterfaceClass __main__.I1>]
88

89
    However, that's not the case with ABCs and ABC interfaces. Just
90
    because ``isinstance(A(), AnABC)`` and ``isinstance(B(), AnABC)``
91
    are both true, that doesn't mean there's any class hierarchy
92
    relationship between ``A`` and ``B``, or between either of them
93
    and ``AnABC``. Thus, if ``AnABC`` implemented ``IAnABC``, it would
94
    not follow that either ``A`` or ``B`` implements ``IAnABC`` (nor
95
    their instances provide it)::
96

97
        >>> class SizedClass(object):
98
        ...     def __len__(self): return 1
99
        ...
100
        >>> from collections.abc import Sized
101
        >>> isinstance(SizedClass(), Sized)
102
        True
103
        >>> from zope.interface import classImplements
104
        >>> classImplements(Sized, I1)
105
        None
106
        >>> list(providedBy(SizedClass()))
107
        []
108

109
    Thus, to avoid conflicting assumptions, ABCs should not be
110
    declared to implement their parallel ABC interface. Only concrete
111
    classes specifically registered with the ABC should be declared to
112
    do so.
113

114
    .. versionadded:: 5.0.0
115
    """
116

117
    # If we could figure out invalidation, and used some special
118
    # Specification/Declaration instances, and override the method
119
    # ``providedBy`` here, perhaps we could more closely integrate with ABC
120
    # virtual inheritance?
121

122
    def __init__(self, name, bases, attrs):
7✔
123
        # go ahead and give us a name to ease debugging.
124
        self.__name__ = name
7✔
125
        extra_classes = attrs.pop('extra_classes', ())
7✔
126
        ignored_classes = attrs.pop('ignored_classes', ())
7✔
127

128
        if 'abc' not in attrs:
7✔
129
            # Something like ``IList(ISequence)``: We're extending
130
            # abc interfaces but not an ABC interface ourself.
131
            InterfaceClass.__init__(self, name, bases, attrs)
7✔
132
            ABCInterfaceClass.__register_classes(
7✔
133
                self, extra_classes, ignored_classes,
134
            )
135
            self.__class__ = InterfaceClass
7✔
136
            return
7✔
137

138
        based_on = attrs.pop('abc')
7✔
139
        self.__abc = based_on
7✔
140
        self.__extra_classes = tuple(extra_classes)
7✔
141
        self.__ignored_classes = tuple(ignored_classes)
7✔
142

143
        assert name[1:] == based_on.__name__, (name, based_on)
7✔
144
        methods = {
7✔
145
            # Passing the name is important in case of aliases,
146
            # e.g., ``__ror__ = __or__``.
147
            k: self.__method_from_function(v, k)
148
            for k, v in vars(based_on).items()
149
            if isinstance(v, FunctionType) and
150
            not self.__is_private_name(k) and
151
            not self.__is_reverse_protocol_name(k)
152
        }
153

154
        methods['__doc__'] = self.__create_class_doc(attrs)
7✔
155
        # Anything specified in the body takes precedence.
156
        methods.update(attrs)
7✔
157
        InterfaceClass.__init__(self, name, bases, methods)
7✔
158
        self.__register_classes()
7✔
159

160
    @staticmethod
7✔
161
    def __optional_methods_to_docs(attrs):
7✔
162
        optionals = {k: v for k, v in attrs.items() if isinstance(v, optional)}
7✔
163
        for k in optionals:
7✔
164
            attrs[k] = _decorator_non_return
7✔
165

166
        if not optionals:
7✔
167
            return ''
7✔
168

169
        docs = "\n\nThe following methods are optional:\n - " + "\n-".join(
7✔
170
            f"{k}\n{v.__doc__}" for k, v in optionals.items()
171
        )
172
        return docs
7✔
173

174
    def __create_class_doc(self, attrs):
7✔
175
        based_on = self.__abc
7✔
176

177
        def ref(c):
7✔
178
            mod = c.__module__
7✔
179
            name = c.__name__
7✔
180
            if mod == str.__module__:
7✔
181
                return "`%s`" % name
7✔
182
            if mod == '_io':
7✔
183
                mod = 'io'
7✔
184
            return f"`{mod}.{name}`"
7✔
185

186
        implementations_doc = "\n - ".join(
7✔
187
            ref(c)
188
            for c in sorted(self.getRegisteredConformers(), key=ref)
189
        )
190
        if implementations_doc:
7✔
191
            implementations_doc = (
7✔
192
                "\n\nKnown implementations are:\n\n - " + implementations_doc
193
            )
194

195
        based_on_doc = (based_on.__doc__ or '')
7✔
196
        based_on_doc = based_on_doc.splitlines()
7✔
197
        based_on_doc = based_on_doc[0] if based_on_doc else ''
7✔
198

199
        doc = """Interface for the ABC `{}.{}`.\n\n{}{}{}""".format(
7✔
200
            based_on.__module__, based_on.__name__,
201
            attrs.get('__doc__', based_on_doc),
202
            self.__optional_methods_to_docs(attrs),
203
            implementations_doc
204
        )
205
        return doc
7✔
206

207
    @staticmethod
7✔
208
    def __is_private_name(name):
7✔
209
        if name.startswith('__') and name.endswith('__'):
7✔
210
            return False
7✔
211
        return name.startswith('_')
7✔
212

213
    @staticmethod
7✔
214
    def __is_reverse_protocol_name(name):
7✔
215
        # The reverse names, like __rand__,
216
        # aren't really part of the protocol. The interpreter has
217
        # very complex behaviour around invoking those. PyPy
218
        # doesn't always even expose them as attributes.
219
        return name.startswith('__r') and name.endswith('__')
7✔
220

221
    def __method_from_function(self, function, name):
7✔
222
        method = fromFunction(function, self, name=name)
7✔
223
        # Eliminate the leading *self*, which is implied in
224
        # an interface, but explicit in an ABC.
225
        method.positional = method.positional[1:]
7✔
226
        return method
7✔
227

228
    def __register_classes(self, conformers=None, ignored_classes=None):
7✔
229
        # Make the concrete classes already present in our ABC's registry
230
        # declare that they implement this interface.
231
        conformers = (
7✔
232
            conformers if conformers is not None
233
            else self.getRegisteredConformers()
234
        )
235
        ignored = (
7✔
236
            ignored_classes if ignored_classes is not None
237
            else self.__ignored_classes
238
        )
239
        for cls in conformers:
7✔
240
            if cls in ignored:
7!
241
                continue
×
242
            classImplements(cls, self)
7✔
243

244
    def getABC(self):
7✔
245
        """
246
        Return the ABC this interface represents.
247
        """
248
        return self.__abc
7✔
249

250
    def getRegisteredConformers(self):
7✔
251
        """
252
        Return an iterable of the classes that are known to conform to
253
        the ABC this interface parallels.
254
        """
255
        based_on = self.__abc
7✔
256

257
        # The registry only contains things that aren't already
258
        # known to be subclasses of the ABC. But the ABC is in charge
259
        # of checking that, so its quite possible that registrations
260
        # are in fact ignored, winding up just in the _abc_cache.
261
        try:
7✔
262
            registered = (
7✔
263
                list(based_on._abc_registry) + list(based_on._abc_cache)
264
            )
265
        except AttributeError:
6✔
266
            # Rewritten in C in CPython 3.7.
267
            # These expose the underlying weakref.
268
            from abc import _get_dump
6✔
269
            data = _get_dump(based_on)
6✔
270
            registry = data[0]
6✔
271
            cache = data[1]
6✔
272
            registered = [x() for x in itertools.chain(registry, cache)]
6✔
273
            registered = [x for x in registered if x is not None]
6✔
274

275
        return set(itertools.chain(registered, self.__extra_classes))
7✔
276

277

278
def _create_ABCInterface():
7✔
279
    # It's a two-step process to create the root ABCInterface, because without
280
    # specifying a corresponding ABC, using the normal constructor gets us a
281
    # plain InterfaceClass object, and there is no ABC to associate with the
282
    # root.
283
    abc_name_bases_attrs = ('ABCInterface', (Interface,), {})
7✔
284
    instance = ABCInterfaceClass.__new__(
7✔
285
        ABCInterfaceClass, *abc_name_bases_attrs,
286
    )
287
    InterfaceClass.__init__(instance, *abc_name_bases_attrs)
7✔
288
    return instance
7✔
289

290

291
ABCInterface = _create_ABCInterface()
7✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc