exceptions.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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
  15. """
  16. __all__ = [
  17. # Invalid tree
  18. 'Invalid',
  19. 'DoesNotImplement',
  20. 'BrokenImplementation',
  21. 'BrokenMethodImplementation',
  22. 'MultipleInvalid',
  23. # Other
  24. 'BadImplements',
  25. 'InvalidInterface',
  26. ]
  27. class Invalid(Exception):
  28. """A specification is violated
  29. """
  30. class _TargetInvalid(Invalid):
  31. # Internal use. Subclass this when you're describing
  32. # a particular target object that's invalid according
  33. # to a specific interface.
  34. #
  35. # For backwards compatibility, the *target* and *interface* are
  36. # optional, and the signatures are inconsistent in their ordering.
  37. #
  38. # We deal with the inconsistency in ordering by defining the index
  39. # of the two values in ``self.args``. *target* uses a marker object to
  40. # distinguish "not given" from "given, but None", because the latter
  41. # can be a value that gets passed to validation. For this reason, it must
  42. # always be the last argument (we detect absence by the ``IndexError``).
  43. _IX_INTERFACE = 0
  44. _IX_TARGET = 1
  45. # The exception to catch when indexing self.args indicating that
  46. # an argument was not given. If all arguments are expected,
  47. # a subclass should set this to ().
  48. _NOT_GIVEN_CATCH = IndexError
  49. _NOT_GIVEN = '<Not Given>'
  50. def _get_arg_or_default(self, ix, default=None):
  51. try:
  52. return self.args[ix] # pylint:disable=unsubscriptable-object
  53. except self._NOT_GIVEN_CATCH:
  54. return default
  55. @property
  56. def interface(self):
  57. return self._get_arg_or_default(self._IX_INTERFACE)
  58. @property
  59. def target(self):
  60. return self._get_arg_or_default(self._IX_TARGET, self._NOT_GIVEN)
  61. ###
  62. # str
  63. #
  64. # The ``__str__`` of self is implemented by concatenating (%s), in order,
  65. # these properties (none of which should have leading or trailing
  66. # whitespace):
  67. #
  68. # - self._str_subject
  69. # Begin the message, including a description of the target.
  70. # - self._str_description
  71. # Provide a general description of the type of error, including
  72. # the interface name if possible and relevant.
  73. # - self._str_conjunction
  74. # Join the description to the details. Defaults to ": ".
  75. # - self._str_details
  76. # Provide details about how this particular instance of the error.
  77. # - self._str_trailer
  78. # End the message. Usually just a period.
  79. ###
  80. @property
  81. def _str_subject(self):
  82. target = self.target
  83. if target is self._NOT_GIVEN:
  84. return "An object"
  85. return "The object %r" % (target,)
  86. @property
  87. def _str_description(self):
  88. return "has failed to implement interface %s" % (
  89. self.interface or '<Unknown>'
  90. )
  91. _str_conjunction = ": "
  92. _str_details = "<unknown>"
  93. _str_trailer = '.'
  94. def __str__(self):
  95. return "%s %s%s%s%s" % (
  96. self._str_subject,
  97. self._str_description,
  98. self._str_conjunction,
  99. self._str_details,
  100. self._str_trailer
  101. )
  102. class DoesNotImplement(_TargetInvalid):
  103. """
  104. DoesNotImplement(interface[, target])
  105. The *target* (optional) does not implement the *interface*.
  106. .. versionchanged:: 5.0.0
  107. Add the *target* argument and attribute, and change the resulting
  108. string value of this object accordingly.
  109. """
  110. _str_details = "Does not declaratively implement the interface"
  111. class BrokenImplementation(_TargetInvalid):
  112. """
  113. BrokenImplementation(interface, name[, target])
  114. The *target* (optional) is missing the attribute *name*.
  115. .. versionchanged:: 5.0.0
  116. Add the *target* argument and attribute, and change the resulting
  117. string value of this object accordingly.
  118. The *name* can either be a simple string or a ``Attribute`` object.
  119. """
  120. _IX_NAME = _TargetInvalid._IX_INTERFACE + 1
  121. _IX_TARGET = _IX_NAME + 1
  122. @property
  123. def name(self):
  124. return self.args[1] # pylint:disable=unsubscriptable-object
  125. @property
  126. def _str_details(self):
  127. return "The %s attribute was not provided" % (
  128. repr(self.name) if isinstance(self.name, str) else self.name
  129. )
  130. class BrokenMethodImplementation(_TargetInvalid):
  131. """
  132. BrokenMethodImplementation(method, message[, implementation, interface, target])
  133. The *target* (optional) has a *method* in *implementation* that violates
  134. its contract in a way described by *mess*.
  135. .. versionchanged:: 5.0.0
  136. Add the *interface* and *target* argument and attribute,
  137. and change the resulting string value of this object accordingly.
  138. The *method* can either be a simple string or a ``Method`` object.
  139. .. versionchanged:: 5.0.0
  140. If *implementation* is given, then the *message* will have the
  141. string "implementation" replaced with an short but informative
  142. representation of *implementation*.
  143. """
  144. _IX_IMPL = 2
  145. _IX_INTERFACE = _IX_IMPL + 1
  146. _IX_TARGET = _IX_INTERFACE + 1
  147. @property
  148. def method(self):
  149. return self.args[0] # pylint:disable=unsubscriptable-object
  150. @property
  151. def mess(self):
  152. return self.args[1] # pylint:disable=unsubscriptable-object
  153. @staticmethod
  154. def __implementation_str(impl):
  155. # It could be a callable or some arbitrary object, we don't
  156. # know yet.
  157. import inspect # Inspect is a heavy-weight dependency, lots of imports
  158. try:
  159. sig = inspect.signature
  160. formatsig = str
  161. except AttributeError:
  162. sig = inspect.getargspec
  163. f = inspect.formatargspec
  164. formatsig = lambda sig: f(*sig) # pylint:disable=deprecated-method
  165. try:
  166. sig = sig(impl)
  167. except (ValueError, TypeError):
  168. # Unable to introspect. Darn.
  169. # This could be a non-callable, or a particular builtin,
  170. # or a bound method that doesn't even accept 'self', e.g.,
  171. # ``Class.method = lambda: None; Class().method``
  172. return repr(impl)
  173. try:
  174. name = impl.__qualname__
  175. except AttributeError:
  176. name = impl.__name__
  177. return name + formatsig(sig)
  178. @property
  179. def _str_details(self):
  180. impl = self._get_arg_or_default(self._IX_IMPL, self._NOT_GIVEN)
  181. message = self.mess
  182. if impl is not self._NOT_GIVEN and 'implementation' in message:
  183. message = message.replace("implementation", '%r')
  184. message = message % (self.__implementation_str(impl),)
  185. return 'The contract of %s is violated because %s' % (
  186. repr(self.method) if isinstance(self.method, str) else self.method,
  187. message,
  188. )
  189. class MultipleInvalid(_TargetInvalid):
  190. """
  191. The *target* has failed to implement the *interface* in
  192. multiple ways.
  193. The failures are described by *exceptions*, a collection of
  194. other `Invalid` instances.
  195. .. versionadded:: 5.0
  196. """
  197. _NOT_GIVEN_CATCH = ()
  198. def __init__(self, interface, target, exceptions):
  199. super(MultipleInvalid, self).__init__(interface, target, tuple(exceptions))
  200. @property
  201. def exceptions(self):
  202. return self.args[2] # pylint:disable=unsubscriptable-object
  203. @property
  204. def _str_details(self):
  205. # It would be nice to use tabs here, but that
  206. # is hard to represent in doctests.
  207. return '\n ' + '\n '.join(
  208. x._str_details.strip() if isinstance(x, _TargetInvalid) else str(x)
  209. for x in self.exceptions
  210. )
  211. _str_conjunction = ':' # We don't want a trailing space, messes up doctests
  212. _str_trailer = ''
  213. class InvalidInterface(Exception):
  214. """The interface has invalid contents
  215. """
  216. class BadImplements(TypeError):
  217. """An implementation assertion is invalid
  218. because it doesn't contain an interface or a sequence of valid
  219. implementation assertions.
  220. """