Skip to content

Commit

Permalink
py/builtinimport: Allow builtin modules to be packages.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
jimmo authored and dpgeorge committed Jun 1, 2023
1 parent 5255577 commit ed90f30
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 0 deletions.
14 changes: 14 additions & 0 deletions py/builtinimport.c
Expand Up @@ -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];
Expand Down
14 changes: 14 additions & 0 deletions py/mpconfig.h
Expand Up @@ -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)
Expand Down

0 comments on commit ed90f30

Please sign in to comment.