From 0d4b53522fd907c2ace7e69333a5cbaa2ea316c6 Mon Sep 17 00:00:00 2001 From: Bystroushaak Date: Mon, 23 Mar 2015 13:44:43 +0100 Subject: [PATCH 1/2] Added failing test of the decorator with parameters modyfing kwargs. --- test/test_route.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) 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'])) From 57213331104005589287a3226a1a72703e120cb9 Mon Sep 17 00:00:00 2001 From: Bystroushaak Date: Mon, 23 Mar 2015 13:46:13 +0100 Subject: [PATCH 2/2] Added fix of the failing test (0d4b53522fd907c2ace7e69333a5cbaa2ea316c6). See #739 for details. --- bottle.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bottle.py b/bottle.py index 9956aae87..01c0aff81 100644 --- a/bottle.py +++ b/bottle.py @@ -42,6 +42,7 @@ import base64, cgi, email.utils, functools, hmac, imp, itertools, mimetypes,\ os, re, subprocess, 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):