• 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

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

17
__all__ = [
7✔
18
    # Invalid tree
19
    'Invalid',
20
    'DoesNotImplement',
21
    'BrokenImplementation',
22
    'BrokenMethodImplementation',
23
    'MultipleInvalid',
24
    # Other
25
    'BadImplements',
26
    'InvalidInterface',
27
]
28

29

30
class Invalid(Exception):
7✔
31
    """A specification is violated
32
    """
33

34

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

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

57
    def _get_arg_or_default(self, ix, default=None):
7✔
58
        try:
7✔
59
            return self.args[ix]  # pylint:disable=unsubscriptable-object
7✔
60
        except self._NOT_GIVEN_CATCH:
7✔
61
            return default
7✔
62

63
    @property
7✔
64
    def interface(self):
7✔
65
        return self._get_arg_or_default(self._IX_INTERFACE)
7✔
66

67
    @property
7✔
68
    def target(self):
7✔
69
        return self._get_arg_or_default(self._IX_TARGET, self._NOT_GIVEN)
7✔
70

71
    ###
72
    # str
73
    #
74
    # The ``__str__`` of self is implemented by concatenating (%s), in order,
75
    # these properties (none of which should have leading or trailing
76
    # whitespace):
77
    #
78
    # - self._str_subject
79
    #   Begin the message, including a description of the target.
80
    # - self._str_description
81
    #   Provide a general description of the type of error, including
82
    #   the interface name if possible and relevant.
83
    # - self._str_conjunction
84
    #   Join the description to the details. Defaults to ": ".
85
    # - self._str_details
86
    #   Provide details about how this particular instance of the error.
87
    # - self._str_trailer
88
    #   End the message. Usually just a period.
89
    ###
90

91
    @property
7✔
92
    def _str_subject(self):
7✔
93
        target = self.target
7✔
94
        if target is self._NOT_GIVEN:
7✔
95
            return "An object"
7✔
96
        return f"The object {target!r}"
7✔
97

98
    @property
7✔
99
    def _str_description(self):
7✔
100
        return "has failed to implement interface %s" % (
7✔
101
            self.interface or '<Unknown>'
102
        )
103

104
    _str_conjunction = ": "
7✔
105
    _str_details = "<unknown>"
7✔
106
    _str_trailer = '.'
7✔
107

108
    def __str__(self):
7✔
109
        return "{} {}{}{}{}".format(
7✔
110
            self._str_subject,
111
            self._str_description,
112
            self._str_conjunction,
113
            self._str_details,
114
            self._str_trailer
115
        )
116

117

118
class DoesNotImplement(_TargetInvalid):
7✔
119
    """
120
    DoesNotImplement(interface[, target])
121

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

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

129
    _str_details = "Does not declaratively implement the interface"
7✔
130

131

132
class BrokenImplementation(_TargetInvalid):
7✔
133
    """
134
    BrokenImplementation(interface, name[, target])
135

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

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

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

145
    _IX_NAME = _TargetInvalid._IX_INTERFACE + 1
7✔
146
    _IX_TARGET = _IX_NAME + 1
7✔
147

148
    @property
7✔
149
    def name(self):
7✔
150
        return self.args[1]  # pylint:disable=unsubscriptable-object
7✔
151

152
    @property
7✔
153
    def _str_details(self):
7✔
154
        return "The %s attribute was not provided" % (
7✔
155
            repr(self.name) if isinstance(self.name, str) else self.name
156
        )
157

158

159
class BrokenMethodImplementation(_TargetInvalid):
7✔
160
    """
161
    BrokenMethodImplementation(
162
        method, message[, implementation, interface, target]
163
    )
164

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

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

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

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

179
    """
180

181
    _IX_IMPL = 2
7✔
182
    _IX_INTERFACE = _IX_IMPL + 1
7✔
183
    _IX_TARGET = _IX_INTERFACE + 1
7✔
184

185
    @property
7✔
186
    def method(self):
7✔
187
        return self.args[0]  # pylint:disable=unsubscriptable-object
7✔
188

189
    @property
7✔
190
    def mess(self):
7✔
191
        return self.args[1]  # pylint:disable=unsubscriptable-object
7✔
192

193
    @staticmethod
7✔
194
    def __implementation_str(impl):
7✔
195
        # It could be a callable or some arbitrary object, we don't
196
        # know yet.
197
        import inspect  # Inspect is a heavy-weight dependency, lots of imports
7✔
198
        try:
7✔
199
            sig = inspect.signature
7✔
200
            formatsig = str
7✔
201
        except AttributeError:
×
202
            sig = inspect.getargspec
×
203
            formatsig = inspect.formatargspec
×
204

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

214
        try:
7✔
215
            name = impl.__qualname__
7✔
216
        except AttributeError:
×
217
            name = impl.__name__
×
218

219
        return name + formatsig(sig)
7✔
220

221
    @property
7✔
222
    def _str_details(self):
7✔
223
        impl = self._get_arg_or_default(self._IX_IMPL, self._NOT_GIVEN)
7✔
224
        message = self.mess
7✔
225
        if impl is not self._NOT_GIVEN and 'implementation' in message:
7✔
226
            message = message.replace("implementation", '%r')
7✔
227
            message = message % (self.__implementation_str(impl),)
7✔
228

229
        return 'The contract of {} is violated because {}'.format(
7✔
230
            repr(self.method) if isinstance(self.method, str) else self.method,
231
            message,
232
        )
233

234

235
class MultipleInvalid(_TargetInvalid):
7✔
236
    """
237
    The *target* has failed to implement the *interface* in
238
    multiple ways.
239

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

243
    .. versionadded:: 5.0
244
    """
245

246
    _NOT_GIVEN_CATCH = ()
7✔
247

248
    def __init__(self, interface, target, exceptions):
7✔
249
        super().__init__(interface, target, tuple(exceptions))
7✔
250

251
    @property
7✔
252
    def exceptions(self):
7✔
253
        return self.args[2]  # pylint:disable=unsubscriptable-object
7✔
254

255
    @property
7✔
256
    def _str_details(self):
7✔
257
        # It would be nice to use tabs here, but that
258
        # is hard to represent in doctests.
259
        return '\n    ' + '\n    '.join(
7✔
260
            x._str_details.strip() if isinstance(x, _TargetInvalid) else str(x)
261
            for x in self.exceptions
262
        )
263

264
    _str_conjunction = ':'  # no trailing space, messes up doctests
7✔
265
    _str_trailer = ''
7✔
266

267

268
class InvalidInterface(Exception):
7✔
269
    """The interface has invalid contents
270
    """
271

272

273
class BadImplements(TypeError):
7✔
274
    """An implementation assertion is invalid
275

276
    because it doesn't contain an interface or a sequence of valid
277
    implementation assertions.
278
    """
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