-
Notifications
You must be signed in to change notification settings - Fork 69
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
Conversation
`validateInvariants` in all interfaces inheriting from that interface. Make sure to call each invariant only once when validating invariants.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this!
Co-authored-by: Jason Madden <jason+github@nextthought.com>
…he public interface/signature.
Thanks for considering that! I see your new implementation and I agree it looks nice and clean! While reviewing it, I had another thought though: What if the body was changed from
As this function proceeds to walk up But here's the problem with that: it might actually find them multiple times: >>> from zope.interface import Interface
>>> class I1(Interface):
... pass
...
>>> class I2(I1):
... pass
...
>>> class I11(I1):
… pass
…
>>> class I3(I11, I2):
... pass
...
>>> I3.__iro__
(<InterfaceClass __main__.I3>,
<InterfaceClass __main__.I11>,
<InterfaceClass __main__.I2>,
<InterfaceClass __main__.I1>,
<InterfaceClass zope.interface.Interface>)
>>> def visit(s, pfx=''):
... print(pfx, s)
... for b in s.__bases__:
... visit(b, pfx + ' ')
...
>>> visit(I3)
<InterfaceClass __main__.I3>
<InterfaceClass __main__.I11>
<InterfaceClass __main__.I1>
<InterfaceClass zope.interface.Interface>
<InterfaceClass __main__.I2>
<InterfaceClass __main__.I1>
<InterfaceClass zope.interface.Interface> Actually, thinking more about this, I find this method very confusing as it currently exists. It uses What if the method was made non-recursive using def validateInvariants(self, obj, errors=None):
# pseudo-code
for iface in self.__iro__: # Iterate starting with self
for invariant in iface.queryDirectTaggedValue('invariants', ()):
try:
invariant(obj)
except Invalid:
if errors is not None: errors.append(e)
else: raise
if errors: raise Invalid(errors) That seems a lot simpler and more direct. It eliminates the hashing of the PR. It still has the same issue with people that have tried to override
Thoughts? What have I missed? |
…p track of invariants we have already seen.
The implementation using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Thanks for being willing to iterate!
Would you mind making a pypi release so I can tie up loose ends at my end? Thanks! |
Done! And thank you! I haven't benchmarked but for deep interface trees I expect this also results in a small performance boost too. |
5.1.2 (2020-10-01) ================== - Make sure to call each invariant only once when validating invariants. Previously, invariants could be called multiple times because when an invariant is defined in an interface, it's found by in all interfaces inheriting from that interface. See `pull request 215 <https://github.com/zopefoundation/zope.interface/pull/215/>`_. 5.1.1 (2020-09-30) ================== - Fix the method definitions of ``IAdapterRegistry.subscribe``, ``subscriptions`` and ``subscribers``. Previously, they all were defined to accept a ``name`` keyword argument, but subscribers have no names and the implementation of that interface did not accept that argument. See `issue 208 <https://github.com/zopefoundation/zope.interface/issues/208>`_. - Fix a potential reference leak in the C optimizations. Previously, applications that dynamically created unique ``Specification`` objects (e.g., used ``@implementer`` on dynamic classes) could notice a growth of small objects over time leading to increased garbage collection times. See `issue 216 <https://github.com/zopefoundation/zope.interface/issues/216>`_. .. caution:: This leak could prevent interfaces used as the bases of other interfaces from being garbage collected. Those interfaces will now be collected. One way in which this would manifest was that ``weakref.ref`` objects (and things built upon them, like ``Weak[Key|Value]Dictionary``) would continue to have access to the original object even if there were no other visible references to Python and the original object *should* have been collected. This could be especially problematic for the ``WeakKeyDictionary`` when combined with dynamic or local (created in the scope of a function) interfaces, since interfaces are hashed based just on their name and module name. See the linked issue for an example of a resulting ``KeyError``. Note that such potential errors are not new, they are just once again a possibility.
When an invariant is defined in an interface, it's found by
validateInvariants
in all interfaces inheriting from that interface.