Skip to content

Commit

Permalink
Merge pull request #24 from zopefoundation/fix-ordered-dict-checker
Browse files Browse the repository at this point in the history
OrderedDict, BTree and dict all iterate the same way.
  • Loading branch information
jamadden committed May 17, 2017
2 parents 2b82f83 + cdb0161 commit 7c93ce5
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 30 deletions.
7 changes: 6 additions & 1 deletion CHANGES.rst
Expand Up @@ -4,7 +4,12 @@ Changes
4.1.1 (unreleased)
------------------

- TBD
- Fix `issue 23 <https://github.com/zopefoundation/zope.security/issues/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)
------------------
Expand Down
35 changes: 25 additions & 10 deletions src/zope/security/checker.py
Expand Up @@ -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
Expand All @@ -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)
Expand Down
52 changes: 33 additions & 19 deletions src/zope/security/tests/test_checker.py
Expand Up @@ -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):

Expand Down

0 comments on commit 7c93ce5

Please sign in to comment.