2 # To check a particular Tahoe source distribution, this should be invoked from
3 # the root directory of that distribution as
5 # bin/tahoe @misc/coding_tools/check-interfaces.py
7 import os, sys, re, platform
9 import zope.interface as zi
10 # We use the forked version of verifyClass below.
11 #from zope.interface.verify import verifyClass
12 from zope.interface.advice import addClassAdvisor
15 interesting_modules = re.compile(r'(allmydata)|(foolscap)\..*')
16 excluded_classnames = re.compile(r'(_)|(Mock)|(Fake)|(Dummy).*')
17 excluded_file_basenames = re.compile(r'(check)|(bench)_.*')
20 _other_modules_with_violations = set()
22 _report_argname_mismatch = False # very noisy and usually not important
26 def strictly_implements(*interfaces):
27 frame = sys._getframe(1)
28 f_locals = frame.f_locals
30 # Try to make sure we were called from a class def. Assumes Python > 2.2.
31 if f_locals is frame.f_globals or '__module__' not in f_locals:
32 raise TypeError("implements can be used only from a class definition.")
34 if '__implements_advice_data__' in f_locals:
35 raise TypeError("implements can be used only once in a class definition.")
37 def _implements_advice(cls):
38 interfaces, classImplements = cls.__dict__['__implements_advice_data__']
39 del cls.__implements_advice_data__
40 classImplements(cls, *interfaces)
42 if interesting_modules.match(cls.__module__):
43 if not excluded_classnames.match(cls.__name__):
44 for interface in interfaces:
46 verifyClass(interface, cls)
48 print >>_err, ("%s.%s does not correctly implement %s.%s:\n%s"
49 % (cls.__module__, cls.__name__,
50 interface.__module__, interface.__name__, e))
52 _other_modules_with_violations.add(cls.__module__)
55 f_locals['__implements_advice_data__'] = interfaces, zi.classImplements
56 addClassAdvisor(_implements_advice, depth=2)
61 zi.implements = strictly_implements
63 if len(sys.argv) >= 2:
64 if sys.argv[1] == '--help' or len(sys.argv) > 2:
65 print >>_err, "Usage: check-miscaptures.py [SOURCEDIR]"
69 # import modules under src/ by default
72 # attempt to avoid side-effects from importing command scripts
73 sys.argv = ['', '--help']
75 syslow = platform.system().lower()
76 is_windows = 'cygwin' in syslow or 'windows' in syslow
78 for (dirpath, dirnames, filenames) in os.walk(srcdir):
80 (basename, ext) = os.path.splitext(fn)
81 if ext in ('.pyc', '.pyo') and not os.path.exists(os.path.join(dirpath, basename+'.py')):
82 print >>_err, ("Warning: no .py source file for %r.\n"
83 % (os.path.join(dirpath, fn),))
85 if ext == '.py' and not excluded_file_basenames.match(basename):
86 relpath = os.path.join(dirpath[len(srcdir)+1:], basename)
87 module = relpath.replace(os.sep, '/').replace('/', '.')
90 except ImportError, e:
91 if not is_windows and (' _win' in str(e) or 'win32' in str(e)):
92 print >>_err, ("Warning: %r imports a Windows-specific module, so we cannot check it (%s).\n"
96 traceback.print_exc(file=_err)
99 others = list(_other_modules_with_violations)
101 print >>_err, "There were also interface violations in:\n", ", ".join(others), "\n"
105 # http://svn.zope.org/*checkout*/Zope3/trunk/src/zope/interface/verify.py?content-type=text%2Fplain&rev=27687
106 # but modified to report all interface violations rather than just the first.
108 ##############################################################################
110 # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
111 # All Rights Reserved.
113 # This software is subject to the provisions of the Zope Public License,
114 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
115 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
116 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
117 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
118 # FOR A PARTICULAR PURPOSE.
120 ##############################################################################
121 """Verify interface implementations
125 from zope.interface.exceptions import DoesNotImplement
126 from zope.interface.exceptions import BrokenMethodImplementation
127 from types import FunctionType, MethodType
128 from zope.interface.interface import fromMethod, fromFunction, Method
130 # This will be monkey-patched when running under Zope 2, so leave this
132 MethodTypes = (MethodType, )
135 def _verify(iface, candidate, tentative=0, vtype=None):
136 """Verify that 'candidate' might correctly implements 'iface'.
140 o Making sure the candidate defines all the necessary methods
142 o Making sure the methods have the correct signature
144 o Making sure the candidate asserts that it implements the interface
146 Note that this isn't the same as verifying that the class does
147 implement the interface.
149 If optional tentative is true, suppress the "is implemented by" test.
153 tester = iface.implementedBy
155 tester = iface.providedBy
159 return " " + str(e).strip() + "\n"
161 if not tentative and not tester(candidate):
162 violations.append(format(DoesNotImplement(iface)))
164 # Here the `desc` is either an `Attribute` or `Method` instance
165 for name, desc in iface.namesAndDescriptions(1):
166 if not hasattr(candidate, name):
167 if (not isinstance(desc, Method)) and vtype == 'c':
168 # We can't verify non-methods on classes, since the
169 # class may provide attrs in it's __init__.
172 if isinstance(desc, Method):
173 violations.append(" The %r method was not provided.\n" % (name,))
175 violations.append(" The %r attribute was not provided.\n" % (name,))
178 attr = getattr(candidate, name)
179 if not isinstance(desc, Method):
180 # If it's not a method, there's nothing else we can test
183 if isinstance(attr, FunctionType):
184 # should never get here, since classes should not provide functions
185 meth = fromFunction(attr, iface, name=name)
186 elif (isinstance(attr, MethodTypes)
187 and type(attr.im_func) is FunctionType):
188 meth = fromMethod(attr, iface, name)
190 if not callable(attr):
191 violations.append(format(BrokenMethodImplementation(name, "Not a method")))
192 # sigh, it's callable, but we don't know how to intrspect it, so
193 # we have to give it a pass.
196 # Make sure that the required and implemented method signatures are
198 desc = desc.getSignatureInfo()
199 meth = meth.getSignatureInfo()
201 mess = _incompat(desc, meth)
203 violations.append(format(BrokenMethodImplementation(name, mess)))
206 raise Exception("".join(violations))
209 def verifyClass(iface, candidate, tentative=0):
210 return _verify(iface, candidate, tentative, vtype='c')
212 def verifyObject(iface, candidate, tentative=0):
213 return _verify(iface, candidate, tentative, vtype='o')
215 def _incompat(required, implemented):
216 if len(implemented['required']) > len(required['required']):
217 return 'implementation requires too many arguments'
218 if ((len(implemented['positional']) < len(required['positional']))
219 and not implemented['varargs']):
220 return "implementation doesn't allow enough arguments"
221 if required['kwargs'] and not implemented['kwargs']:
222 return "implementation doesn't support keyword arguments"
223 if required['varargs'] and not implemented['varargs']:
224 return "implementation doesn't support variable arguments"
225 if (_report_argname_mismatch and required['positional'] !=
226 implemented['positional'][:len(required['positional'])]
227 and implemented['kwargs'] is None):
228 return 'implementation has different argument names'
231 if __name__ == "__main__":
233 # Avoid spurious warnings about ignored exceptions during shutdown by doing a hard exit.