diff options
author | Johannes Raggam <thetetet@gmail.com> | 2017-02-09 18:59:43 +0100 |
---|---|---|
committer | Jens W. Klein <jk@kleinundpartner.at> | 2017-02-09 18:59:43 +0100 |
commit | 0565a81faf06b48e1192e30d20603e979ff53d12 (patch) | |
tree | 97e5b4e581a12cb56ba55c9ba37e722d6434dd5f | |
parent | 39d67bc488ea3d5d33edb14d79d0d0be7f2d29f1 (diff) | |
download | zope-publisher-0565a81faf06b48e1192e30d20603e979ff53d12.tar.gz |
Compatibility with PythonScripts (#10)
Compatibility with PythonScripts:
* Needed for current versions of Products/CMFCore/FSPythonScript.py.
* Use ``six`` to access the function object and function code in ``zope.publisher.publisher.unwrapMethod``.
This restores compatibility with Products.PythonScripts, where parameters were not extracted.
* Use both new and old locations for __code__
* Added test showing we can still handle a pre Python 2.6 method.
-rw-r--r-- | CHANGES.rst | 6 | ||||
-rw-r--r-- | src/zope/publisher/publish.py | 16 | ||||
-rw-r--r-- | src/zope/publisher/tests/test_mapply.py | 54 |
3 files changed, 72 insertions, 4 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 4ce1647..64a75b6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,11 @@ Changes 4.3.1 (unreleased) ------------------ +- Accept both new and old locations for ``__code__`` in + ``zope.publisher.publisher.unwrapMethod``. This restores compatibility with + Products.PythonScripts, where parameters were not extracted. + [maurits, thet, MatthewWilkes] + - Fix file uploads on python 3.4 and up. cgi.FieldStorage explicitly closes files when it is garbage collected. For details, see: @@ -13,7 +18,6 @@ Changes We now keep a reference to the FieldStorage till we are finished processing the request. - 4.3.0 (2016-07-04) ------------------ diff --git a/src/zope/publisher/publish.py b/src/zope/publisher/publish.py index f06f395..c68d6b8 100644 --- a/src/zope/publisher/publish.py +++ b/src/zope/publisher/publish.py @@ -25,6 +25,7 @@ from zope.proxy import removeAllProxies _marker = object() # Create a new marker object. + def unwrapMethod(obj): """obj -> (unwrapped, wrapperCount) @@ -42,13 +43,18 @@ def unwrapMethod(obj): raise TypeError("mapply() can not call class constructors") im_func = getattr(unwrapped, '__func__', None) + if im_func is None: + # Backwards compatibility with objects aimed at Python 2 + im_func = getattr(unwrapped, 'im_func', None) if im_func is not None: unwrapped = im_func wrapperCount += 1 elif getattr(unwrapped, '__code__', None) is not None: break + elif getattr(unwrapped, 'func_code', None) is not None: + break else: - unwrapped = getattr(unwrapped, '__call__' , None) + unwrapped = getattr(unwrapped, '__call__', None) if unwrapped is None: raise TypeError("mapply() can not call %s" % repr(obj)) else: @@ -66,8 +72,12 @@ def mapply(obj, positional=(), request={}): unwrapped, wrapperCount = unwrapMethod(unwrapped) - code = unwrapped.__code__ - defaults = unwrapped.__defaults__ + code = getattr(unwrapped, '__code__', None) + if code is None: + code = unwrapped.func_code + defaults = getattr(unwrapped, '__defaults__', None) + if defaults is None: + defaults = getattr(unwrapped, 'func_defaults', None) names = code.co_varnames[wrapperCount:code.co_argcount] nargs = len(names) diff --git a/src/zope/publisher/tests/test_mapply.py b/src/zope/publisher/tests/test_mapply.py index b322f62..d00bdc1 100644 --- a/src/zope/publisher/tests/test_mapply.py +++ b/src/zope/publisher/tests/test_mapply.py @@ -19,6 +19,46 @@ from zope.publisher.publish import mapply from zope.publisher._compat import PYTHON2 +class AncientMethodCode(object): + """Pretend to be pre Python 2.6 method code. + + See https://docs.python.org/2/reference/datamodel.html + """ + + def __init__(self, code, defaults=None): + self.my_code = code + self.func_defaults = defaults + + def actual_func(self, first, second=None): + if second is None: + second = self.func_defaults[0] + return eval(self.my_code % (first, second)) + + @property + def func_code(self): + return self.actual_func.__code__ + + def __call__(self, *args, **kwargs): + return self.actual_func(*args, **kwargs) + + +class AncientMethod(object): + """Pretend to be a Python 2.6 method. + + Before Python 2.6, methods did not have __func__ and __code__. + They had im_func and func_code instead. + This may still be the case for RestrictedPython scripts. + + See https://docs.python.org/2/reference/datamodel.html + """ + + def __init__(self, code, defaults=None): + self.im_func = AncientMethodCode(code, defaults=defaults) + + def __call__(self, *args, **kwargs): + return self.im_func(*args, **kwargs) + + class MapplyTests(unittest.TestCase): def testMethod(self): def compute(a,b,c=4): @@ -66,6 +106,20 @@ class MapplyTests(unittest.TestCase): v = mapply(c2inst, (), values) self.assertEqual(v, '334') + def testAncientMethod(self): + # Before Python 2.6, methods did not have __func__ and __code__. + # They had im_func and func_code instead. + # This may still be the case for RestrictedPython scripts. + # Pretend a method that accepts one argument and one keyword argument. + # The default value for the keyword argument is given as a tuple. + method = AncientMethod('7 * %d + %d', (0,)) + values = {} + v = mapply(method, (6,), values) + self.assertEqual(v, 42) + v = mapply(method, (5, 4), values) + self.assertEqual(v, 39) + + def test_suite(): loader = unittest.TestLoader() return loader.loadTestsFromTestCase(MapplyTests) |