Skip to content

Commit

Permalink
Compatibility with PythonScripts (#10)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
thet authored and jensens committed Feb 9, 2017
1 parent 39d67bc commit 0565a81
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 4 deletions.
6 changes: 5 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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)
------------------

Expand Down
16 changes: 13 additions & 3 deletions src/zope/publisher/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

_marker = object() # Create a new marker object.


def unwrapMethod(obj):
"""obj -> (unwrapped, wrapperCount)
Expand All @@ -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:
Expand All @@ -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)
Expand Down
54 changes: 54 additions & 0 deletions src/zope/publisher/tests/test_mapply.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,46 @@
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):
Expand Down Expand Up @@ -66,6 +106,20 @@ class c2:
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)
Expand Down

0 comments on commit 0565a81

Please sign in to comment.