gh-95754: Better AttributeError on partially initialised module (#112577)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
Shantanu 2023-12-21 13:24:10 -08:00 committed by GitHub
parent 2d91409c69
commit 61e8184095
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 2 deletions

View 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

View File

@ -0,0 +1,3 @@
import test.test_import.data.circular_imports.import_cycle as m
m.some_attribute

View File

@ -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.

View 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);