From 4ccfe8925d4e6f8de320124f7117634bc4a8d35f Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Mon, 14 Sep 2015 00:50:26 -0400 Subject: [PATCH] Make ArgReplacer compatible with cython-compiled functions. This requires a directive in the cython code to make the signature introspectable. --- maint/test/cython/cythonapp.pyx | 7 +++++++ maint/test/cython/cythonapp_test.py | 12 ++++++++++++ tornado/util.py | 16 +++++++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/maint/test/cython/cythonapp.pyx b/maint/test/cython/cythonapp.pyx index 3bd1bc9d..0539a228 100644 --- a/maint/test/cython/cythonapp.pyx +++ b/maint/test/cython/cythonapp.pyx @@ -1,3 +1,4 @@ +import cython from tornado import gen import pythonmodule @@ -13,3 +14,9 @@ def decorated_coroutine(): if x != "hello": raise ValueError("expected hello, got %r" % x) return "goodbye" + +# The binding directive is necessary for compatibility with +# ArgReplacer (and therefore return_future). +@cython.binding(True) +def function_with_args(one, two, three): + return (one, two, three) diff --git a/maint/test/cython/cythonapp_test.py b/maint/test/cython/cythonapp_test.py index 76427f42..975194eb 100644 --- a/maint/test/cython/cythonapp_test.py +++ b/maint/test/cython/cythonapp_test.py @@ -6,6 +6,8 @@ else: backports_abc.patch() from tornado.testing import AsyncTestCase, gen_test +from tornado.util import ArgReplacer +import unittest import cythonapp @@ -20,3 +22,13 @@ class CythonCoroutineTest(AsyncTestCase): def test_decorated_coroutine(self): x = yield cythonapp.decorated_coroutine() self.assertEqual(x, "goodbye") + + +class CythonArgReplacerTest(unittest.TestCase): + def test_arg_replacer(self): + replacer = ArgReplacer(cythonapp.function_with_args, 'two') + args = (1, 'old', 3) + kwargs = {} + self.assertEqual(replacer.get_old_value(args, kwargs), 'old') + self.assertEqual(replacer.replace('new', args, kwargs), + ('old', [1, 'new', 3], {})) diff --git a/tornado/util.py b/tornado/util.py index ea4da876..ce5362dc 100644 --- a/tornado/util.py +++ b/tornado/util.py @@ -290,11 +290,25 @@ class ArgReplacer(object): def __init__(self, func, name): self.name = name try: - self.arg_pos = getargspec(func).args.index(self.name) + self.arg_pos = self._getargnames(func).index(name) except ValueError: # Not a positional parameter self.arg_pos = None + def _getargnames(self, func): + try: + return getargspec(func).args + except TypeError: + if hasattr(func, 'func_code'): + # Cython-generated code has all the attributes needed + # by inspect.getargspec (when the + # @cython.binding(True) directive is used), but the + # inspect module only works with ordinary functions. + # Inline the portion of getargspec that we need here. + code = func.func_code + return code.co_varnames[:code.co_argcount] + raise + def get_old_value(self, args, kwargs, default=None): """Returns the old value of the named argument without replacing it.