aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/zope.interface/py3/zope/interface/exceptions.py
blob: b86fb1e04c7c7ab2f2e900c445ed6171c8329c6f (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
273
274
275
276
277
278
##############################################################################
#
# Copyright (c) 2002 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.
#
##############################################################################
"""Interface-specific exceptions
"""

__all__ = [
    # Invalid tree
    'Invalid',
    'DoesNotImplement',
    'BrokenImplementation',
    'BrokenMethodImplementation',
    'MultipleInvalid',
    # Other
    'BadImplements',
    'InvalidInterface',
]


class Invalid(Exception):
    """A specification is violated
    """


class _TargetInvalid(Invalid):
    # Internal use. Subclass this when you're describing
    # a particular target object that's invalid according
    # to a specific interface.
    #
    # For backwards compatibility, the *target* and *interface* are
    # optional, and the signatures are inconsistent in their ordering.
    #
    # We deal with the inconsistency in ordering by defining the index
    # of the two values in ``self.args``. *target* uses a marker object to
    # distinguish "not given" from "given, but None", because the latter
    # can be a value that gets passed to validation. For this reason, it must
    # always be the last argument (we detect absence by the ``IndexError``).

    _IX_INTERFACE = 0
    _IX_TARGET = 1
    # The exception to catch when indexing self.args indicating that
    # an argument was not given. If all arguments are expected,
    # a subclass should set this to ().
    _NOT_GIVEN_CATCH = IndexError
    _NOT_GIVEN = '<Not Given>'

    def _get_arg_or_default(self, ix, default=None):
        try:
            return self.args[ix]  # pylint:disable=unsubscriptable-object
        except self._NOT_GIVEN_CATCH:
            return default

    @property
    def interface(self):
        return self._get_arg_or_default(self._IX_INTERFACE)

    @property
    def target(self):
        return self._get_arg_or_default(self._IX_TARGET, self._NOT_GIVEN)

    ###
    # str
    #
    # The ``__str__`` of self is implemented by concatenating (%s), in order,
    # these properties (none of which should have leading or trailing
    # whitespace):
    #
    # - self._str_subject
    #   Begin the message, including a description of the target.
    # - self._str_description
    #   Provide a general description of the type of error, including
    #   the interface name if possible and relevant.
    # - self._str_conjunction
    #   Join the description to the details. Defaults to ": ".
    # - self._str_details
    #   Provide details about how this particular instance of the error.
    # - self._str_trailer
    #   End the message. Usually just a period.
    ###

    @property
    def _str_subject(self):
        target = self.target
        if target is self._NOT_GIVEN:
            return "An object"
        return f"The object {target!r}"

    @property
    def _str_description(self):
        return "has failed to implement interface %s" % (
            self.interface or '<Unknown>'
        )

    _str_conjunction = ": "
    _str_details = "<unknown>"
    _str_trailer = '.'

    def __str__(self):
        return "{} {}{}{}{}".format(
            self._str_subject,
            self._str_description,
            self._str_conjunction,
            self._str_details,
            self._str_trailer
        )


class DoesNotImplement(_TargetInvalid):
    """
    DoesNotImplement(interface[, target])

    The *target* (optional) does not implement the *interface*.

    .. versionchanged:: 5.0.0
       Add the *target* argument and attribute, and change the resulting
       string value of this object accordingly.
    """

    _str_details = "Does not declaratively implement the interface"


class BrokenImplementation(_TargetInvalid):
    """
    BrokenImplementation(interface, name[, target])

    The *target* (optional) is missing the attribute *name*.

    .. versionchanged:: 5.0.0
       Add the *target* argument and attribute, and change the resulting
       string value of this object accordingly.

       The *name* can either be a simple string or a ``Attribute`` object.
    """

    _IX_NAME = _TargetInvalid._IX_INTERFACE + 1
    _IX_TARGET = _IX_NAME + 1

    @property
    def name(self):
        return self.args[1]  # pylint:disable=unsubscriptable-object

    @property
    def _str_details(self):
        return "The %s attribute was not provided" % (
            repr(self.name) if isinstance(self.name, str) else self.name
        )


class BrokenMethodImplementation(_TargetInvalid):
    """
    BrokenMethodImplementation(
        method, message[, implementation, interface, target]
    )

    The *target* (optional) has a *method* in *implementation* that violates
    its contract in a way described by *mess*.

    .. versionchanged:: 5.0.0
       Add the *interface* and *target* argument and attribute,
       and change the resulting string value of this object accordingly.

       The *method* can either be a simple string or a ``Method`` object.

    .. versionchanged:: 5.0.0
       If *implementation* is given, then the *message* will have the
       string "implementation" replaced with an short but informative
       representation of *implementation*.

    """

    _IX_IMPL = 2
    _IX_INTERFACE = _IX_IMPL + 1
    _IX_TARGET = _IX_INTERFACE + 1

    @property
    def method(self):
        return self.args[0]  # pylint:disable=unsubscriptable-object

    @property
    def mess(self):
        return self.args[1]  # pylint:disable=unsubscriptable-object

    @staticmethod
    def __implementation_str(impl):
        # It could be a callable or some arbitrary object, we don't
        # know yet.
        import inspect  # Inspect is a heavy-weight dependency, lots of imports
        try:
            sig = inspect.signature
            formatsig = str
        except AttributeError:
            sig = inspect.getargspec
            formatsig = inspect.formatargspec

        try:
            sig = sig(impl)
        except (ValueError, TypeError):
            # Unable to introspect. Darn.
            # This could be a non-callable, or a particular builtin,
            # or a bound method that doesn't even accept 'self', e.g.,
            # ``Class.method = lambda: None; Class().method``
            return repr(impl)

        try:
            name = impl.__qualname__
        except AttributeError:
            name = impl.__name__

        return name + formatsig(sig)

    @property
    def _str_details(self):
        impl = self._get_arg_or_default(self._IX_IMPL, self._NOT_GIVEN)
        message = self.mess
        if impl is not self._NOT_GIVEN and 'implementation' in message:
            message = message.replace("implementation", '%r')
            message = message % (self.__implementation_str(impl),)

        return 'The contract of {} is violated because {}'.format(
            repr(self.method) if isinstance(self.method, str) else self.method,
            message,
        )


class MultipleInvalid(_TargetInvalid):
    """
    The *target* has failed to implement the *interface* in
    multiple ways.

    The failures are described by *exceptions*, a collection of
    other `Invalid` instances.

    .. versionadded:: 5.0
    """

    _NOT_GIVEN_CATCH = ()

    def __init__(self, interface, target, exceptions):
        super().__init__(interface, target, tuple(exceptions))

    @property
    def exceptions(self):
        return self.args[2]  # pylint:disable=unsubscriptable-object

    @property
    def _str_details(self):
        # It would be nice to use tabs here, but that
        # is hard to represent in doctests.
        return '\n    ' + '\n    '.join(
            x._str_details.strip() if isinstance(x, _TargetInvalid) else str(x)
            for x in self.exceptions
        )

    _str_conjunction = ':'  # no trailing space, messes up doctests
    _str_trailer = ''


class InvalidInterface(Exception):
    """The interface has invalid contents
    """


class BadImplements(TypeError):
    """An implementation assertion is invalid

    because it doesn't contain an interface or a sequence of valid
    implementation assertions.
    """