]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-95754: Better AttributeError on partially initialised module (#112577)
authorShantanu <12621235+hauntsaninja@users.noreply.github.com>
Thu, 21 Dec 2023 21:24:10 +0000 (13:24 -0800)
committerGitHub <noreply@github.com>
Thu, 21 Dec 2023 21:24:10 +0000 (13:24 -0800)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Lib/test/test_import/__init__.py
Lib/test/test_import/data/circular_imports/import_cycle.py [new file with mode: 0644]
Misc/NEWS.d/next/Core and Builtins/2023-12-01-08-16-10.gh-issue-95754.ae4gwy.rst [new file with mode: 0644]
Objects/moduleobject.c

index 48c0a43f29e27f8c58fef0203abe4dd2fb8ad410..7b0126226c4aba21ab921a18418ac1d9955e830b 100644 (file)
@@ -1632,6 +1632,14 @@ class CircularImportTests(unittest.TestCase):
             str(cm.exception),
         )
 
+    def test_circular_import(self):
+        with self.assertRaisesRegex(
+            AttributeError,
+            r"partially initialized module 'test.test_import.data.circular_imports.import_cycle' "
+            r"from '.*' has no attribute 'some_attribute' \(most likely due to a circular import\)"
+        ):
+            import test.test_import.data.circular_imports.import_cycle
+
     def test_absolute_circular_submodule(self):
         with self.assertRaises(AttributeError) as cm:
             import test.test_import.data.circular_imports.subpkg2.parent
diff --git a/Lib/test/test_import/data/circular_imports/import_cycle.py b/Lib/test/test_import/data/circular_imports/import_cycle.py
new file mode 100644 (file)
index 0000000..cd9507b
--- /dev/null
@@ -0,0 +1,3 @@
+import test.test_import.data.circular_imports.import_cycle as m
+
+m.some_attribute
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-01-08-16-10.gh-issue-95754.ae4gwy.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-01-08-16-10.gh-issue-95754.ae4gwy.rst
new file mode 100644 (file)
index 0000000..0884bc4
--- /dev/null
@@ -0,0 +1 @@
+Provide a better error message when accessing invalid attributes on partially initialized modules. The origin of the module being accessed is now included in the message to help with the common issue of shadowing other modules.
index e2741fef6debd3e6da7469d4b6305cb417cee651..3a1c516658dce784aa9a81f9c5881dfaa6e2d913 100644 (file)
@@ -788,7 +788,7 @@ PyObject*
 _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
 {
     // When suppress=1, this function suppresses AttributeError.
-    PyObject *attr, *mod_name, *getattr;
+    PyObject *attr, *mod_name, *getattr, *origin;
     attr = _PyObject_GenericGetAttrWithDict((PyObject *)m, name, NULL, suppress);
     if (attr) {
         return attr;
@@ -831,11 +831,31 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
         if (suppress != 1) {
             int rc = _PyModuleSpec_IsInitializing(spec);
             if (rc > 0) {
-                PyErr_Format(PyExc_AttributeError,
+                int valid_spec = PyObject_GetOptionalAttr(spec, &_Py_ID(origin), &origin);
+                if (valid_spec == -1) {
+                    Py_XDECREF(spec);
+                    Py_DECREF(mod_name);
+                    return NULL;
+                }
+                if (valid_spec == 1 && !PyUnicode_Check(origin)) {
+                    valid_spec = 0;
+                    Py_DECREF(origin);
+                }
+                if (valid_spec == 1) {
+                    PyErr_Format(PyExc_AttributeError,
+                                "partially initialized "
+                                "module '%U' from '%U' has no attribute '%U' "
+                                "(most likely due to a circular import)",
+                                mod_name, origin, name);
+                    Py_DECREF(origin);
+                }
+                else {
+                    PyErr_Format(PyExc_AttributeError,
                                 "partially initialized "
                                 "module '%U' has no attribute '%U' "
                                 "(most likely due to a circular import)",
                                 mod_name, name);
+                }
             }
             else if (rc == 0) {
                 rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name);