276 lines
8.4 KiB
Python
276 lines
8.4 KiB
Python
##############################################################################
|
|
#
|
|
# 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 "The object {!r}".format(target)
|
|
|
|
@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
|
|
f = inspect.formatargspec
|
|
formatsig = lambda sig: f(*sig) # pylint:disable=deprecated-method
|
|
|
|
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 = ':' # We don't want a 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.
|
|
"""
|