From fb5fa208565a462470f7884c6d9cf73adad886c5 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 6 Feb 2020 06:09:29 -0600 Subject: Let interfaces be iterated on Python 3 Fixes https://github.com/zopefoundation/zope.interface/issues/141 --- CHANGES.rst | 12 +++++++----- setup.py | 2 +- src/zope/security/checker.py | 10 ++++++++-- src/zope/security/tests/test_proxy.py | 27 ++++++++++++++++++++++++--- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 18cdfef..86a6a70 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,14 +2,16 @@ Changes ========= -5.1 (unreleased) -================ +5.1.0 (unreleased) +================== -- Nothing changed yet. +- Let proxied interfaces be iterated on Python 3. This worked on + Python 2, but raised ``ForbiddenAttribute`` an Python 3. See + `zope.interface issue 141 `_. -5.0 (2019-11-11) -================ +5.0.0 (2019-11-11) +================== - Drop support for Python 3.4. diff --git a/setup.py b/setup.py index 91b59c6..59a69fa 100644 --- a/setup.py +++ b/setup.py @@ -132,7 +132,7 @@ TESTS_REQUIRE = [ setup(name='zope.security', - version='5.1.dev0', + version='5.1.0.dev0', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description='Zope Security Framework', diff --git a/src/zope/security/checker.py b/src/zope/security/checker.py index f5541a7..8fbf3ec 100644 --- a/src/zope/security/checker.py +++ b/src/zope/security/checker.py @@ -858,8 +858,14 @@ _default_checkers = { type(f()): _iteratorChecker, type(Interface): InterfaceChecker( IInterface, - __str__=CheckerPublic, _implied=CheckerPublic, subscribe=CheckerPublic, - ), + __str__=CheckerPublic, + _implied=CheckerPublic, + subscribe=CheckerPublic, + # To iterate, Python calls __len__ as a hint. + # Python 2 ignores AttributeErrors, but Python 3 + # lets them pass. + __len__=CheckerPublic, + ), zope.interface.interface.Method: InterfaceChecker( zope.interface.interfaces.IMethod), zope.interface.declarations.ProvidesClass: _Declaration_checker, diff --git a/src/zope/security/tests/test_proxy.py b/src/zope/security/tests/test_proxy.py index 55d6d43..cecac86 100644 --- a/src/zope/security/tests/test_proxy.py +++ b/src/zope/security/tests/test_proxy.py @@ -27,6 +27,7 @@ if not PYTHON2: # pragma: no cover (Python 3) raise NotImplementedError("Not on Python 3") cmp = coerce long = int + unicode = str class AbstractProxyTestBase(object): @@ -1770,7 +1771,7 @@ class Something: return x == 42 -class ProxyTests(unittest.TestCase): +class ProxyFactoryTests(unittest.TestCase): def setUp(self): from zope.security.proxy import ProxyFactory @@ -1958,6 +1959,11 @@ class ProxyTests(unittest.TestCase): if PYTHON2: unops.append("long(x)") + def _make_eval(self, expr, locs): + def _eval(*args): + eval(expr, globals(), locs) + return _eval + def test_unops(self): # We want the starting value of the expressions to be a proxy, # but we don't want to create new proxies as a result of @@ -1968,6 +1974,7 @@ class ProxyTests(unittest.TestCase): self.c.unproxied_types = {str, int, float} if PYTHON2: self.c.unproxied_types.add(long) + for expr in self.unops: x = 1 y = eval(expr) @@ -1976,7 +1983,7 @@ class ProxyTests(unittest.TestCase): z = eval(expr) self.assertEqual(removeSecurityProxy(z), y, "x=%r; expr=%r" % (x, expr)) - self.shouldFail(lambda x: eval(expr), x) + self.shouldFail(self._make_eval(expr, locals()), x) @_skip_if_not_Py2 def test_odd_unops(self): @@ -2007,7 +2014,7 @@ class ProxyTests(unittest.TestCase): else: self.assertEqual(removeSecurityProxy(eval(expr)), z, "x=%r; y=%r; expr=%r" % (x, y, expr)) - self.shouldFail(lambda x, y: eval(expr), x, y) + self.shouldFail(self._make_eval(expr, locals()), x, y) def test_inplace(self): # TODO: should test all inplace operators... @@ -2101,6 +2108,20 @@ class ProxyTests(unittest.TestCase): self.assertIs(type(removeSecurityProxy(a)), float) self.assertIs(b, y) + def test_iterate_interface(self): + # This used to work on Python 2, but fail on Python 3. + # See https://github.com/zopefoundation/zope.interface/issues/141 + from zope.interface import Interface + from zope.security.proxy import ProxyFactory + + class IFoo(Interface): + def x(): + """A method""" + + proxy = ProxyFactory(IFoo) + self.assertEqual(list(IFoo), ['x']) + self.assertEqual(list(proxy), list(IFoo)) + def test_using_mapping_slots_hack(): """The security proxy will use mapping slots, on the checker to go faster -- cgit v1.2.1