self.assertNotIn("?", err.getvalue())
+ def test_attribute_error_inside_nested_getattr(self):
+ class A:
+ bluch = 1
+
+ class B:
+ def __getattribute__(self, attr):
+ a = A()
+ return a.blich
+
+ try:
+ B().something
+ except AttributeError as exc:
+ with support.captured_stderr() as err:
+ sys.__excepthook__(*sys.exc_info())
+
+ self.assertIn("Did you mean", err.getvalue())
+ self.assertIn("bluch", err.getvalue())
+
class ImportErrorTests(unittest.TestCase):
set_attribute_error_context(PyObject* v, PyObject* name)
{
assert(PyErr_Occurred());
- // Intercept AttributeError exceptions and augment them to offer
- // suggestions later.
- if (PyErr_ExceptionMatches(PyExc_AttributeError)){
- PyObject *type, *value, *traceback;
- PyErr_Fetch(&type, &value, &traceback);
- PyErr_NormalizeException(&type, &value, &traceback);
- if (PyErr_GivenExceptionMatches(value, PyExc_AttributeError) &&
- (PyObject_SetAttr(value, &_Py_ID(name), name) ||
- PyObject_SetAttr(value, &_Py_ID(obj), v))) {
- return 1;
- }
- PyErr_Restore(type, value, traceback);
+ if (!PyErr_ExceptionMatches(PyExc_AttributeError)){
+ return 0;
+ }
+ // Intercept AttributeError exceptions and augment them to offer suggestions later.
+ PyObject *type, *value, *traceback;
+ PyErr_Fetch(&type, &value, &traceback);
+ PyErr_NormalizeException(&type, &value, &traceback);
+ // Check if the normalized exception is indeed an AttributeError
+ if (!PyErr_GivenExceptionMatches(value, PyExc_AttributeError)) {
+ goto restore;
+ }
+ PyAttributeErrorObject* the_exc = (PyAttributeErrorObject*) value;
+ // Check if this exception was already augmented
+ if (the_exc->name || the_exc->obj) {
+ goto restore;
+ }
+ // Augment the exception with the name and object
+ if (PyObject_SetAttr(value, &_Py_ID(name), name) ||
+ PyObject_SetAttr(value, &_Py_ID(obj), v)) {
+ return 1;
}
+restore:
+ PyErr_Restore(type, value, traceback);
return 0;
}
PyErr_Fetch(&type, &value, &traceback);
PyErr_NormalizeException(&type, &value, &traceback);
if (PyErr_GivenExceptionMatches(value, PyExc_NameError)) {
- // We do not care if this fails because we are going to restore the
- // NameError anyway.
- (void)PyObject_SetAttr(value, &_Py_ID(name), obj);
+ PyNameErrorObject* exc = (PyNameErrorObject*) value;
+ if (exc->name == NULL) {
+ // We do not care if this fails because we are going to restore the
+ // NameError anyway.
+ (void)PyObject_SetAttr(value, &_Py_ID(name), obj);
+ }
}
PyErr_Restore(type, value, traceback);
}