Make ArgReplacer compatible with cython-compiled functions.

This requires a directive in the cython code to make the signature
introspectable.
This commit is contained in:
Ben Darnell 2015-09-14 00:50:26 -04:00
parent 3463036d75
commit 4ccfe8925d
3 changed files with 34 additions and 1 deletions

View File

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

View File

@ -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], {}))

View File

@ -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.