Skip to content

Commit

Permalink
Fix calling of callable object when binding was attempted.
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamDumpleton committed Oct 5, 2024
1 parent 095323e commit 555865b
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 6 deletions.
6 changes: 6 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ Note that version 1.17.0 drops support for Python 3.6 and 3.7. Python version
or instance. This was not the correct behaviour and the class or instance
should not have been passed as the first argument.

* When an instance of a callable class object was wrapped which didn't not have
a `__get__()` method for binding, and it was called in context whhere binding
would be attempted, it would fail with error that `__get__()` did not exist
when instead it should have been called directly, ignoring that binding was
not possible.

Version 1.16.0
--------------

Expand Down
6 changes: 2 additions & 4 deletions src/wrapt/_wrappers.c
Original file line number Diff line number Diff line change
Expand Up @@ -2488,10 +2488,8 @@ static PyObject *WraptFunctionWrapperBase_descr_get(
}

if (Py_TYPE(self->object_proxy.wrapped)->tp_descr_get == NULL) {
PyErr_Format(PyExc_AttributeError,
"'%s' object has no attribute '__get__'",
Py_TYPE(self->object_proxy.wrapped)->tp_name);
return NULL;
Py_INCREF(self);
return (PyObject *)self;
}

descriptor = (Py_TYPE(self->object_proxy.wrapped)->tp_descr_get)(
Expand Down
10 changes: 9 additions & 1 deletion src/wrapt/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,13 +528,21 @@ def __get__(self, instance, owner):
# extract the instance from the first argument of those passed in.

if self._self_parent is None:
# Technically can probably just check for existence of __get__ on
# the wrapped object, but this is more explicit.

if self._self_binding == 'builtin':
return self

if self._self_binding == "class":
return self

descriptor = self.__wrapped__.__get__(instance, owner)
binder = getattr(self.__wrapped__, '__get__', None)

if binder is None:
return self

descriptor = binder(instance, owner)

return self.__bound_function_wrapper__(descriptor, instance,
self._self_wrapper, self._self_enabled,
Expand Down
38 changes: 38 additions & 0 deletions tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,5 +297,43 @@ class C13:
C11.f1(c11)
C12.f2(c12)

def test_call_semantics_for_assorted_wrapped_descriptor_use_cases(self):
class A:
def __call__(self):
print("A:__call__")

a = A()

class B:
def __call__(self):
print("B:__call__")
def __get__(self, obj, type):
print("B:__get__")
return self

b = B()

class C:
f1 = a
f2 = b

c = C()

c.f1()
c.f2()

@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)

class D:
f1 = wrapper(a)
f2 = wrapper(b)

d = D()

d.f1()
d.f2()

if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion tests/test_function_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ def _wrapper(wrapped, instance, args, kwargs):
wrapper = wrapt.FunctionWrapper(None, _wrapper)
wrapper.__get__(list(), list)()

self.assertRaises(AttributeError, run, ())
self.assertRaises(TypeError, run, ())

class TestInvalidCalling(unittest.TestCase):

Expand Down

0 comments on commit 555865b

Please sign in to comment.