Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sure to call each invariant only once when validating invariants. #215

Merged
merged 6 commits into from Oct 1, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -2,6 +2,7 @@
*.pyc
*.pyo
*.so
*.swp
__pycache__
.coverage
.coverage.*
Expand Down
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -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.
janjaapdriessen marked this conversation as resolved.
Show resolved Hide resolved

5.1.0 (2020-04-08)
==================
Expand Down
9 changes: 7 additions & 2 deletions src/zope/interface/interface.py
Expand Up @@ -875,9 +875,14 @@ def direct(self, name):
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):
janjaapdriessen marked this conversation as resolved.
Show resolved Hide resolved
"""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:
Expand All @@ -886,7 +891,7 @@ def validateInvariants(self, obj, errors=None):
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
Expand Down
14 changes: 14 additions & 0 deletions src/zope/interface/tests/test_interface.py
Expand Up @@ -1014,6 +1014,20 @@ def _fail(*args, **kw):
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')
Expand Down