zope.interface
zope.interface copied to clipboard
verifyObject doesn't correctly work on classes with classProvides
In https://bugs.launchpad.net/zope.interface/+bug/675424, Christian Zagrodnik reported:
When for instance declaring a factory
classProvides
is handy at times:
>>> import zope.interface
>>> import zope.interface.verify
>>>
>>> class IFoo(zope.interface.Interface):
... def __call__(arg):
... pass
...
>>> class FooOkay(object):
... zope.interface.classProvides(IFoo)
... def __init__(self, arg):
... pass
...
>>> zope.interface.verify.verifyObject(IFoo, FooOkay)
True
That's was working.
The next case should raise an error, but doesn't.
IFoo
requires an arg to be passed which the following class doesn't implement:
>>> class FooVerifiedByNotLikeSpec(object):
... zope.interface.classProvides(IFoo)
...
>>> zope.interface.verify.verifyObject(IFoo, FooVerifiedByNotLikeSpec)
True
The next case should actually pass but doesn't.
>>> class FooBroken(object):
... zope.interface.classProvides(IFoo)
... def __init__(self, arg):
... pass
... def __call__(self):
... pass
...
>>> zope.interface.verify.verifyObject(IFoo, FooBroken)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/Users/zagy/development/eggs/zope.interface-3.6.1-py2.7-macosx-10.6-x86_64.egg/zope/interface/verify.py", line 102, in verifyObject
return _verify(iface, candidate, tentative, vtype='o')
File "/Users/zagy/development/eggs/zope.interface-3.6.1-py2.7-macosx-10.6-x86_64.egg/zope/interface/verify.py", line 94, in _verify
raise BrokenMethodImplementation(name, mess)
BrokenMethodImplementation: The implementation of __call__ violates its contract
because implementation doesn't allow enough arguments.
Apparently
verifyObject
doesn't take any special care for classes which proivde an interface.
This is not a trivial problem. What's happening is that verification doesn't take special method lookup into account. How should interfaces handle special methods, like __add__
and __contains__
? These methods can never be directly provided, really, (except for modules, which as of Python 3.7 can directly provide __dir__
and __getattr__
functions).
And for classes without a more specific metaclass than type
, it could be argued that they could only ever "directly provide" __call__(*args, **kwargs)
, never something more specific. That the type.__call__()
method used for ClassObj()
invokes __new__
and, optionally, __new__
, is an implementation detail that verifyObject
can't be expected to handle.