From 56391c0baf49fa2748124626360c4c238174e3d7 Mon Sep 17 00:00:00 2001 From: Chris Liechti Date: Mon, 30 Nov 2020 16:40:56 +0100 Subject: [PATCH 1/4] py/builtinimport: support relative import in custom __import__ callbacks The globals need to be forwarded from the callers context. --- py/builtinimport.c | 21 +++++++++++++++----- py/runtime.c | 2 +- tests/import/import_override2.py | 29 ++++++++++++++++++++++++++++ tests/import/import_override2.py.exp | 15 ++++++++++++++ 4 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 tests/import/import_override2.py create mode 100644 tests/import/import_override2.py.exp diff --git a/py/builtinimport.c b/py/builtinimport.c index ff894f69d99e2..183a8b8fd50ca 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -267,7 +267,7 @@ static void do_load(mp_module_context_t *module_obj, vstr_t *file) { // Convert a relative (to the current module) import, going up "level" levels, // into an absolute import. -static void evaluate_relative_import(mp_int_t level, const char **module_name, size_t *module_name_len) { +static void evaluate_relative_import(mp_int_t level, const char **module_name, size_t *module_name_len, mp_obj_t globals) { // What we want to do here is to take the name of the current module, // remove trailing components, and concatenate the passed-in // module name. @@ -276,7 +276,7 @@ static void evaluate_relative_import(mp_int_t level, const char **module_name, s // module's position in the package hierarchy." // http://legacy.python.org/dev/peps/pep-0328/#relative-imports-and-name - mp_obj_t current_module_name_obj = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___name__)); + mp_obj_t current_module_name_obj = mp_obj_dict_get(globals, MP_OBJ_NEW_QSTR(MP_QSTR___name__)); assert(current_module_name_obj != MP_OBJ_NULL); #if MICROPY_MODULE_OVERRIDE_MAIN_IMPORT && MICROPY_CPYTHON_COMPAT @@ -284,12 +284,12 @@ static void evaluate_relative_import(mp_int_t level, const char **module_name, s // This is a module loaded by -m command-line switch (e.g. unix port), // and so its __name__ has been set to "__main__". Get its real name // that we stored during import in the __main__ attribute. - current_module_name_obj = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); + current_module_name_obj = mp_obj_dict_get(globals, MP_OBJ_NEW_QSTR(MP_QSTR___main__)); } #endif // If we have a __path__ in the globals dict, then we're a package. - bool is_pkg = mp_map_lookup(&mp_globals_get()->map, MP_OBJ_NEW_QSTR(MP_QSTR___path__), MP_MAP_LOOKUP); + bool is_pkg = mp_map_lookup(mp_obj_dict_get_map(globals), MP_OBJ_NEW_QSTR(MP_QSTR___path__), MP_MAP_LOOKUP); #if DEBUG_PRINT DEBUG_printf("Current module/package: "); @@ -546,6 +546,17 @@ mp_obj_t mp_builtin___import___default(size_t n_args, const mp_obj_t *args) { // "from ...foo.bar import baz" --> module_name="foo.bar" mp_obj_t module_name_obj = args[0]; + // This is the dict with all global symbols. + mp_obj_t globals = mp_const_none; + if (n_args >= 2) { + globals = args[1]; + } + if (globals == mp_const_none) { + globals = MP_OBJ_FROM_PTR(mp_globals_get()); + } else if (!mp_obj_is_type(globals, &mp_type_dict)) { + mp_raise_TypeError(MP_ERROR_TEXT("globals must be dict")); + } + // These are the imported names. // i.e. "from foo.bar import baz, zap" --> fromtuple=("baz", "zap",) // Note: There's a special case on the Unix port, where this is set to mp_const_false which means that it's __main__. @@ -571,8 +582,8 @@ mp_obj_t mp_builtin___import___default(size_t n_args, const mp_obj_t *args) { if (level != 0) { // Turn "foo.bar" with level=3 into ".foo.bar". // Current module name is extracted from globals().__name__. - evaluate_relative_import(level, &module_name, &module_name_len); // module_name is now an absolute module path. + evaluate_relative_import(level, &module_name, &module_name_len, globals); } if (module_name_len == 0) { diff --git a/py/runtime.c b/py/runtime.c index a84e22760be3b..ddc1ef7d3ead0 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -1522,7 +1522,7 @@ mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level) { // build args array mp_obj_t args[5]; args[0] = MP_OBJ_NEW_QSTR(name); - args[1] = mp_const_none; // TODO should be globals + args[1] = MP_OBJ_FROM_PTR(mp_globals_get()); // globals of the current context args[2] = mp_const_none; // TODO should be locals args[3] = fromlist; args[4] = level; diff --git a/tests/import/import_override2.py b/tests/import/import_override2.py new file mode 100644 index 0000000000000..2cd2da21ebd55 --- /dev/null +++ b/tests/import/import_override2.py @@ -0,0 +1,29 @@ +# test overriding __import__ combined with importing from the filesystem + + +def custom_import(name, globals, locals, fromlist, level): + print("import", name, fromlist, level) + return orig_import(name, globals, locals, fromlist, level) + + +orig_import = __import__ +try: + __import__("builtins").__import__ = custom_import +except AttributeError: + print("SKIP") + raise SystemExit + +# import calls __import__ behind the scenes +import pkg7.subpkg1.subpkg2.mod3 + + +try: + # globals must be a dict or None, not a string + orig_import("builtins", "globals", None, None, 0) +except TypeError: + print("TypeError") +try: + # ... same for relative imports (level > 0) + orig_import("builtins", "globals", None, None, 1) +except TypeError: + print("TypeError") diff --git a/tests/import/import_override2.py.exp b/tests/import/import_override2.py.exp new file mode 100644 index 0000000000000..99dd8d7ac2fcc --- /dev/null +++ b/tests/import/import_override2.py.exp @@ -0,0 +1,15 @@ +import pkg7.subpkg1.subpkg2.mod3 None 0 +pkg __name__: pkg7 +pkg __name__: pkg7.subpkg1 +pkg __name__: pkg7.subpkg1.subpkg2 +import ('mod1',) 3 +import pkg7.mod1 True 0 +mod1 +import mod2 ('bar',) 3 +mod2 +mod1.foo +mod2.bar +import ('mod1',) 4 +ImportError +TypeError +TypeError From 308705d6e6942acdc1707fd8557332474bbd6e19 Mon Sep 17 00:00:00 2001 From: Chris Liechti Date: Wed, 2 Dec 2020 00:38:30 +0100 Subject: [PATCH 2/4] extmod/asyncio: Support for overriden __import__. globals() needs to be provided in case __import__ is a Python function --- extmod/asyncio/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extmod/asyncio/__init__.py b/extmod/asyncio/__init__.py index 1f83750c5aafa..801af791414ed 100644 --- a/extmod/asyncio/__init__.py +++ b/extmod/asyncio/__init__.py @@ -26,6 +26,6 @@ def __getattr__(attr): mod = _attrs.get(attr, None) if mod is None: raise AttributeError(attr) - value = getattr(__import__(mod, None, None, True, 1), attr) + value = getattr(__import__(mod, globals(), None, True, 1), attr) globals()[attr] = value return value From f8d170b65c1d504617aa692261cd7cff80ab5d20 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 6 Nov 2025 13:00:05 +1100 Subject: [PATCH 3/4] py/builtinimport: Only parse globals if level>0. Following CPython behaviour. Signed-off-by: Damien George --- py/builtinimport.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/py/builtinimport.c b/py/builtinimport.c index 183a8b8fd50ca..2c7d796680fb6 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -546,17 +546,6 @@ mp_obj_t mp_builtin___import___default(size_t n_args, const mp_obj_t *args) { // "from ...foo.bar import baz" --> module_name="foo.bar" mp_obj_t module_name_obj = args[0]; - // This is the dict with all global symbols. - mp_obj_t globals = mp_const_none; - if (n_args >= 2) { - globals = args[1]; - } - if (globals == mp_const_none) { - globals = MP_OBJ_FROM_PTR(mp_globals_get()); - } else if (!mp_obj_is_type(globals, &mp_type_dict)) { - mp_raise_TypeError(MP_ERROR_TEXT("globals must be dict")); - } - // These are the imported names. // i.e. "from foo.bar import baz, zap" --> fromtuple=("baz", "zap",) // Note: There's a special case on the Unix port, where this is set to mp_const_false which means that it's __main__. @@ -580,6 +569,15 @@ mp_obj_t mp_builtin___import___default(size_t n_args, const mp_obj_t *args) { const char *module_name = mp_obj_str_get_data(module_name_obj, &module_name_len); if (level != 0) { + // This is the dict with all global symbols. + mp_obj_t globals = MP_OBJ_FROM_PTR(mp_globals_get()); + if (n_args >= 2 && args[1] != mp_const_none) { + globals = args[1]; + if (!mp_obj_is_type(globals, &mp_type_dict)) { + mp_raise_TypeError(NULL); + } + } + // Turn "foo.bar" with level=3 into ".foo.bar". // Current module name is extracted from globals().__name__. // module_name is now an absolute module path. From bd758b879e7f127a414f1e76e87098265bb6ab4d Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 6 Nov 2025 13:05:57 +1100 Subject: [PATCH 4/4] tests/import: Split out type test, remove .exp. Signed-off-by: Damien George --- tests/import/builtin_import.py | 9 +++++++++ tests/import/import_override2.py | 15 ++------------- tests/import/import_override2.py.exp | 15 --------------- 3 files changed, 11 insertions(+), 28 deletions(-) delete mode 100644 tests/import/import_override2.py.exp diff --git a/tests/import/builtin_import.py b/tests/import/builtin_import.py index 734498d1be47b..364b0bae912ae 100644 --- a/tests/import/builtin_import.py +++ b/tests/import/builtin_import.py @@ -20,3 +20,12 @@ __import__("xyz", None, None, None, -1) except ValueError: print("ValueError") + +# globals is not checked for level=0 +__import__("builtins", "globals") + +# globals must be a dict (or None) for level>0 +try: + __import__("builtins", "globals", None, None, 1) +except TypeError: + print("TypeError") diff --git a/tests/import/import_override2.py b/tests/import/import_override2.py index 2cd2da21ebd55..25aac44fe98d0 100644 --- a/tests/import/import_override2.py +++ b/tests/import/import_override2.py @@ -2,7 +2,8 @@ def custom_import(name, globals, locals, fromlist, level): - print("import", name, fromlist, level) + if level > 0: + print("import", name, fromlist, level) return orig_import(name, globals, locals, fromlist, level) @@ -15,15 +16,3 @@ def custom_import(name, globals, locals, fromlist, level): # import calls __import__ behind the scenes import pkg7.subpkg1.subpkg2.mod3 - - -try: - # globals must be a dict or None, not a string - orig_import("builtins", "globals", None, None, 0) -except TypeError: - print("TypeError") -try: - # ... same for relative imports (level > 0) - orig_import("builtins", "globals", None, None, 1) -except TypeError: - print("TypeError") diff --git a/tests/import/import_override2.py.exp b/tests/import/import_override2.py.exp deleted file mode 100644 index 99dd8d7ac2fcc..0000000000000 --- a/tests/import/import_override2.py.exp +++ /dev/null @@ -1,15 +0,0 @@ -import pkg7.subpkg1.subpkg2.mod3 None 0 -pkg __name__: pkg7 -pkg __name__: pkg7.subpkg1 -pkg __name__: pkg7.subpkg1.subpkg2 -import ('mod1',) 3 -import pkg7.mod1 True 0 -mod1 -import mod2 ('bar',) 3 -mod2 -mod1.foo -mod2.bar -import ('mod1',) 4 -ImportError -TypeError -TypeError