When an invariant is defined in an interface, it's found by

`validateInvariants` in all interfaces inheriting from that interface.
Make sure to call each invariant only once when validating invariants.
This commit is contained in:
Jan-Jaap Driessen 2020-09-25 11:29:29 +02:00
parent 255db9d3c0
commit 1025519cb6
4 changed files with 25 additions and 2 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
*.pyc
*.pyo
*.so
*.swp
__pycache__
.coverage
.coverage.*

View File

@ -12,6 +12,9 @@
that argument. See `issue 208
<https://github.com/zopefoundation/zope.interface/issues/208>`_.
- When an invariant is defined in an interface, it's found by
`validateInvariants` in all interfaces inheriting from that interface.
Make sure to call each invariant only once when validating invariants.
5.1.0 (2020-04-08)
==================

View File

@ -875,9 +875,14 @@ class InterfaceClass(_InterfaceClassBase):
def queryDescriptionFor(self, name, default=None):
return self.get(name, default)
def validateInvariants(self, obj, errors=None):
def validateInvariants(self, obj, errors=None, seen=None):
"""validate object to defined invariants."""
if seen is None:
seen = set()
for call in self.queryTaggedValue('invariants', []):
if call in seen:
continue
seen.add(call)
try:
call(obj)
except Invalid as e:
@ -886,7 +891,7 @@ class InterfaceClass(_InterfaceClassBase):
errors.append(e)
for base in self.__bases__:
try:
base.validateInvariants(obj, errors)
base.validateInvariants(obj, errors, seen=seen)
except Invalid:
if errors is None:
raise

View File

@ -1014,6 +1014,20 @@ class InterfaceClassTests(unittest.TestCase):
self.assertEqual(len(_errors), 1)
self.assertTrue(isinstance(_errors[0], Invalid))
def test_validateInvariants_inherited_not_called_multiple_times(self):
_passable_called_with = []
def _passable(*args, **kw):
_passable_called_with.append((args, kw))
return True
obj = object()
base = self._makeOne('IBase')
base.setTaggedValue('invariants', [_passable])
derived = self._makeOne('IDerived', (base,))
derived.validateInvariants(obj)
self.assertEqual(1, len(_passable_called_with))
def test___reduce__(self):
iface = self._makeOne('PickleMe')
self.assertEqual(iface.__reduce__(), 'PickleMe')