summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2020-04-07 07:56:48 -0500
committerJason Madden <jamadden@gmail.com>2020-04-07 07:56:48 -0500
commitc500360aaba5db30e5005664d14659ed7d8186ec (patch)
treeea2515835357faff9c80b952b546df0f1cc4fd6f
parenta404e5fed4766625b564de558942dd4efd979e89 (diff)
downloadzope-interface-issue204.tar.gz
The ImmutableDeclaration also has immutable _v_attrs.issue204
Fixes #204
-rw-r--r--CHANGES.rst6
-rw-r--r--src/zope/interface/declarations.py12
-rw-r--r--src/zope/interface/tests/test_declarations.py33
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):