From dd81bd7b5b604c58572c4863c25bdd2385d56468 Mon Sep 17 00:00:00 2001 From: Graham Dumpleton Date: Wed, 11 Jan 2023 12:52:34 +1100 Subject: [PATCH] Fix handing of exceptions in attribute wrappers on a proxy object. --- docs/changes.rst | 23 +++++++++++++++++ src/wrapt/_wrappers.c | 3 +++ tests/test_object_proxy.py | 52 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index b9e4aab5..aa231a62 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -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 -------------- diff --git a/src/wrapt/_wrappers.c b/src/wrapt/_wrappers.c index 67c5d5e1..7ff1085a 100644 --- a/src/wrapt/_wrappers.c +++ b/src/wrapt/_wrappers.c @@ -1535,6 +1535,9 @@ static PyObject *WraptObjectProxy_getattro( if (object) return object; + if (!PyErr_ExceptionMatches(PyExc_AttributeError)) + return NULL; + PyErr_Clear(); if (!getattr_str) { diff --git a/tests/test_object_proxy.py b/tests/test_object_proxy.py index b6d730ce..93586268 100644 --- a/tests/test_object_proxy.py +++ b/tests/test_object_proxy.py @@ -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):