123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- ##############################################################################
- #
- # 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.
- """
|