Fix an argspec detection edge case in pecan.util.getargspec

In lieu of decorators that responsibly duplicate the argspec of underlying
functions, pecan must make a best guess.  When multiple nested decorators are
discovered, the __closure__ of the wrapped function is searched for callables
(one of which likely represents the underlying wrapped function - the
controller method itself).  This change tweaks the algorithm a bit further to
place higher match precedence on in-scope functions that have `self` as a first
argument.
This commit is contained in:
Ryan Petrello
2015-08-18 21:43:45 -04:00
parent 34bd468cb3
commit 1281274677
2 changed files with 37 additions and 6 deletions

View File

@@ -93,3 +93,23 @@ class TestArgSpec(unittest.TestCase):
actual = util.getargspec(dec(True)(
self.controller.static_index))
assert expected == actual
def test_nested_cells(self):
def before(handler):
def deco(f):
def wrapped(*args, **kwargs):
if callable(handler):
handler()
return f(*args, **kwargs)
return wrapped
return deco
class RootController(object):
@expose()
@before(lambda: True)
def index(self, a, b, c):
return 'Hello, World!'
argspec = util._cfg(RootController.index)['argspec']
assert argspec.args == ['self', 'a', 'b', 'c']

View File

@@ -26,15 +26,26 @@ def getargspec(method):
# NOTE(sileht): if the closure is None we cannot look deeper,
# so return actual argspec, this occurs when the method
# is static for example.
if func_closure is None:
if not func_closure:
return argspec
closure = next(
(
c for c in func_closure if six.callable(c.cell_contents)
),
None
closure = None
# In the case of deeply nested decorators (with arguments), it's possible
# that there are several callables in scope; Take a best guess and go
# with the one that looks most like a pecan controller function
# ('self' is the first argument)
func_closure = filter(
lambda c: six.callable(c.cell_contents),
func_closure
)
func_closure = sorted(
func_closure,
key=lambda c: 'self' in c.cell_contents.__code__.co_varnames,
reverse=True
)
closure = func_closure[0]
method = closure.cell_contents
return getargspec(method)