From d235fdec3ad9b4c5f4be8406349f15a5509cd689 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Fri, 7 Apr 2017 16:34:54 -0700 Subject: [PATCH 1/2] Add test for wrapping an inherited classmethod. Overriding an inherited classmethod should not change the class type returned by the classmethod. This checks for incorrect binding of methods that occur as a side effect of the wrapping. --- tests/test_monkey_patching.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/tests/test_monkey_patching.py b/tests/test_monkey_patching.py index 61c82c5..5e290f5 100644 --- a/tests/test_monkey_patching.py +++ b/tests/test_monkey_patching.py @@ -24,7 +24,13 @@ class Class_1(object): class Class_2(object): @classmethod def method(cls, *args, **kwargs): - return args, kwargs + return cls, args, kwargs + +class Class_2_1(Class_2): + pass + +class Class_2_2(Class_2_1): + pass class Class_3(object): @staticmethod @@ -199,7 +205,32 @@ class TestMonkeyPatching(unittest.TestCase): result = Class_2.method(*_args, **_kwargs) - self.assertEqual(result, (_args, _kwargs)) + self.assertEqual(result, (Class_2, _args, _kwargs)) + self.assertEqual(called[0], (_args, _kwargs)) + + def test_wrap_class_method_inherited(self): + _args = (1, 2) + _kwargs = {'one': 1, 'two': 2} + + called = [] + + def wrapper(wrapped, instance, args, kwargs): + called.append((args, kwargs)) + self.assertEqual(args, _args) + self.assertEqual(kwargs, _kwargs) + return wrapped(*args, **kwargs) + + wrapt.wrap_function_wrapper(__name__, 'Class_2_1.method', + wrapper) + + result = Class_2_1.method(*_args, **_kwargs) + self.assertEqual(result, (Class_2_1, _args, _kwargs)) + self.assertEqual(called[0], (_args, _kwargs)) + + called.pop() + + result = Class_2_2.method(*_args, **_kwargs) + self.assertEqual(result, (Class_2_2, _args, _kwargs)) self.assertEqual(called[0], (_args, _kwargs)) def test_wrap_static_method_module_name(self): From 33148ea028b7a915bb0294c6c49773d4168e96d8 Mon Sep 17 00:00:00 2001 From: Allan Feldman Date: Fri, 7 Apr 2017 16:38:12 -0700 Subject: [PATCH 2/2] Fix introspection logic when resolving class attributes. Using getattr can cause classmethods to bind. In order to access the unbound class method, it must be accessed from the __dict__ attribute. --- src/wrapt/wrappers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wrapt/wrappers.py b/src/wrapt/wrappers.py index be99939..3b1e207 100644 --- a/src/wrapt/wrappers.py +++ b/src/wrapt/wrappers.py @@ -711,8 +711,8 @@ def resolve_path(module, name): if inspect.isclass(original): for cls in inspect.getmro(original): - if attribute in vars(original): - original = vars(original)[attribute] + if attribute in vars(cls): + original = vars(cls)[attribute] break else: original = getattr(original, attribute)