From ff933cd264bbc146646c6351937c285f954cd4f6 Mon Sep 17 00:00:00 2001 From: Graham Dumpleton Date: Sun, 25 Aug 2013 13:50:00 +1000 Subject: [PATCH] Use __self__ from bound function as allows us to provide instance as class type for class method and None for static method. --- src/_wrappers.c | 23 +++++++++++++++++++++-- src/wrappers.py | 17 ++++++++++++++++- tests/test_inner_classmethod.py | 16 ++++++---------- tests/test_inner_staticmethod.py | 12 ++++-------- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/_wrappers.c b/src/_wrappers.c index ccfe9fb..afa0343 100644 --- a/src/_wrappers.c +++ b/src/_wrappers.c @@ -1602,6 +1602,8 @@ static PyObject *WraptBoundFunctionWrapper_call( PyObject *call_args = NULL; PyObject *param_kwds = NULL; + PyObject *instance = NULL; + PyObject *result = NULL; Py_ssize_t len = 0; @@ -1612,16 +1614,31 @@ static PyObject *WraptBoundFunctionWrapper_call( kwds = param_kwds; } + /* + * We actually ignore the instance supplied when the function was + * bound and use that saved against __self__ of the bound function. + * This will be the class type for a class method and None for the + * case of a static method. + */ + + instance = PyObject_GetAttrString(self->object_proxy.wrapped, "__self__"); + + if (!instance) { + PyErr_Clear(); + Py_INCREF(Py_None); + instance = Py_None; + } + len = PySequence_Size(self->wrapper_args); call_args = PyTuple_New(len+4); Py_INCREF(self->object_proxy.wrapped); - Py_INCREF(self->instance); + Py_INCREF(instance); Py_INCREF(args); Py_INCREF(kwds); PyTuple_SET_ITEM(call_args, 0, self->object_proxy.wrapped); - PyTuple_SET_ITEM(call_args, 1, self->instance); + PyTuple_SET_ITEM(call_args, 1, instance); PyTuple_SET_ITEM(call_args, 2, args); PyTuple_SET_ITEM(call_args, 3, kwds); @@ -1637,6 +1654,8 @@ static PyObject *WraptBoundFunctionWrapper_call( Py_DECREF(call_args); Py_XDECREF(param_kwds); + Py_DECREF(instance); + return result; } diff --git a/src/wrappers.py b/src/wrappers.py index 17dd030..30e9cc7 100644 --- a/src/wrappers.py +++ b/src/wrappers.py @@ -330,7 +330,22 @@ class _BoundFunctionWrapper(ObjectProxy): self._self_wrapper_kwargs = kwargs def __call__(self, *args, **kwargs): - return self._self_wrapper(self._self_wrapped, self._self_instance, + # As in this case we would be dealing with a class method or + # static method, then _self_instance will only tell us whether + # when calling the class or static method they did it via an + # instance of the class it is bound to and not the case where + # done by the class type itself. We thus ignore __self_instance + # and use the __self__ attribute of the bound function instead. + # For a class method, this means instance will be the class type + # and for a static method it will be None. This is probably the + # more useful thing we can pass through even though we loose + # knowledge of whether they were called on the instance vs the + # class type, as it reflects what they have available in the + # decoratored function. + + instance = getattr(self._self_wrapped, '__self__', None) + + return self._self_wrapper(self._self_wrapped, instance, args, kwargs, *self._self_wrapper_args, **self._self_wrapper_kwargs) diff --git a/tests/test_inner_classmethod.py b/tests/test_inner_classmethod.py index f9d075f..87e920b 100644 --- a/tests/test_inner_classmethod.py +++ b/tests/test_inner_classmethod.py @@ -128,7 +128,7 @@ class TestCallingInnerClassMethod(unittest.TestCase): @wrapt.decorator def _decorator(wrapped, instance, args, kwargs): - self.assertEqual(instance, None) + self.assertEqual(instance, Class) self.assertEqual(args, _args) self.assertEqual(kwargs, _kwargs) return wrapped(*args, **kwargs) @@ -148,16 +148,14 @@ class TestCallingInnerClassMethod(unittest.TestCase): self.assertEqual(result, (_args, _kwargs)) def test_instance_call_function(self): - # Test calling classmethod via class instance. The instance - # passed to the wrapper will not be None because our decorator - # surrounds the classmethod decorator. + # Test calling classmethod via class instance. _args = (1, 2) _kwargs = { 'one': 1, 'two': 2 } @wrapt.decorator def _decorator(wrapped, instance, args, kwargs): - self.assertNotEqual(instance, None) + self.assertEqual(instance, Class) self.assertEqual(args, _args) self.assertEqual(kwargs, _kwargs) return wrapped(*args, **kwargs) @@ -184,7 +182,7 @@ class TestCallingInnerClassMethod(unittest.TestCase): @wrapt.decorator def _decorator(wrapped, instance, args, kwargs): - self.assertEqual(instance, None) + self.assertEqual(instance, Class) self.assertEqual(args, _args) self.assertEqual(kwargs, _kwargs) return wrapped(*args, **kwargs) @@ -205,16 +203,14 @@ class TestCallingInnerClassMethod(unittest.TestCase): self.assertEqual(result, (_args, _kwargs)) def test_instance_call_function_nested_decorators(self): - # Test calling classmethod via class instance. The instance - # passed to the wrapper will not be None because our decorator - # surrounds the classmethod decorator. + # Test calling classmethod via class instance. _args = (1, 2) _kwargs = { 'one': 1, 'two': 2 } @wrapt.decorator def _decorator(wrapped, instance, args, kwargs): - self.assertNotEqual(instance, None) + self.assertEqual(instance, Class) self.assertEqual(args, _args) self.assertEqual(kwargs, _kwargs) return wrapped(*args, **kwargs) diff --git a/tests/test_inner_staticmethod.py b/tests/test_inner_staticmethod.py index e84d043..baebe0d 100644 --- a/tests/test_inner_staticmethod.py +++ b/tests/test_inner_staticmethod.py @@ -148,16 +148,14 @@ class TestCallingInnerStaticMethod(unittest.TestCase): self.assertEqual(result, (_args, _kwargs)) def test_instance_call_function(self): - # Test calling staticmethod via class instance. The instance - # passed to the wrapper will not be None because our decorator - # surrounds the staticmethod decorator. + # Test calling staticmethod via class instance. _args = (1, 2) _kwargs = { 'one': 1, 'two': 2 } @wrapt.decorator def _decorator(wrapped, instance, args, kwargs): - self.assertNotEqual(instance, None) + self.assertEqual(instance, None) self.assertEqual(args, _args) self.assertEqual(kwargs, _kwargs) return wrapped(*args, **kwargs) @@ -205,16 +203,14 @@ class TestCallingInnerStaticMethod(unittest.TestCase): self.assertEqual(result, (_args, _kwargs)) def test_instance_call_function_nested_decorator(self): - # Test calling staticmethod via class instance. The instance - # passed to the wrapper will not be None because our decorator - # surrounds the staticmethod decorator. + # Test calling staticmethod via class instance. _args = (1, 2) _kwargs = { 'one': 1, 'two': 2 } @wrapt.decorator def _decorator(wrapped, instance, args, kwargs): - self.assertNotEqual(instance, None) + self.assertEqual(instance, None) self.assertEqual(args, _args) self.assertEqual(kwargs, _kwargs) return wrapped(*args, **kwargs)