summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Raggam <thetetet@gmail.com>2017-02-09 18:59:43 +0100
committerJens W. Klein <jk@kleinundpartner.at>2017-02-09 18:59:43 +0100
commit0565a81faf06b48e1192e30d20603e979ff53d12 (patch)
tree97e5b4e581a12cb56ba55c9ba37e722d6434dd5f
parent39d67bc488ea3d5d33edb14d79d0d0be7f2d29f1 (diff)
downloadzope-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.rst6
-rw-r--r--src/zope/publisher/publish.py16
-rw-r--r--src/zope/publisher/tests/test_mapply.py54
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)