Move __lazy_imports__ check into the interpreter

This commit is contained in:
Dino Viehland 2025-10-02 12:17:36 -07:00
parent 39c33df51a
commit c8c8838b1c
4 changed files with 89 additions and 46 deletions

View file

@ -34,6 +34,8 @@ extern int _PyImport_FixupBuiltin(
extern PyObject *
_PyImport_ResolveName(PyThreadState *tstate, PyObject *name, PyObject *globals, int level);
extern PyObject *
_PyImport_GetAbsName(PyThreadState *tstate, PyObject *name, PyObject *globals, int level);
extern PyObject *
_PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import);
extern PyObject *
_PyImport_LazyImportModuleLevelObject(PyThreadState *tstate, PyObject *name, PyObject *builtins, PyObject *globals,

View file

@ -2622,6 +2622,14 @@ def test_compatibility_mode(self):
self.assertFalse("test.test_import.data.lazy_imports.basic2" in sys.modules)
def test_compatibility_mode_used(self):
try:
import test.test_import.data.lazy_imports.basic_compatibility_mode_used
except ImportError as e:
self.fail('lazy import failed')
self.assertTrue("test.test_import.data.lazy_imports.basic2" in sys.modules)
def test_compatibility_mode_relative(self):
try:
import test.test_import.data.lazy_imports.basic_compatibility_mode_relative

View file

@ -2986,10 +2986,81 @@ _PyEval_SliceIndexNotNone(PyObject *v, Py_ssize_t *pi)
return 1;
}
int
check_lazy_import_comatibility(PyThreadState *tstate, PyObject *lazy_modules,
PyObject *builtins, PyObject *globals, PyObject *locals,
PyObject *name, PyObject *fromlist, PyObject *level,
PyObject **mod)
{
int ilevel = PyLong_AsInt(level);
if (ilevel == -1 && _PyErr_Occurred(tstate)) {
return -1;
}
PyObject *abs_name = _PyImport_GetAbsName(tstate, name, globals, ilevel);
if (abs_name == NULL) {
return -1;
}
int contains = PySequence_Contains(lazy_modules, abs_name);
Py_DECREF(abs_name);
if (contains < 0) {
return -1;
} else if (contains == 0) {
*mod = NULL;
return 0;
}
_PyInterpreterFrame *frame = _PyEval_GetFrame();
if (frame == NULL) {
PyErr_SetString(PyExc_RuntimeError, "no current frame");
return -1;
}
PyObject *import_func;
if (PyMapping_GetOptionalItem(frame->f_builtins, &_Py_ID(__import__), &import_func) < 0) {
return -1;
}
if (import_func == NULL) {
_PyErr_SetString(tstate, PyExc_ImportError, "__import__ not found");
return -1;
}
PyObject *final_mod = _PyImport_LazyImportModuleLevelObject(tstate, name, import_func, globals,
locals, fromlist, ilevel);
Py_DECREF(import_func);
if (final_mod == NULL) {
return -1;
}
*mod = final_mod;
return 0;
}
PyObject *
_PyEval_ImportName(PyThreadState *tstate, PyObject *builtins, PyObject *globals, PyObject *locals,
PyObject *name, PyObject *fromlist, PyObject *level)
{
// Check if this module should be imported lazily due to the compatbility mode support via
// __lazy_modules__.
PyObject *lazy_modules;
if (globals != NULL &&
PyMapping_GetOptionalItem(globals, &_Py_ID(__lazy_modules__), &lazy_modules) < 0) {
return NULL;
}
PyObject *res;
if (lazy_modules != NULL) {
int lazy = check_lazy_import_comatibility(tstate, lazy_modules, builtins, globals,
locals, name, fromlist, level, &res);
Py_DECREF(lazy_modules);
if (lazy < 0) {
return NULL;
} else if (res != NULL) {
return res;
}
}
PyObject *import_func;
if (PyMapping_GetOptionalItem(builtins, &_Py_ID(__import__), &import_func) < 0) {
return NULL;
@ -2999,7 +3070,7 @@ _PyEval_ImportName(PyThreadState *tstate, PyObject *builtins, PyObject *globals,
return NULL;
}
PyObject *res = _PyEval_ImportNameWithImport(tstate, import_func, globals, locals, name, fromlist, level);
res = _PyEval_ImportNameWithImport(tstate, import_func, globals, locals, name, fromlist, level);
Py_DECREF(import_func);
return res;
}

View file

@ -3956,6 +3956,13 @@ get_abs_name(PyThreadState *tstate, PyObject *name, PyObject *globals, int level
return Py_NewRef(name);
}
PyObject *
_PyImport_GetAbsName(PyThreadState *tstate, PyObject *name, PyObject *globals, int level)
{
return get_abs_name(tstate, name, globals, level);
}
PyObject *
PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
PyObject *locals, PyObject *fromlist,
@ -3975,11 +3982,6 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
goto error;
}
if (globals != NULL &&
PyMapping_GetOptionalItem(globals, &_Py_ID(__lazy_modules__), &lazy_modules) < 0) {
goto error;
}
/* The below code is importlib.__import__() & _gcd_import(), ported to C
for added performance. */
@ -3998,44 +4000,6 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
goto error;
}
if (lazy_modules != NULL) {
// Check and see if the module is opting in w/o syntax for backwards compatibility
// with older Python versions.
int contains = PySequence_Contains(lazy_modules, abs_name);
if (contains < 0) {
goto error;
} else if (contains == 1) {
// Don't create lazy import if we're already resolving a lazy import
if (interp->imports.lazy_importing_modules != NULL &&
PySet_GET_SIZE(interp->imports.lazy_importing_modules) > 0) {
contains = 0; // Skip lazy import creation
}
}
if (contains == 1) {
_PyInterpreterFrame *frame = _PyEval_GetFrame();
if (frame == NULL) {
PyErr_SetString(PyExc_RuntimeError, "no current frame");
goto error;
}
PyObject *import_func;
if (PyMapping_GetOptionalItem(frame->f_builtins, &_Py_ID(__import__), &import_func) < 0) {
return NULL;
}
if (import_func == NULL) {
_PyErr_SetString(tstate, PyExc_ImportError, "__import__ not found");
return NULL;
}
final_mod = _PyImport_LazyImportModuleLevelObject(tstate, name, import_func, globals,
locals, fromlist, level);
Py_DECREF(import_func);
goto error;
}
}
mod = import_get_module(tstate, abs_name);
if (mod == NULL && _PyErr_Occurred(tstate)) {
goto error;
@ -4219,7 +4183,6 @@ register_lazy_on_parent(PyThreadState *tstate, PyObject *name, PyObject *import_
goto done;
}
if (PyDict_CheckExact(parent_dict) && !PyDict_Contains(parent_dict, child)) {
printf("!!! Adding lazy onto %s %s\n", PyUnicode_AsUTF8(parent), PyUnicode_AsUTF8(child));
PyObject *lazy_module_attr = _PyLazyImport_New(import_func, parent, child);
if (lazy_module_attr == NULL) {
goto done;
@ -5344,7 +5307,6 @@ _imp__set_lazy_attributes_impl(PyObject *module, PyObject *child_module,
Py_hash_t hash;
while (_PySet_NextEntry(lazy_submodules, &pos, &attr_name, &hash)) {
if (PyDict_Contains(child_dict, attr_name)) {
printf("!!!!!!!! Not replacing %s\n", PyUnicode_AsUTF8(attr_name));
continue;
}
PyObject *builtins = _PyEval_GetBuiltins(tstate);