verify.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. ##############################################################################
  2. #
  3. # Copyright (c) 2001, 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. """Verify interface implementations
  15. """
  16. from __future__ import print_function
  17. import inspect
  18. import sys
  19. from types import FunctionType
  20. from types import MethodType
  21. from zope.interface._compat import PYPY2
  22. from zope.interface.exceptions import BrokenImplementation
  23. from zope.interface.exceptions import BrokenMethodImplementation
  24. from zope.interface.exceptions import DoesNotImplement
  25. from zope.interface.exceptions import Invalid
  26. from zope.interface.exceptions import MultipleInvalid
  27. from zope.interface.interface import fromMethod, fromFunction, Method
  28. __all__ = [
  29. 'verifyObject',
  30. 'verifyClass',
  31. ]
  32. # This will be monkey-patched when running under Zope 2, so leave this
  33. # here:
  34. MethodTypes = (MethodType, )
  35. def _verify(iface, candidate, tentative=False, vtype=None):
  36. """
  37. Verify that *candidate* might correctly provide *iface*.
  38. This involves:
  39. - Making sure the candidate claims that it provides the
  40. interface using ``iface.providedBy`` (unless *tentative* is `True`,
  41. in which case this step is skipped). This means that the candidate's class
  42. declares that it `implements <zope.interface.implementer>` the interface,
  43. or the candidate itself declares that it `provides <zope.interface.provider>`
  44. the interface
  45. - Making sure the candidate defines all the necessary methods
  46. - Making sure the methods have the correct signature (to the
  47. extent possible)
  48. - Making sure the candidate defines all the necessary attributes
  49. :return bool: Returns a true value if everything that could be
  50. checked passed.
  51. :raises zope.interface.Invalid: If any of the previous
  52. conditions does not hold.
  53. .. versionchanged:: 5.0
  54. If multiple methods or attributes are invalid, all such errors
  55. are collected and reported. Previously, only the first error was reported.
  56. As a special case, if only one such error is present, it is raised
  57. alone, like before.
  58. """
  59. if vtype == 'c':
  60. tester = iface.implementedBy
  61. else:
  62. tester = iface.providedBy
  63. excs = []
  64. if not tentative and not tester(candidate):
  65. excs.append(DoesNotImplement(iface, candidate))
  66. for name, desc in iface.namesAndDescriptions(all=True):
  67. try:
  68. _verify_element(iface, name, desc, candidate, vtype)
  69. except Invalid as e:
  70. excs.append(e)
  71. if excs:
  72. if len(excs) == 1:
  73. raise excs[0]
  74. raise MultipleInvalid(iface, candidate, excs)
  75. return True
  76. def _verify_element(iface, name, desc, candidate, vtype):
  77. # Here the `desc` is either an `Attribute` or `Method` instance
  78. try:
  79. attr = getattr(candidate, name)
  80. except AttributeError:
  81. if (not isinstance(desc, Method)) and vtype == 'c':
  82. # We can't verify non-methods on classes, since the
  83. # class may provide attrs in it's __init__.
  84. return
  85. # TODO: On Python 3, this should use ``raise...from``
  86. raise BrokenImplementation(iface, desc, candidate)
  87. if not isinstance(desc, Method):
  88. # If it's not a method, there's nothing else we can test
  89. return
  90. if inspect.ismethoddescriptor(attr) or inspect.isbuiltin(attr):
  91. # The first case is what you get for things like ``dict.pop``
  92. # on CPython (e.g., ``verifyClass(IFullMapping, dict))``). The
  93. # second case is what you get for things like ``dict().pop`` on
  94. # CPython (e.g., ``verifyObject(IFullMapping, dict()))``.
  95. # In neither case can we get a signature, so there's nothing
  96. # to verify. Even the inspect module gives up and raises
  97. # ValueError: no signature found. The ``__text_signature__`` attribute
  98. # isn't typically populated either.
  99. #
  100. # Note that on PyPy 2 or 3 (up through 7.3 at least), these are
  101. # not true for things like ``dict.pop`` (but might be true for C extensions?)
  102. return
  103. if isinstance(attr, FunctionType):
  104. if sys.version_info[0] >= 3 and isinstance(candidate, type) and vtype == 'c':
  105. # This is an "unbound method" in Python 3.
  106. # Only unwrap this if we're verifying implementedBy;
  107. # otherwise we can unwrap @staticmethod on classes that directly
  108. # provide an interface.
  109. meth = fromFunction(attr, iface, name=name,
  110. imlevel=1)
  111. else:
  112. # Nope, just a normal function
  113. meth = fromFunction(attr, iface, name=name)
  114. elif (isinstance(attr, MethodTypes)
  115. and type(attr.__func__) is FunctionType):
  116. meth = fromMethod(attr, iface, name)
  117. elif isinstance(attr, property) and vtype == 'c':
  118. # Without an instance we cannot be sure it's not a
  119. # callable.
  120. # TODO: This should probably check inspect.isdatadescriptor(),
  121. # a more general form than ``property``
  122. return
  123. else:
  124. if not callable(attr):
  125. raise BrokenMethodImplementation(desc, "implementation is not a method",
  126. attr, iface, candidate)
  127. # sigh, it's callable, but we don't know how to introspect it, so
  128. # we have to give it a pass.
  129. return
  130. # Make sure that the required and implemented method signatures are
  131. # the same.
  132. mess = _incompat(desc.getSignatureInfo(), meth.getSignatureInfo())
  133. if mess:
  134. if PYPY2 and _pypy2_false_positive(mess, candidate, vtype):
  135. return
  136. raise BrokenMethodImplementation(desc, mess, attr, iface, candidate)
  137. def verifyClass(iface, candidate, tentative=False):
  138. """
  139. Verify that the *candidate* might correctly provide *iface*.
  140. """
  141. return _verify(iface, candidate, tentative, vtype='c')
  142. def verifyObject(iface, candidate, tentative=False):
  143. return _verify(iface, candidate, tentative, vtype='o')
  144. verifyObject.__doc__ = _verify.__doc__
  145. _MSG_TOO_MANY = 'implementation requires too many arguments'
  146. _KNOWN_PYPY2_FALSE_POSITIVES = frozenset((
  147. _MSG_TOO_MANY,
  148. ))
  149. def _pypy2_false_positive(msg, candidate, vtype):
  150. # On PyPy2, builtin methods and functions like
  151. # ``dict.pop`` that take pseudo-optional arguments
  152. # (those with no default, something you can't express in Python 2
  153. # syntax; CPython uses special internal APIs to implement these methods)
  154. # return false failures because PyPy2 doesn't expose any way
  155. # to detect this pseudo-optional status. PyPy3 doesn't have this problem
  156. # because of __defaults_count__, and CPython never gets here because it
  157. # returns true for ``ismethoddescriptor`` or ``isbuiltin``.
  158. #
  159. # We can't catch all such cases, but we can handle the common ones.
  160. #
  161. if msg not in _KNOWN_PYPY2_FALSE_POSITIVES:
  162. return False
  163. known_builtin_types = vars(__builtins__).values()
  164. candidate_type = candidate if vtype == 'c' else type(candidate)
  165. if candidate_type in known_builtin_types:
  166. return True
  167. return False
  168. def _incompat(required, implemented):
  169. #if (required['positional'] !=
  170. # implemented['positional'][:len(required['positional'])]
  171. # and implemented['kwargs'] is None):
  172. # return 'imlementation has different argument names'
  173. if len(implemented['required']) > len(required['required']):
  174. return _MSG_TOO_MANY
  175. if ((len(implemented['positional']) < len(required['positional']))
  176. and not implemented['varargs']):
  177. return "implementation doesn't allow enough arguments"
  178. if required['kwargs'] and not implemented['kwargs']:
  179. return "implementation doesn't support keyword arguments"
  180. if required['varargs'] and not implemented['varargs']:
  181. return "implementation doesn't support variable arguments"