from codecs import BOM_UTF8
from itertools import product
from textwrap import dedent
+from types import ModuleType
from test.support import (captured_stderr, check_impl_detail,
cpython_only, gc_collect,
self.assertEqual("bluch", exc.name)
self.assertEqual(obj, exc.obj)
+ def test_getattr_error_message(self):
+ def fqn(type):
+ return f'{type.__module__}.{type.__qualname__}'
+
+ class RaiseWithName:
+ def __getattr__(self, name):
+ raise AttributeError(name)
+ obj = RaiseWithName()
+ with self.assertRaises(AttributeError) as cm:
+ getattr(obj, "missing1")
+ self.assertEqual(str(cm.exception),
+ f"'{fqn(RaiseWithName)}' object has no attribute 'missing1'")
+ self.assertIs(cm.exception.obj, obj)
+ self.assertEqual(cm.exception.name, "missing1")
+
+ class BareRaise:
+ def __getattr__(self, name):
+ raise AttributeError
+ obj = BareRaise()
+ with self.assertRaises(AttributeError) as cm:
+ getattr(obj, "missing2")
+ self.assertEqual(str(cm.exception),
+ f"'{fqn(BareRaise)}' object has no attribute 'missing2'")
+ self.assertIs(cm.exception.obj, obj)
+ self.assertEqual(cm.exception.name, "missing2")
+
+ class RaiseCustom:
+ def __getattr__(self, name):
+ raise AttributeError("custom")
+ obj = RaiseCustom()
+ with self.assertRaises(AttributeError) as cm:
+ getattr(obj, "missing3")
+ self.assertEqual(str(cm.exception), "custom")
+ self.assertIs(cm.exception.obj, obj)
+ self.assertEqual(cm.exception.name, "missing3")
+
+ def test_class_getattr_error_message(self):
+ def fqn(type):
+ return f'{type.__module__}.{type.__qualname__}'
+
+ class MetaclassRaiseWithName(type):
+ def __getattr__(self, name):
+ raise AttributeError(name)
+ cls = MetaclassRaiseWithName("spam", (), {})
+ with self.assertRaises(AttributeError) as cm:
+ getattr(cls, "missing1")
+ self.assertEqual(str(cm.exception),
+ f"type object '{fqn(cls)}' has no attribute 'missing1'")
+ self.assertIs(cm.exception.obj, cls)
+ self.assertEqual(cm.exception.name, "missing1")
+
+ class MetaclassBareRaise(type):
+ def __getattr__(self, name):
+ raise AttributeError
+ cls = MetaclassBareRaise("eggs", (), {})
+ with self.assertRaises(AttributeError) as cm:
+ getattr(cls, "missing2")
+ self.assertEqual(str(cm.exception),
+ f"type object '{fqn(cls)}' has no attribute 'missing2'")
+ self.assertIs(cm.exception.obj, cls)
+ self.assertEqual(cm.exception.name, "missing2")
+
+ class MetaclassRaiseCustom(type):
+ def __getattr__(self, name):
+ raise AttributeError("custom")
+ cls = MetaclassRaiseCustom("ham", (), {})
+ with self.assertRaises(AttributeError) as cm:
+ getattr(cls, "missing3")
+ self.assertEqual(str(cm.exception), "custom")
+ self.assertIs(cm.exception.obj, cls)
+ self.assertEqual(cm.exception.name, "missing3")
+
+ def test_module_getattr_error_message(self):
+ raisewithname_mod = ModuleType("raisewithname")
+ def raise_with_name(name):
+ raise AttributeError(name)
+ raisewithname_mod.__getattr__ = raise_with_name
+ with self.assertRaises(AttributeError) as cm:
+ getattr(raisewithname_mod, "missing1")
+ self.assertEqual(str(cm.exception),
+ "module 'raisewithname' has no attribute 'missing1'")
+ self.assertIs(cm.exception.obj, raisewithname_mod)
+ self.assertEqual(cm.exception.name, "missing1")
+
+ bareraise_mod = ModuleType("bareraise")
+ def bare_raise(name):
+ raise AttributeError
+ bareraise_mod.__getattr__ = bare_raise
+ with self.assertRaises(AttributeError) as cm:
+ getattr(bareraise_mod, "missing2")
+ self.assertEqual(str(cm.exception),
+ "module 'bareraise' has no attribute 'missing2'")
+ self.assertIs(cm.exception.obj, bareraise_mod)
+ self.assertEqual(cm.exception.name, "missing2")
+
+ custom_mod = ModuleType("custom")
+ def raise_custom(name):
+ raise AttributeError("custom")
+ custom_mod.__getattr__ = raise_custom
+ with self.assertRaises(AttributeError) as cm:
+ getattr(custom_mod, "missing3")
+ self.assertEqual(str(cm.exception), "custom")
+ self.assertIs(cm.exception.obj, custom_mod)
+ self.assertEqual(cm.exception.name, "missing3")
+
+ nameless_mod = ModuleType("forgettable")
+ del nameless_mod.__dict__["__name__"]
+ nameless_mod.__getattr__ = raise_with_name
+ with self.assertRaises(AttributeError) as cm:
+ getattr(nameless_mod, "missing4")
+ self.assertEqual(str(cm.exception), "module has no attribute 'missing4'")
+ self.assertIs(cm.exception.obj, nameless_mod)
+ self.assertEqual(cm.exception.name, "missing4")
+
+ nameless_mod = ModuleType("broken")
+ nameless_mod.__dict__["__name__"] = 10j
+ nameless_mod.__getattr__ = raise_with_name
+ with self.assertRaises(AttributeError) as cm:
+ getattr(nameless_mod, "missing4")
+ self.assertEqual(str(cm.exception), "module has no attribute 'missing4'")
+ self.assertIs(cm.exception.obj, nameless_mod)
+ self.assertEqual(cm.exception.name, "missing4")
+
# Note: name suggestion tests live in `test_traceback`.
#include "pycore_exceptions.h" // struct _Py_exc_state
#include "pycore_initconfig.h"
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
+#include "pycore_moduleobject.h" // _PyModule_CAST()
#include "pycore_object.h"
#include "pycore_pyerrors.h" // struct _PyErr_SetRaisedException
#include "pycore_tuple.h" // _PyTuple_FromPair
+#include "pycore_unicodeobject.h" // _PyUnicode_Equal()
#include "osdefs.h" // SEP
#include "clinic/exceptions.c.h"
Py_TYPE(self)->tp_free(self);
}
+static PyObject *
+AttributeError_str(PyObject *op)
+{
+ PyAttributeErrorObject *self = PyAttributeErrorObject_CAST(op);
+ PyObject *arg; // borrowed ref
+ PyObject *obj = NULL, *name = NULL;
+
+ /* .name and .obj are set automatically when attribute lookup fails, so
+ synthesize a more informative message from them when the caller
+ didn't supply a meaningful one of their own -- that is, when args is
+ empty, or contains only the attribute name. Otherwise, use the
+ message the caller gave. */
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ if (
+ self->obj && self->name && PyUnicode_Check(self->name)
+ && ((PyTuple_GET_SIZE(self->args) == 1
+ && PyUnicode_Check(arg = PyTuple_GET_ITEM(self->args, 0))
+ && _PyUnicode_Equal(arg, self->name))
+ || PyTuple_GET_SIZE(self->args) == 0)
+ ) {
+ obj = Py_NewRef(self->obj);
+ name = Py_NewRef(self->name);
+ }
+ Py_END_CRITICAL_SECTION();
+
+ if (!obj) {
+ assert(!name);
+ return BaseException_str(op); /* re-acquires lock */
+ }
+
+ PyObject *result = NULL;
+ if (PyModule_Check(obj)) {
+ PyModuleObject *mod = _PyModule_CAST(obj);
+ PyObject *modname;
+ if (PyDict_GetItemRef(mod->md_dict, &_Py_ID(__name__), &modname) < 0) {
+ goto done;
+ }
+ if (modname && PyUnicode_Check(modname)) {
+ result = PyUnicode_FromFormat("module %R has no attribute %R",
+ modname, name);
+ Py_DECREF(modname);
+ } else {
+ Py_XDECREF(modname);
+ result = PyUnicode_FromFormat("module has no attribute %R", name);
+ }
+ } else if (PyType_Check(obj)) {
+ result = PyUnicode_FromFormat("type object '%N' has no attribute %R",
+ obj, name);
+ } else {
+ result = PyUnicode_FromFormat("'%T' object has no attribute %R",
+ obj, name);
+ }
+done:
+ Py_DECREF(obj);
+ Py_DECREF(name);
+ return result;
+}
+
static int
AttributeError_traverse(PyObject *op, visitproc visit, void *arg)
{
ComplexExtendsException(PyExc_Exception, AttributeError,
AttributeError, 0,
AttributeError_methods, AttributeError_members,
- 0, BaseException_str, 0, "Attribute not found.");
+ 0, AttributeError_str, 0, "Attribute not found.");
/*
* SyntaxError extends Exception