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 in ('.pyc', '.pyo') and not os.path.exists(os.path.join(dirpath, basename+'.py')):
69 print >>sys.stderr, ("Warning: no .py source file for %r.\n"
70 % (os.path.join(dirpath, fn),))
72 if ext == '.py' and not excluded_file_basenames.match(basename):
73 relpath = os.path.join(dirpath[len(srcdir)+1:], basename)
74 module = relpath.replace(os.sep, '/').replace('/', '.')
82 others = list(other_modules_with_violations)
84 print >>sys.stderr, "There were also interface violations in:\n", ", ".join(others), "\n"
88 # http://svn.zope.org/*checkout*/Zope3/trunk/src/zope/interface/verify.py?content-type=text%2Fplain&rev=27687
89 # but modified to report all interface violations rather than just the first.
91 ##############################################################################
93 # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
94 # All Rights Reserved.
96 # This software is subject to the provisions of the Zope Public License,
97 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
98 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
99 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
100 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
101 # FOR A PARTICULAR PURPOSE.
103 ##############################################################################
104 """Verify interface implementations
108 from zope.interface.exceptions import BrokenImplementation, DoesNotImplement
109 from zope.interface.exceptions import BrokenMethodImplementation
110 from types import FunctionType, MethodType
111 from zope.interface.interface import fromMethod, fromFunction, Method
113 # This will be monkey-patched when running under Zope 2, so leave this
115 MethodTypes = (MethodType, )
118 def _verify(iface, candidate, tentative=0, vtype=None):
119 """Verify that 'candidate' might correctly implements 'iface'.
123 o Making sure the candidate defines all the necessary methods
125 o Making sure the methods have the correct signature
127 o Making sure the candidate asserts that it implements the interface
129 Note that this isn't the same as verifying that the class does
130 implement the interface.
132 If optional tentative is true, suppress the "is implemented by" test.
136 tester = iface.implementedBy
138 tester = iface.providedBy
142 return " " + str(e).strip() + "\n"
144 if not tentative and not tester(candidate):
145 violations.append(format(DoesNotImplement(iface)))
147 # Here the `desc` is either an `Attribute` or `Method` instance
148 for name, desc in iface.namesAndDescriptions(1):
149 if not hasattr(candidate, name):
150 if (not isinstance(desc, Method)) and vtype == 'c':
151 # We can't verify non-methods on classes, since the
152 # class may provide attrs in it's __init__.
155 if isinstance(desc, Method):
156 violations.append(" The %r method was not provided.\n" % (name,))
158 violations.append(" The %r attribute was not provided.\n" % (name,))
161 attr = getattr(candidate, name)
162 if not isinstance(desc, Method):
163 # If it's not a method, there's nothing else we can test
166 if isinstance(attr, FunctionType):
167 # should never get here, since classes should not provide functions
168 meth = fromFunction(attr, iface, name=name)
169 elif (isinstance(attr, MethodTypes)
170 and type(attr.im_func) is FunctionType):
171 meth = fromMethod(attr, iface, name)
173 if not callable(attr):
174 violations.append(format(BrokenMethodImplementation(name, "Not a method")))
175 # sigh, it's callable, but we don't know how to intrspect it, so
176 # we have to give it a pass.
179 # Make sure that the required and implemented method signatures are
181 desc = desc.getSignatureInfo()
182 meth = meth.getSignatureInfo()
184 mess = _incompat(desc, meth)
186 violations.append(format(BrokenMethodImplementation(name, mess)))
189 raise Exception("".join(violations))
192 def verifyClass(iface, candidate, tentative=0):
193 return _verify(iface, candidate, tentative, vtype='c')
195 def verifyObject(iface, candidate, tentative=0):
196 return _verify(iface, candidate, tentative, vtype='o')
198 def _incompat(required, implemented):
199 #if (required['positional'] !=
200 # implemented['positional'][:len(required['positional'])]
201 # and implemented['kwargs'] is None):
202 # return 'imlementation has different argument names'
203 if len(implemented['required']) > len(required['required']):
204 return 'implementation requires too many arguments'
205 if ((len(implemented['positional']) < len(required['positional']))
206 and not implemented['varargs']):
207 return "implementation doesn't allow enough arguments"
208 if required['kwargs'] and not implemented['kwargs']:
209 return "implementation doesn't support keyword arguments"
210 if required['varargs'] and not implemented['varargs']:
211 return "implementation doesn't support variable arguments"