diff options
author | Jason Madden <jamadden@gmail.com> | 2020-04-07 07:56:48 -0500 |
---|---|---|
committer | Jason Madden <jamadden@gmail.com> | 2020-04-07 07:56:48 -0500 |
commit | c500360aaba5db30e5005664d14659ed7d8186ec (patch) | |
tree | ea2515835357faff9c80b952b546df0f1cc4fd6f | |
parent | a404e5fed4766625b564de558942dd4efd979e89 (diff) | |
download | zope-interface-issue204.tar.gz |
The ImmutableDeclaration also has immutable _v_attrs.issue204
Fixes #204
-rw-r--r-- | CHANGES.rst | 6 | ||||
-rw-r--r-- | src/zope/interface/declarations.py | 12 | ||||
-rw-r--r-- | src/zope/interface/tests/test_declarations.py | 33 |
3 files changed, 45 insertions, 6 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 70f85e9..b86e14d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -45,6 +45,12 @@ See `issue 3 <https://github.com/zopefoundation/zope.interface/issues/3>`_. +- Make the internal singleton object returned by APIs like + ``implementedBy`` and ``directlyProvidedBy`` for objects that + implement or provide no interfaces more immutable. Previously an + internal cache could be mutated. See `issue 204 + <https://github.com/zopefoundation/zope.interface/issues/204>`_. + 5.0.2 (2020-03-30) ================== diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py index b42c906..d77c824 100644 --- a/src/zope/interface/declarations.py +++ b/src/zope/interface/declarations.py @@ -195,6 +195,18 @@ class _ImmutableDeclaration(Declaration): # object, and that includes a method.) return _ImmutableDeclaration + @property + def _v_attrs(self): + # _v_attrs is not a public, documented property, but some client + # code uses it anyway as a convenient place to cache things. To keep + # the empty declaration truly immutable, we must ignore that. That includes + # ignoring assignments as well. + return {} + + @_v_attrs.setter + def _v_attrs(self, new_attrs): + pass + ############################################################################## # diff --git a/src/zope/interface/tests/test_declarations.py b/src/zope/interface/tests/test_declarations.py index 83815d7..fc2ab68 100644 --- a/src/zope/interface/tests/test_declarations.py +++ b/src/zope/interface/tests/test_declarations.py @@ -122,6 +122,20 @@ class EmptyDeclarationTests(unittest.TestCase): decl = self._getEmpty() self.assertEqual(decl.__iro__, (Interface,)) + def test_get(self): + decl = self._getEmpty() + self.assertIsNone(decl.get('attr')) + self.assertEqual(decl.get('abc', 'def'), 'def') + # It's a positive cache only (when it even exists) + # so this added nothing. + self.assertFalse(decl._v_attrs) + + def test_changed_w_existing__v_attrs(self): + decl = self._getEmpty() + decl._v_attrs = object() + decl.changed(decl) + self.assertFalse(decl._v_attrs) + class DeclarationTests(EmptyDeclarationTests): @@ -153,12 +167,6 @@ class DeclarationTests(EmptyDeclarationTests): decl.changed(decl) # doesn't raise self.assertIsNone(decl._v_attrs) - def test_changed_w_existing__v_attrs(self): - decl = self._makeOne() - decl._v_attrs = object() - decl.changed(decl) - self.assertIsNone(decl._v_attrs) - def test___contains__w_self(self): decl = self._makeOne() self.assertNotIn(decl, decl) @@ -335,6 +343,19 @@ class TestImmutableDeclaration(EmptyDeclarationTests): self.assertIsNone(self._getEmpty().get('name')) self.assertEqual(self._getEmpty().get('name', 42), 42) + def test_v_attrs(self): + decl = self._getEmpty() + self.assertEqual(decl._v_attrs, {}) + + decl._v_attrs['attr'] = 42 + self.assertEqual(decl._v_attrs, {}) + self.assertIsNone(decl.get('attr')) + + attrs = decl._v_attrs = {} + attrs['attr'] = 42 + self.assertEqual(decl._v_attrs, {}) + self.assertIsNone(decl.get('attr')) + class TestImplements(NameAndModuleComparisonTestsMixin, unittest.TestCase): |