############################################################################## # # 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 = '' 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 '' ) _str_conjunction = ": " _str_details = "" _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. """