diff --git a/bottle.py b/bottle.py index ee4398ea2..f85fd0a13 100644 --- a/bottle.py +++ b/bottle.py @@ -42,6 +42,7 @@ import base64, cgi, email.utils, functools, hmac, imp, itertools, mimetypes,\ os, re, sys, tempfile, threading, time, warnings +from types import FunctionType from datetime import date as datedate, datetime, timedelta from tempfile import TemporaryFile from traceback import format_exc, print_exc @@ -543,7 +544,17 @@ def get_undecorated_callback(self): func = getattr(func, '__func__' if py3k else 'im_func', func) closure_attr = '__closure__' if py3k else 'func_closure' while hasattr(func, closure_attr) and getattr(func, closure_attr): - func = getattr(func, closure_attr)[0].cell_contents + attributes = getattr(func, closure_attr) + func = attributes[0].cell_contents + + # in case of decorators with multiple arguments + if not isinstance(func, FunctionType) and func is not None: + # pick first FunctionType instance from multiple arguments + func = filter( + lambda x: isinstance(x, FunctionType), + map(lambda x: x.cell_contents, attributes) + ) + func = list(func)[0] # py3 support return func def get_callback_args(self): diff --git a/test/test_route.py b/test/test_route.py index 66709cb5c..237d3c7f2 100644 --- a/test/test_route.py +++ b/test/test_route.py @@ -28,3 +28,23 @@ def w(): self.assertEqual(route.get_undecorated_callback(), x) self.assertEqual(set(route.get_callback_args()), set(['a', 'b'])) + def test_callback_inspection_multiple_args(self): + # decorator with argument, modifying kwargs + def d2(f="1"): + def d(fn): + def w(*args, **kwargs): + # modification of kwargs WITH the decorator argument + # is necessary requirement for the error + kwargs["a"] = f + return fn(*args, **kwargs) + return w + return d + + @d2(f='foo') + def x(a, b): + return + + route = bottle.Route(None, None, None, x) + + # triggers the "TypeError: 'foo' is not a Python function" + self.assertEqual(set(route.get_callback_args()), set(['a', 'b']))