From 41ab0924071f82c97ac8d5461ce6a077d7a0055a Mon Sep 17 00:00:00 2001 From: Dino Viehland Date: Mon, 22 Sep 2025 06:43:55 -0700 Subject: [PATCH] Add compatiblity mode --- .../pycore_global_objects_fini_generated.h | 2 +- Include/internal/pycore_global_strings.h | 2 +- .../internal/pycore_runtime_init_generated.h | 2 +- .../internal/pycore_unicodeobject_generated.h | 8 +++--- Lib/test/test_import/__init__.py | 17 +++++++++++++ .../lazy_imports/basic_compatibility_mode.py | 2 ++ .../basic_compatibility_mode_relative.py | 2 ++ Python/import.c | 25 +++++++++++++++++++ 8 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 Lib/test/test_import/data/lazy_imports/basic_compatibility_mode.py create mode 100644 Lib/test/test_import/data/lazy_imports/basic_compatibility_mode_relative.py diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index df3287aa21b..cf436ffb466 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1433,6 +1433,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__itruediv__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__ixor__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__lazy_import__)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__lazy_modules__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__le__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__len__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__length_hint__)); @@ -1851,7 +1852,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(last_type)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(last_value)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(latin1)); - _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(lazy)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(leaf_size)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(legacy)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(len)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 337fff915b5..c11dc8e98f4 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -156,6 +156,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(__itruediv__) STRUCT_FOR_ID(__ixor__) STRUCT_FOR_ID(__lazy_import__) + STRUCT_FOR_ID(__lazy_modules__) STRUCT_FOR_ID(__le__) STRUCT_FOR_ID(__len__) STRUCT_FOR_ID(__length_hint__) @@ -574,7 +575,6 @@ struct _Py_global_strings { STRUCT_FOR_ID(last_type) STRUCT_FOR_ID(last_value) STRUCT_FOR_ID(latin1) - STRUCT_FOR_ID(lazy) STRUCT_FOR_ID(leaf_size) STRUCT_FOR_ID(legacy) STRUCT_FOR_ID(len) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 989013d1dea..bfe4302f5b5 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1431,6 +1431,7 @@ extern "C" { INIT_ID(__itruediv__), \ INIT_ID(__ixor__), \ INIT_ID(__lazy_import__), \ + INIT_ID(__lazy_modules__), \ INIT_ID(__le__), \ INIT_ID(__len__), \ INIT_ID(__length_hint__), \ @@ -1849,7 +1850,6 @@ extern "C" { INIT_ID(last_type), \ INIT_ID(last_value), \ INIT_ID(latin1), \ - INIT_ID(lazy), \ INIT_ID(leaf_size), \ INIT_ID(legacy), \ INIT_ID(len), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index bac3ac58e42..609570d8bed 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -412,6 +412,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(__lazy_modules__); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(__le__); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -2084,10 +2088,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); - string = &_Py_ID(lazy); - _PyUnicode_InternStatic(interp, &string); - assert(_PyUnicode_CheckConsistency(string, 1)); - assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(leaf_size); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index e6d95610fc2..5608a55c761 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -2614,6 +2614,23 @@ def test_global_filter_from_true(self): self.assertFalse("test.test_import.data.lazy_imports.basic2" in sys.modules) + def test_compatibility_mode(self): + try: + import test.test_import.data.lazy_imports.basic_compatibility_mode + except ImportError as e: + self.fail('lazy import failed') + + self.assertFalse("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 + except ImportError as e: + self.fail('lazy import failed') + + self.assertFalse("test.test_import.data.lazy_imports.basic2" in sys.modules) + + class TestSinglePhaseSnapshot(ModuleSnapshot): """A representation of a single-phase init module for testing. diff --git a/Lib/test/test_import/data/lazy_imports/basic_compatibility_mode.py b/Lib/test/test_import/data/lazy_imports/basic_compatibility_mode.py new file mode 100644 index 00000000000..5076fa4894e --- /dev/null +++ b/Lib/test/test_import/data/lazy_imports/basic_compatibility_mode.py @@ -0,0 +1,2 @@ +__lazy_modules__ = ['test.test_import.data.lazy_imports.basic2'] +import test.test_import.data.lazy_imports.basic2 diff --git a/Lib/test/test_import/data/lazy_imports/basic_compatibility_mode_relative.py b/Lib/test/test_import/data/lazy_imports/basic_compatibility_mode_relative.py new file mode 100644 index 00000000000..e37759348f3 --- /dev/null +++ b/Lib/test/test_import/data/lazy_imports/basic_compatibility_mode_relative.py @@ -0,0 +1,2 @@ +__lazy_modules__ = ['test.test_import.data.lazy_imports.basic2'] +lazy from .basic2 import f diff --git a/Python/import.c b/Python/import.c index fda8f30f2eb..ac03db2c821 100644 --- a/Python/import.c +++ b/Python/import.c @@ -3862,6 +3862,7 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals, PyObject *final_mod = NULL; PyObject *mod = NULL; PyObject *package = NULL; + PyObject *lazy_modules = NULL; PyInterpreterState *interp = tstate->interp; int has_from; @@ -3870,6 +3871,11 @@ 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. */ @@ -3888,6 +3894,24 @@ 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, name); + if (contains < 0) { + goto error; + } else if (contains == 1) { + _PyInterpreterFrame *frame = _PyEval_GetFrame(); + if (frame == NULL) { + PyErr_SetString(PyExc_RuntimeError, "no current frame"); + goto error; + } + final_mod = _PyImport_LazyImportModuleLevelObject(tstate, name, frame->f_builtins, globals, + locals, fromlist, level); + goto error; + } + } + mod = import_get_module(tstate, abs_name); if (mod == NULL && _PyErr_Occurred(tstate)) { goto error; @@ -3980,6 +4004,7 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals, } error: + Py_XDECREF(lazy_modules); Py_XDECREF(abs_name); Py_XDECREF(mod); Py_XDECREF(package);