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
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()
23 def strictly_implements(*interfaces):
24 frame = sys._getframe(1)
25 f_locals = frame.f_locals
27 # Try to make sure we were called from a class def. Assumes Python > 2.2.
28 if f_locals is frame.f_globals or '__module__' not in f_locals:
29 raise TypeError("implements can be used only from a class definition.")
31 if '__implements_advice_data__' in f_locals:
32 raise TypeError("implements can be used only once in a class definition.")
34 def _implements_advice(cls):
35 interfaces, classImplements = cls.__dict__['__implements_advice_data__']
36 del cls.__implements_advice_data__
37 classImplements(cls, *interfaces)
39 if interesting_modules.match(cls.__module__):
40 if not excluded_classnames.match(cls.__name__):
41 for interface in interfaces:
43 verifyClass(interface, cls)
45 print >>sys.stderr, ("%s.%s does not correctly implement %s.%s:\n%s"
46 % (cls.__module__, cls.__name__,
47 interface.__module__, interface.__name__, e))
49 other_modules_with_violations.add(cls.__module__)
52 f_locals['__implements_advice_data__'] = interfaces, zi.classImplements
53 addClassAdvisor(_implements_advice, depth=2)
58 zi.implements = strictly_implements
60 # attempt to avoid side-effects from importing command scripts
61 sys.argv = ['', '--help']
63 # import modules under src/
65 for (dirpath, dirnames, filenames) in os.walk(srcdir):
67 (basename, ext) = os.path.splitext(fn)
68 if ext == '.py' and not excluded_file_basenames.match(basename):
69 relpath = os.path.join(dirpath[len(srcdir)+1:], basename)
70 module = relpath.replace(os.sep, '/').replace('/', '.')
78 others = list(other_modules_with_violations)
80 print >>sys.stderr, "There were also interface violations in:\n", ", ".join(others), "\n"
84 # http://svn.zope.org/*checkout*/Zope3/trunk/src/zope/interface/verify.py?content-type=text%2Fplain&rev=27687
85 # but modified to report all interface violations rather than just the first.
87 ##############################################################################
89 # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
90 # All Rights Reserved.
92 # This software is subject to the provisions of the Zope Public License,
93 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
94 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
95 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
96 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
97 # FOR A PARTICULAR PURPOSE.
99 ##############################################################################
100 """Verify interface implementations
104 from zope.interface.exceptions import BrokenImplementation, DoesNotImplement
105 from zope.interface.exceptions import BrokenMethodImplementation
106 from types import FunctionType, MethodType
107 from zope.interface.interface import fromMethod, fromFunction, Method
109 # This will be monkey-patched when running under Zope 2, so leave this
111 MethodTypes = (MethodType, )
114 def _verify(iface, candidate, tentative=0, vtype=None):
115 """Verify that 'candidate' might correctly implements 'iface'.
119 o Making sure the candidate defines all the necessary methods
121 o Making sure the methods have the correct signature
123 o Making sure the candidate asserts that it implements the interface
125 Note that this isn't the same as verifying that the class does
126 implement the interface.
128 If optional tentative is true, suppress the "is implemented by" test.
132 tester = iface.implementedBy
134 tester = iface.providedBy
138 return " " + str(e).strip() + "\n"
140 if not tentative and not tester(candidate):
141 violations.append(format(DoesNotImplement(iface)))
143 # Here the `desc` is either an `Attribute` or `Method` instance
144 for name, desc in iface.namesAndDescriptions(1):
145 if not hasattr(candidate, name):
146 if (not isinstance(desc, Method)) and vtype == 'c':
147 # We can't verify non-methods on classes, since the
148 # class may provide attrs in it's __init__.
151 if isinstance(desc, Method):
152 violations.append(" The %r method was not provided.\n" % (name,))
154 violations.append(" The %r attribute was not provided.\n" % (name,))
157 attr = getattr(candidate, name)
158 if not isinstance(desc, Method):
159 # If it's not a method, there's nothing else we can test
162 if isinstance(attr, FunctionType):
163 # should never get here, since classes should not provide functions
164 meth = fromFunction(attr, iface, name=name)
165 elif (isinstance(attr, MethodTypes)
166 and type(attr.im_func) is FunctionType):
167 meth = fromMethod(attr, iface, name)
169 if not callable(attr):
170 violations.append(format(BrokenMethodImplementation(name, "Not a method")))
171 # sigh, it's callable, but we don't know how to intrspect it, so
172 # we have to give it a pass.
175 # Make sure that the required and implemented method signatures are
177 desc = desc.getSignatureInfo()
178 meth = meth.getSignatureInfo()
180 mess = _incompat(desc, meth)
182 violations.append(format(BrokenMethodImplementation(name, mess)))
185 raise Exception("".join(violations))
188 def verifyClass(iface, candidate, tentative=0):
189 return _verify(iface, candidate, tentative, vtype='c')
191 def verifyObject(iface, candidate, tentative=0):
192 return _verify(iface, candidate, tentative, vtype='o')
194 def _incompat(required, implemented):
195 #if (required['positional'] !=
196 # implemented['positional'][:len(required['positional'])]
197 # and implemented['kwargs'] is None):
198 # return 'imlementation has different argument names'
199 if len(implemented['required']) > len(required['required']):
200 return 'implementation requires too many arguments'
201 if ((len(implemented['positional']) < len(required['positional']))
202 and not implemented['varargs']):
203 return "implementation doesn't allow enough arguments"
204 if required['kwargs'] and not implemented['kwargs']:
205 return "implementation doesn't support keyword arguments"
206 if required['varargs'] and not implemented['varargs']:
207 return "implementation doesn't support variable arguments"