mirror of https://github.com/python/cpython
gh-95754: Better AttributeError on partially initialised module (#112577)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
2d91409c69
commit
61e8184095
|
@ -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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import test.test_import.data.circular_imports.import_cycle as m
|
||||
|
||||
m.some_attribute
|
|
@ -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.
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue