aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/zope.interface/py2/zope/interface/common/__init__.py
blob: 137e93867eee3cb5e3d16613c048bb65278a780a (plain) (blame)
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
##############################################################################
# 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 classImplements
from zope.interface import Interface
from zope.interface.interface import fromFunction
from zope.interface.interface import InterfaceClass
from zope.interface.interface import _decorator_non_return

__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(object):
    # 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(
            "%s\n%s" % (k, 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 "`%s.%s`" % (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 `%s.%s`.\n\n%s%s%s""" % (
            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()