diff --git a/ports/unix/mpconfigport_coverage.h b/ports/unix/mpconfigport_coverage.h index 9e58f8aba..9ab442ff9 100644 --- a/ports/unix/mpconfigport_coverage.h +++ b/ports/unix/mpconfigport_coverage.h @@ -36,6 +36,7 @@ #define MICROPY_FLOAT_HIGH_QUALITY_HASH (1) #define MICROPY_ENABLE_SCHEDULER (1) #define MICROPY_READER_VFS (1) +#define MICROPY_MODULE_GETATTR (1) #define MICROPY_PY_DELATTR_SETATTR (1) #define MICROPY_PY_REVERSE_SPECIAL_METHODS (1) #define MICROPY_PY_BUILTINS_RANGE_BINOP (1) diff --git a/py/mpconfig.h b/py/mpconfig.h index 2e1efe7d5..10a373ce8 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -633,6 +633,11 @@ typedef double mp_float_t; #define MICROPY_MODULE_BUILTIN_INIT (0) #endif +// Whether to support module-level __getattr__ (see PEP 562) +#ifndef MICROPY_MODULE_GETATTR +#define MICROPY_MODULE_GETATTR (0) +#endif + // Whether module weak links are supported #ifndef MICROPY_MODULE_WEAK_LINKS #define MICROPY_MODULE_WEAK_LINKS (0) diff --git a/py/objmodule.c b/py/objmodule.c index 4e6f17541..3a00b7ddc 100644 --- a/py/objmodule.c +++ b/py/objmodule.c @@ -61,6 +61,13 @@ STATIC void module_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { mp_map_elem_t *elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); if (elem != NULL) { dest[0] = elem->value; + #if MICROPY_MODULE_GETATTR + } else if (attr != MP_QSTR___getattr__) { + elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(MP_QSTR___getattr__), MP_MAP_LOOKUP); + if (elem != NULL) { + dest[0] = mp_call_function_1(elem->value, MP_OBJ_NEW_QSTR(attr)); + } + #endif } } else { // delete/store attribute diff --git a/tests/import/module_getattr.py b/tests/import/module_getattr.py new file mode 100644 index 000000000..4a18f414d --- /dev/null +++ b/tests/import/module_getattr.py @@ -0,0 +1,23 @@ +# test __getattr__ on module + +# ensure that does_not_exist doesn't exist to start with +this = __import__(__name__) +try: + this.does_not_exist + assert False +except AttributeError: + pass + +# define __getattr__ +def __getattr__(attr): + if attr == 'does_not_exist': + return False + raise AttributeError + +# do feature test (will also test functionality if the feature exists) +if not hasattr(this, 'does_not_exist'): + print('SKIP') + raise SystemExit + +# check that __getattr__ works as expected +print(this.does_not_exist)