The ImmutableDeclaration also has immutable _v_attrs.

Fixes #204
This commit is contained in:
Jason Madden 2020-04-07 07:56:48 -05:00
parent a404e5fed4
commit c500360aab
No known key found for this signature in database
GPG Key ID: 349F84431A08B99E
3 changed files with 45 additions and 6 deletions

View File

@ -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)
==================

View File

@ -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
##############################################################################
#

View File

@ -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):