diff options
author | Jim Mussared <jim.mussared@gmail.com> | 2023-05-10 13:22:54 +1000 |
---|---|---|
committer | Damien George <damien@micropython.org> | 2023-06-01 16:21:37 +1000 |
commit | ed90f30dd5d39e958ce99c041f1dc1f9dbb63bb2 (patch) | |
tree | 9394f2480b961b4e6836dda72180eb36e27d6030 | |
parent | 525557738cccb73d7b00d2048b9fd47e4caeeec6 (diff) |
py/builtinimport: Allow builtin modules to be packages.
To use this:
- Create a built-in module, and add the module object as a member of the
parent module's globals dict.
- The submodule can set its `__name__` to either `QSTR_foo_dot_bar` or
`QSTR_bar`. The former requires using qstrdefs(port).h to make the qstr.
Because `bar` is a member of `foo`'s globals, it is possible to write
`import foo` and then immediately use `foo.bar` without importing it
explicitly. This means that if `bar` has an `__init__`, it will not be
called in this situation, and for that reason, sub-modules should not have
`__init__` methods. If this is required, then all initalisation for
sub-modules should be done by the top-level module's (i.e. `foo`'s)
`__init__` method.
This work was funded through GitHub Sponsors.
Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
-rw-r--r-- | py/builtinimport.c | 14 | ||||
-rw-r--r-- | py/mpconfig.h | 14 |
2 files changed, 28 insertions, 0 deletions
diff --git a/py/builtinimport.c b/py/builtinimport.c index d8127a66f..f6c2f7c34 100644 --- a/py/builtinimport.c +++ b/py/builtinimport.c @@ -428,6 +428,20 @@ STATIC mp_obj_t process_import_at_level(qstr full_mod_name, qstr level_mod_name, } else { DEBUG_printf("Searching for sub-module\n"); + #if MICROPY_MODULE_BUILTIN_SUBPACKAGES + // If the outer module is a built-in (because its map is in ROM), then + // treat it like a package if it contains this submodule in its + // globals dict. + mp_obj_module_t *mod = MP_OBJ_TO_PTR(outer_module_obj); + if (mod->globals->map.is_fixed) { + elem = mp_map_lookup(&mod->globals->map, MP_OBJ_NEW_QSTR(level_mod_name), MP_MAP_LOOKUP); + // Also verify that the entry in the globals dict is in fact a module. + if (elem && mp_obj_is_type(elem->value, &mp_type_module)) { + return elem->value; + } + } + #endif + // If the outer module is a package, it will have __path__ set. // We can use that as the path to search inside. mp_obj_t dest[2]; diff --git a/py/mpconfig.h b/py/mpconfig.h index f3d5ce557..292fafb52 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -850,6 +850,20 @@ typedef double mp_float_t; #define MICROPY_MODULE_BUILTIN_INIT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) #endif +// Whether to allow built-in modules to have sub-packages (by making the +// sub-package a member of their locals dict). Sub-packages should not be +// registered with MP_REGISTER_MODULE, instead they should be added as +// members of the parent's globals dict. To match CPython behavior, +// their __name__ should be "foo.bar"(i.e. QSTR_foo_dot_bar) which will +// require an entry in qstrdefs, although it does also work to just call +// it "bar". Also, because subpackages can be accessed without being +// imported (e.g. as foo.bar after `import foo`), they should not +// have __init__ methods. Instead, the top-level package's __init__ should +// initialise all sub-packages. +#ifndef MICROPY_MODULE_BUILTIN_SUBPACKAGES +#define MICROPY_MODULE_BUILTIN_SUBPACKAGES (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING) +#endif + // Whether to support module-level __getattr__ (see PEP 562) #ifndef MICROPY_MODULE_GETATTR #define MICROPY_MODULE_GETATTR (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES) |