mirror of https://github.com/python/cpython
bpo-33237: Improve AttributeError message for partially initialized module. (GH-6398)
This commit is contained in:
parent
95b6acf951
commit
3e429dcc24
|
@ -30,6 +30,7 @@ PyAPI_FUNC(PyObject *) PyModule_GetFilenameObject(PyObject *);
|
||||||
#ifndef Py_LIMITED_API
|
#ifndef Py_LIMITED_API
|
||||||
PyAPI_FUNC(void) _PyModule_Clear(PyObject *);
|
PyAPI_FUNC(void) _PyModule_Clear(PyObject *);
|
||||||
PyAPI_FUNC(void) _PyModule_ClearDict(PyObject *);
|
PyAPI_FUNC(void) _PyModule_ClearDict(PyObject *);
|
||||||
|
PyAPI_FUNC(int) _PyModuleSpec_IsInitializing(PyObject *);
|
||||||
#endif
|
#endif
|
||||||
PyAPI_FUNC(struct PyModuleDef*) PyModule_GetDef(PyObject*);
|
PyAPI_FUNC(struct PyModuleDef*) PyModule_GetDef(PyObject*);
|
||||||
PyAPI_FUNC(void*) PyModule_GetState(PyObject*);
|
PyAPI_FUNC(void*) PyModule_GetState(PyObject*);
|
||||||
|
|
|
@ -1271,6 +1271,19 @@ class CircularImportTests(unittest.TestCase):
|
||||||
except ImportError:
|
except ImportError:
|
||||||
self.fail('circular import with binding a submodule to a name failed')
|
self.fail('circular import with binding a submodule to a name failed')
|
||||||
|
|
||||||
|
def test_crossreference1(self):
|
||||||
|
import test.test_import.data.circular_imports.use
|
||||||
|
import test.test_import.data.circular_imports.source
|
||||||
|
|
||||||
|
def test_crossreference2(self):
|
||||||
|
with self.assertRaises(AttributeError) as cm:
|
||||||
|
import test.test_import.data.circular_imports.source
|
||||||
|
errmsg = str(cm.exception)
|
||||||
|
self.assertIn('test.test_import.data.circular_imports.source', errmsg)
|
||||||
|
self.assertIn('spam', errmsg)
|
||||||
|
self.assertIn('partially initialized module', errmsg)
|
||||||
|
self.assertIn('circular import', errmsg)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Test needs to be a package, so we can do relative imports.
|
# Test needs to be a package, so we can do relative imports.
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
from . import use
|
||||||
|
spam = 1
|
|
@ -0,0 +1,2 @@
|
||||||
|
from . import source
|
||||||
|
source.spam
|
|
@ -0,0 +1 @@
|
||||||
|
Improved :exc:`AttributeError` message for partially initialized module.
|
|
@ -698,6 +698,27 @@ module_repr(PyModuleObject *m)
|
||||||
return PyObject_CallMethod(interp->importlib, "_module_repr", "O", m);
|
return PyObject_CallMethod(interp->importlib, "_module_repr", "O", m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check if the "_initializing" attribute of the module spec is set to true.
|
||||||
|
Clear the exception and return 0 if spec is NULL.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
_PyModuleSpec_IsInitializing(PyObject *spec)
|
||||||
|
{
|
||||||
|
if (spec != NULL) {
|
||||||
|
_Py_IDENTIFIER(_initializing);
|
||||||
|
PyObject *value = _PyObject_GetAttrId(spec, &PyId__initializing);
|
||||||
|
if (value != NULL) {
|
||||||
|
int initializing = PyObject_IsTrue(value);
|
||||||
|
Py_DECREF(value);
|
||||||
|
if (initializing >= 0) {
|
||||||
|
return initializing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PyErr_Clear();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
module_getattro(PyModuleObject *m, PyObject *name)
|
module_getattro(PyModuleObject *m, PyObject *name)
|
||||||
{
|
{
|
||||||
|
@ -717,8 +738,24 @@ module_getattro(PyModuleObject *m, PyObject *name)
|
||||||
_Py_IDENTIFIER(__name__);
|
_Py_IDENTIFIER(__name__);
|
||||||
mod_name = _PyDict_GetItemId(m->md_dict, &PyId___name__);
|
mod_name = _PyDict_GetItemId(m->md_dict, &PyId___name__);
|
||||||
if (mod_name && PyUnicode_Check(mod_name)) {
|
if (mod_name && PyUnicode_Check(mod_name)) {
|
||||||
|
_Py_IDENTIFIER(__spec__);
|
||||||
|
Py_INCREF(mod_name);
|
||||||
|
PyObject *spec = _PyDict_GetItemId(m->md_dict, &PyId___spec__);
|
||||||
|
Py_XINCREF(spec);
|
||||||
|
if (_PyModuleSpec_IsInitializing(spec)) {
|
||||||
PyErr_Format(PyExc_AttributeError,
|
PyErr_Format(PyExc_AttributeError,
|
||||||
"module '%U' has no attribute '%U'", mod_name, name);
|
"partially initialized "
|
||||||
|
"module '%U' has no attribute '%U' "
|
||||||
|
"(most likely due to a circular import)",
|
||||||
|
mod_name, name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyErr_Format(PyExc_AttributeError,
|
||||||
|
"module '%U' has no attribute '%U'",
|
||||||
|
mod_name, name);
|
||||||
|
}
|
||||||
|
Py_XDECREF(spec);
|
||||||
|
Py_DECREF(mod_name);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1721,11 +1721,8 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
|
||||||
mod = PyImport_GetModule(abs_name);
|
mod = PyImport_GetModule(abs_name);
|
||||||
if (mod != NULL && mod != Py_None) {
|
if (mod != NULL && mod != Py_None) {
|
||||||
_Py_IDENTIFIER(__spec__);
|
_Py_IDENTIFIER(__spec__);
|
||||||
_Py_IDENTIFIER(_initializing);
|
|
||||||
_Py_IDENTIFIER(_lock_unlock_module);
|
_Py_IDENTIFIER(_lock_unlock_module);
|
||||||
PyObject *value = NULL;
|
|
||||||
PyObject *spec;
|
PyObject *spec;
|
||||||
int initializing = 0;
|
|
||||||
|
|
||||||
/* Optimization: only call _bootstrap._lock_unlock_module() if
|
/* Optimization: only call _bootstrap._lock_unlock_module() if
|
||||||
__spec__._initializing is true.
|
__spec__._initializing is true.
|
||||||
|
@ -1733,26 +1730,17 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
|
||||||
stuffing the new module in sys.modules.
|
stuffing the new module in sys.modules.
|
||||||
*/
|
*/
|
||||||
spec = _PyObject_GetAttrId(mod, &PyId___spec__);
|
spec = _PyObject_GetAttrId(mod, &PyId___spec__);
|
||||||
if (spec != NULL) {
|
if (_PyModuleSpec_IsInitializing(spec)) {
|
||||||
value = _PyObject_GetAttrId(spec, &PyId__initializing);
|
PyObject *value = _PyObject_CallMethodIdObjArgs(interp->importlib,
|
||||||
Py_DECREF(spec);
|
|
||||||
}
|
|
||||||
if (value == NULL)
|
|
||||||
PyErr_Clear();
|
|
||||||
else {
|
|
||||||
initializing = PyObject_IsTrue(value);
|
|
||||||
Py_DECREF(value);
|
|
||||||
if (initializing == -1)
|
|
||||||
PyErr_Clear();
|
|
||||||
if (initializing > 0) {
|
|
||||||
value = _PyObject_CallMethodIdObjArgs(interp->importlib,
|
|
||||||
&PyId__lock_unlock_module, abs_name,
|
&PyId__lock_unlock_module, abs_name,
|
||||||
NULL);
|
NULL);
|
||||||
if (value == NULL)
|
if (value == NULL) {
|
||||||
|
Py_DECREF(spec);
|
||||||
goto error;
|
goto error;
|
||||||
|
}
|
||||||
Py_DECREF(value);
|
Py_DECREF(value);
|
||||||
}
|
}
|
||||||
}
|
Py_XDECREF(spec);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Py_XDECREF(mod);
|
Py_XDECREF(mod);
|
||||||
|
|
Loading…
Reference in New Issue