Skip to content

Commit

Permalink
Fix handing of exceptions in attribute wrappers on a proxy object.
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamDumpleton committed Jan 11, 2023
1 parent 230e127 commit dd81bd7
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 0 deletions.
23 changes: 23 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
Release Notes
=============

Version 1.15.0
--------------

**Bugs Fixed**

* When the C extension for wrapt was being used, and a property was used on an
object proxy wrapping another object to intercept access to an attribute of
the same name on the wrapped object, if the function implementing the property
raised an exception, then the exception was ignored and not propagated back to
the caller. What happened instead was that the original value of the attribute
from the wrapped object was returned, thus silently suppressing that an
exception had occurred in the wrapper. This behaviour was not happening when
the pure Python version of wrapt was being used, with it raising the
exception. The pure Python and C extension implementations thus did not behave
the same.

Note that in the specific case that the exception raised is AttributeError it
still wouldn't be raised. This is the case for both Python and C extension
implementations. If a wrapper for an attribute internally raises an
AttributeError for some reason, the wrapper should if necessary catch the
exception and deal with it, or propagate it as a different exception type if
it is important that an exception still be passed back.

Version 1.14.1
--------------

Expand Down
3 changes: 3 additions & 0 deletions src/wrapt/_wrappers.c
Original file line number Diff line number Diff line change
Expand Up @@ -1535,6 +1535,9 @@ static PyObject *WraptObjectProxy_getattro(
if (object)
return object;

if (!PyErr_ExceptionMatches(PyExc_AttributeError))
return NULL;

PyErr_Clear();

if (!getattr_str) {
Expand Down
52 changes: 52 additions & 0 deletions tests/test_object_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,58 @@ def function1(*args, **kwargs):

self.assertEqual(getattr(function2, 'variable', None), None)

def test_attribute_lookup_modified(self):
class Object:
@property
def value(self):
return "value"

class WrappedObject(wrapt.ObjectProxy):
@property
def value(self):
return 2 * self.__wrapped__.value

WrappedObject(Object()).value == "valuevalue"

def test_attribute_lookup_value_exception(self):
class Object:
@property
def value(self):
return "value"

class WrappedObject(wrapt.ObjectProxy):
@property
def value(self):
raise ValueError("value-error")

try:
WrappedObject(Object()).value == "value"

except ValueError as e:
pass

else:
raise RuntimeError("should not fail here")

def test_attribute_lookup_attribute_exception(self):
class Object:
@property
def value(self):
return "value"

class WrappedObject(wrapt.ObjectProxy):
@property
def value(self):
raise AttributeError("attribute-error")

# Raising of an AttributeError in this case is a sort of odd situation
# because the exception results in it being determined there was no
# wrapper for the value attribute and so it returns the original value
# instead and robs the wrapper of the chance to return an alternate
# value.

WrappedObject(Object()).value == "value"

class TestNamingObjectProxy(unittest.TestCase):

def test_class_object_name(self):
Expand Down

0 comments on commit dd81bd7

Please sign in to comment.