diff --git a/CHANGES.rst b/CHANGES.rst index 0203e6a..aea7438 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,12 @@ Changes 4.1.1 (unreleased) ------------------ -- TBD +- Fix `issue 23 `_: + iteration of ``collections.OrderedDict`` and its various views is + now allowed by default on all versions of Python. + +- As a further fix for issue 20, iteration of BTree itself is now + allowed by default. 4.1.0 (2017-04-24) ------------------ diff --git a/src/zope/security/checker.py b/src/zope/security/checker.py index 3187afc..344428c 100644 --- a/src/zope/security/checker.py +++ b/src/zope/security/checker.py @@ -770,6 +770,27 @@ def f(): #pragma NO COVER _default_checkers[type({}.iterkeys())] = _iteratorChecker _default_checkers[type({}.itervalues())] = _iteratorChecker +def _fixup_dictlike(dict_type): + empty_dict = dict_type() + populated_dict = dict_type({1: 2}) + for dictlike in (empty_dict, populated_dict): + for attr in ('__iter__', 'keys', 'items', 'values'): + obj = getattr(dictlike, attr)() + o_type = type(obj) + if o_type not in _default_checkers: + _default_checkers[o_type] = _iteratorChecker + +def _fixup_odict(): + # OrderedDicts have three different implementations: Python 2 (pure + # python, returns generators and lists), Python <=3.4 (pure Python, + # uses view classes) and CPython 3.5+ (implemented in C). These should + # all be iterable. + from collections import OrderedDict + _fixup_dictlike(OrderedDict) + +_fixup_odict() +del _fixup_odict + try: import BTrees except ImportError: # pragma: no cover @@ -794,20 +815,14 @@ def _fixup_btrees(): for name in ('IF', 'II', 'IO', 'OI', 'OO'): for family_name in ('family32', 'family64'): family = getattr(BTrees, family_name) - btree = getattr(family, name).BTree() - - empty_type = type(btree.items()) - if empty_type not in _default_checkers: - _default_checkers[empty_type] = _iteratorChecker - - btree[1] = 1 - populated_type = type(btree.items()) - if populated_type not in _default_checkers: - _default_checkers[populated_type] = _iteratorChecker + btree = getattr(family, name).BTree + _fixup_dictlike(btree) _fixup_btrees() del _fixup_btrees +del _fixup_dictlike + def _clear(): _checkers.clear() _checkers.update(_default_checkers) diff --git a/src/zope/security/tests/test_checker.py b/src/zope/security/tests/test_checker.py index ceda502..03a8883 100644 --- a/src/zope/security/tests/test_checker.py +++ b/src/zope/security/tests/test_checker.py @@ -391,34 +391,48 @@ def _check(*args): finally: _clear() - @_skip_if_no_btrees - def test_iteration_of_btree_items(self): - # iteration of BTree.items() is allowed by default. + def _check_iteration_of_dict_like(self, dict_like): from zope.security.proxy import Proxy from zope.security.checker import Checker - from zope.security.checker import CheckerPublic - import BTrees + from zope.security.checker import _default_checkers + + checker = _default_checkers[dict] + + proxy = Proxy(dict_like, checker) + # empty + self.assertEqual([], list(proxy.items())) + self.assertEqual([], list(proxy.keys())) + self.assertEqual([], list(proxy.values())) + self.assertEqual([], list(proxy)) - checker = Checker({'items': CheckerPublic, - 'keys': CheckerPublic, - 'values': CheckerPublic}) + # With an object + dict_like[1] = 2 + self.assertEqual([(1, 2)], list(proxy.items())) + self.assertEqual([1], list(proxy.keys())) + self.assertEqual([1], list(proxy)) + self.assertEqual([2], list(proxy.values())) + + @_skip_if_no_btrees + def test_iteration_of_btree_items_keys_values(self): + # iteration of BTree.items() is allowed by default. + import BTrees for name in ('IF', 'II', 'IO', 'OI', 'OO'): for family_name in ('family32', 'family64'): family = getattr(BTrees, family_name) btree = getattr(family, name).BTree() - proxy = Proxy(btree, checker) - # empty - self.assertEqual([], list(proxy.items())) - self.assertEqual([], list(proxy.keys())) - self.assertEqual([], list(proxy.values())) - - # With an object - btree[1] = 2 - self.assertEqual([(1, 2)], list(proxy.items())) - self.assertEqual([1], list(proxy.keys())) - self.assertEqual([2], list(proxy.values())) + self._check_iteration_of_dict_like(btree) + + def test_iteration_of_odict_items_keys_values(self): + # iteration of OrderedDict.items() is allowed by default. + from collections import OrderedDict + + odict = OrderedDict() + self._check_iteration_of_dict_like(odict) + def test_iteration_of_dict_items_keys_values(self): + # iteration of regular dict is allowed by default + self._check_iteration_of_dict_like(dict()) class CheckerPyTests(unittest.TestCase, CheckerTestsBase):