Skip to content

Commit

Permalink
pythongh-95754: Better AttributeError on partially initialised module
Browse files Browse the repository at this point in the history
  • Loading branch information
hauntsaninja committed Dec 1, 2023
1 parent e44f194 commit 3e339ae
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 2 deletions.
8 changes: 8 additions & 0 deletions Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1629,6 +1629,14 @@ def test_circular_from_import(self):
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
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_import/data/circular_imports/import_cycle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import test.test_import.data.circular_imports.import_cycle as m

m.some_attribute
15 changes: 13 additions & 2 deletions Objects/moduleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,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;
Expand Down Expand Up @@ -841,11 +841,22 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
}
if (suppress != 1) {
if (_PyModuleSpec_IsInitializing(spec)) {
PyErr_Format(PyExc_AttributeError,
origin = PyObject_GetAttr(spec, &_Py_ID(origin));
if (origin == NULL || origin == Py_None) {
PyErr_Format(PyExc_AttributeError,
"partially initialized "
"module '%U' has no attribute '%U' "
"(most likely due to a circular import)",
mod_name, name);
}
else {
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_XDECREF(origin);
}
else if (_PyModuleSpec_IsUninitializedSubmodule(spec, name)) {
PyErr_Format(PyExc_AttributeError,
Expand Down

0 comments on commit 3e339ae

Please sign in to comment.