[3.15] gh-154196: Improve `AttributeError` messages from unresolved lazy imports (GH-154688) (#155899)

pablogsal <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/57dface7588ad67fc8ca69aec8c019f4cce10031
commit: 57dface7588ad67fc8ca69aec8c019f4cce10031
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-08-19T11:02:15+01:00
summary:

[3.15] gh-154196: Improve `AttributeError` messages from unresolved lazy imports (GH-154688) (#155899)

files:
A Misc/NEWS.d/next/Core_and_Builtins/2026-07-25-12-43-42.gh-issue-154196.0rAdob.rst
M Lib/test/test_lazy_import/__init__.py
M Objects/lazyimportobject.c

diff --git a/Lib/test/test_lazy_import/__init__.py b/Lib/test/test_lazy_import/__init__.py
index 6951a67d309bf9..d673ba5223cba8 100644
--- a/Lib/test/test_lazy_import/__init__.py
+++ b/Lib/test/test_lazy_import/__init__.py
@@ -285,6 +285,23 @@ def test_lazy_import_type_attributes_accessible(self):
         proc = assert_python_ok("-c", code)
         self.assertIn(b"<built-in method resolve of lazy_import object at", proc.out)
 
+    @support.requires_subprocess()
+    def test_lazy_import_type_attribute_error_message(self):
+        """Check that LazyImportType attribute error message is helpful."""
+        code = textwrap.dedent("""
+            lazy import asyncio
+            try:
+                globals()["asyncio"].Task
+            except AttributeError as exc:
+                assert str(exc) == (
+                    "cannot access attribute 'Task' "
+                    "on unresolved lazy import 'asyncio'"
+                ), repr(str(exc))
+            else:
+                assert False, 'AttributeError is not raised'
+        """)
+        assert_python_ok("-c", code)
+
 
 class SyntaxRestrictionTests(LazyImportTestCase):
     """Tests for syntax restrictions on lazy imports."""
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-25-12-43-42.gh-issue-154196.0rAdob.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-25-12-43-42.gh-issue-154196.0rAdob.rst
new file mode 100644
index 00000000000000..8f55e76a5c0dcd
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-25-12-43-42.gh-issue-154196.0rAdob.rst
@@ -0,0 +1,2 @@
+Improve :exc:`AttributeError` messages from unresolved lazy imports. Patch
+by Bartosz Sławecki.
diff --git a/Objects/lazyimportobject.c b/Objects/lazyimportobject.c
index fa1eb25047d961..8f7f3f98c29128 100644
--- a/Objects/lazyimportobject.c
+++ b/Objects/lazyimportobject.c
@@ -81,6 +81,29 @@ lazy_import_dealloc(PyObject *op)
     Py_TYPE(op)->tp_free(op);
 }
 
+/* Specialize the error message for failed attribute lookups. */
+static PyObject *
+lazy_import_getattro(PyObject *op, PyObject *name)
+{
+    PyObject *value = _PyObject_GenericGetAttrWithDict(op, name, NULL, /* suppress */1);
+    if (value == NULL) {
+        if (PyErr_Occurred()) {
+            // pass up non-AttributeError exception
+            return NULL;
+        }
+        PyObject *lz_name = _PyLazyImport_GetName(op);
+        if (lz_name == NULL) {
+            return NULL;
+        }
+        PyErr_Format(PyExc_AttributeError,
+                     "cannot access attribute %R on unresolved lazy import %R",
+                     name, lz_name);
+        Py_DECREF(lz_name);
+        return NULL;
+    }
+    return value;
+}
+
 static PyObject *
 lazy_import_name(PyLazyImportObject *m)
 {
@@ -149,6 +172,7 @@ PyTypeObject PyLazyImport_Type = {
     .tp_repr = lazy_import_repr,
     .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
     .tp_doc = lazy_import_doc,
+    .tp_getattro = lazy_import_getattro,
     .tp_traverse = lazy_import_traverse,
     .tp_clear = lazy_import_clear,
     .tp_methods = lazy_import_methods,

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.