1 | /* |
2 | * This file is part of the MicroPython project, http://micropython.org/ |
3 | * |
4 | * The MIT License (MIT) |
5 | * |
6 | * Copyright (c) 2013-2019 Damien P. George |
7 | * Copyright (c) 2014 Paul Sokolovsky |
8 | * |
9 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
10 | * of this software and associated documentation files (the "Software"), to deal |
11 | * in the Software without restriction, including without limitation the rights |
12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
13 | * copies of the Software, and to permit persons to whom the Software is |
14 | * furnished to do so, subject to the following conditions: |
15 | * |
16 | * The above copyright notice and this permission notice shall be included in |
17 | * all copies or substantial portions of the Software. |
18 | * |
19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
25 | * THE SOFTWARE. |
26 | */ |
27 | |
28 | #include <stdio.h> |
29 | #include <string.h> |
30 | #include <assert.h> |
31 | |
32 | #include "py/compile.h" |
33 | #include "py/objmodule.h" |
34 | #include "py/persistentcode.h" |
35 | #include "py/runtime.h" |
36 | #include "py/builtin.h" |
37 | #include "py/frozenmod.h" |
38 | |
39 | #if MICROPY_DEBUG_VERBOSE // print debugging info |
40 | #define DEBUG_PRINT (1) |
41 | #define DEBUG_printf DEBUG_printf |
42 | #else // don't print debugging info |
43 | #define DEBUG_PRINT (0) |
44 | #define DEBUG_printf(...) (void)0 |
45 | #endif |
46 | |
47 | #if MICROPY_ENABLE_EXTERNAL_IMPORT |
48 | |
49 | #define PATH_SEP_CHAR '/' |
50 | |
51 | bool mp_obj_is_package(mp_obj_t module) { |
52 | mp_obj_t dest[2]; |
53 | mp_load_method_maybe(module, MP_QSTR___path__, dest); |
54 | return dest[0] != MP_OBJ_NULL; |
55 | } |
56 | |
57 | // Stat either frozen or normal module by a given path |
58 | // (whatever is available, if at all). |
59 | STATIC mp_import_stat_t mp_import_stat_any(const char *path) { |
60 | #if MICROPY_MODULE_FROZEN |
61 | mp_import_stat_t st = mp_frozen_stat(path); |
62 | if (st != MP_IMPORT_STAT_NO_EXIST) { |
63 | return st; |
64 | } |
65 | #endif |
66 | return mp_import_stat(path); |
67 | } |
68 | |
69 | STATIC mp_import_stat_t stat_file_py_or_mpy(vstr_t *path) { |
70 | mp_import_stat_t stat = mp_import_stat_any(vstr_null_terminated_str(path)); |
71 | if (stat == MP_IMPORT_STAT_FILE) { |
72 | return stat; |
73 | } |
74 | |
75 | #if MICROPY_PERSISTENT_CODE_LOAD |
76 | vstr_ins_byte(path, path->len - 2, 'm'); |
77 | stat = mp_import_stat_any(vstr_null_terminated_str(path)); |
78 | if (stat == MP_IMPORT_STAT_FILE) { |
79 | return stat; |
80 | } |
81 | #endif |
82 | |
83 | return MP_IMPORT_STAT_NO_EXIST; |
84 | } |
85 | |
86 | STATIC mp_import_stat_t stat_dir_or_file(vstr_t *path) { |
87 | mp_import_stat_t stat = mp_import_stat_any(vstr_null_terminated_str(path)); |
88 | DEBUG_printf("stat %s: %d\n" , vstr_str(path), stat); |
89 | if (stat == MP_IMPORT_STAT_DIR) { |
90 | return stat; |
91 | } |
92 | |
93 | // not a directory, add .py and try as a file |
94 | vstr_add_str(path, ".py" ); |
95 | return stat_file_py_or_mpy(path); |
96 | } |
97 | |
98 | STATIC mp_import_stat_t find_file(const char *file_str, uint file_len, vstr_t *dest) { |
99 | #if MICROPY_PY_SYS |
100 | // extract the list of paths |
101 | size_t path_num; |
102 | mp_obj_t *path_items; |
103 | mp_obj_list_get(mp_sys_path, &path_num, &path_items); |
104 | |
105 | if (path_num != 0) { |
106 | // go through each path looking for a directory or file |
107 | for (size_t i = 0; i < path_num; i++) { |
108 | vstr_reset(dest); |
109 | size_t p_len; |
110 | const char *p = mp_obj_str_get_data(path_items[i], &p_len); |
111 | if (p_len > 0) { |
112 | vstr_add_strn(dest, p, p_len); |
113 | vstr_add_char(dest, PATH_SEP_CHAR); |
114 | } |
115 | vstr_add_strn(dest, file_str, file_len); |
116 | mp_import_stat_t stat = stat_dir_or_file(dest); |
117 | if (stat != MP_IMPORT_STAT_NO_EXIST) { |
118 | return stat; |
119 | } |
120 | } |
121 | |
122 | // could not find a directory or file |
123 | return MP_IMPORT_STAT_NO_EXIST; |
124 | } |
125 | #endif |
126 | |
127 | // mp_sys_path is empty, so just use the given file name |
128 | vstr_add_strn(dest, file_str, file_len); |
129 | return stat_dir_or_file(dest); |
130 | } |
131 | |
132 | #if MICROPY_MODULE_FROZEN_STR || MICROPY_ENABLE_COMPILER |
133 | STATIC void do_load_from_lexer(mp_obj_t module_obj, mp_lexer_t *lex) { |
134 | #if MICROPY_PY___FILE__ |
135 | qstr source_name = lex->source_name; |
136 | mp_store_attr(module_obj, MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name)); |
137 | #endif |
138 | |
139 | // parse, compile and execute the module in its context |
140 | mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); |
141 | mp_parse_compile_execute(lex, MP_PARSE_FILE_INPUT, mod_globals, mod_globals); |
142 | } |
143 | #endif |
144 | |
145 | #if MICROPY_PERSISTENT_CODE_LOAD || MICROPY_MODULE_FROZEN_MPY |
146 | STATIC void do_execute_raw_code(mp_obj_t module_obj, mp_raw_code_t *raw_code, const char *source_name) { |
147 | (void)source_name; |
148 | |
149 | #if MICROPY_PY___FILE__ |
150 | mp_store_attr(module_obj, MP_QSTR___file__, MP_OBJ_NEW_QSTR(qstr_from_str(source_name))); |
151 | #endif |
152 | |
153 | // execute the module in its context |
154 | mp_obj_dict_t *mod_globals = mp_obj_module_get_globals(module_obj); |
155 | |
156 | // save context |
157 | mp_obj_dict_t *volatile old_globals = mp_globals_get(); |
158 | mp_obj_dict_t *volatile old_locals = mp_locals_get(); |
159 | |
160 | // set new context |
161 | mp_globals_set(mod_globals); |
162 | mp_locals_set(mod_globals); |
163 | |
164 | nlr_buf_t nlr; |
165 | if (nlr_push(&nlr) == 0) { |
166 | mp_obj_t module_fun = mp_make_function_from_raw_code(raw_code, MP_OBJ_NULL, MP_OBJ_NULL); |
167 | mp_call_function_0(module_fun); |
168 | |
169 | // finish nlr block, restore context |
170 | nlr_pop(); |
171 | mp_globals_set(old_globals); |
172 | mp_locals_set(old_locals); |
173 | } else { |
174 | // exception; restore context and re-raise same exception |
175 | mp_globals_set(old_globals); |
176 | mp_locals_set(old_locals); |
177 | nlr_jump(nlr.ret_val); |
178 | } |
179 | } |
180 | #endif |
181 | |
182 | STATIC void do_load(mp_obj_t module_obj, vstr_t *file) { |
183 | #if MICROPY_MODULE_FROZEN || MICROPY_ENABLE_COMPILER || (MICROPY_PERSISTENT_CODE_LOAD && MICROPY_HAS_FILE_READER) |
184 | char *file_str = vstr_null_terminated_str(file); |
185 | #endif |
186 | |
187 | // If we support frozen modules (either as str or mpy) then try to find the |
188 | // requested filename in the list of frozen module filenames. |
189 | #if MICROPY_MODULE_FROZEN |
190 | void *modref; |
191 | int frozen_type = mp_find_frozen_module(file_str, file->len, &modref); |
192 | #endif |
193 | |
194 | // If we support frozen str modules and the compiler is enabled, and we |
195 | // found the filename in the list of frozen files, then load and execute it. |
196 | #if MICROPY_MODULE_FROZEN_STR |
197 | if (frozen_type == MP_FROZEN_STR) { |
198 | do_load_from_lexer(module_obj, modref); |
199 | return; |
200 | } |
201 | #endif |
202 | |
203 | // If we support frozen mpy modules and we found a corresponding file (and |
204 | // its data) in the list of frozen files, execute it. |
205 | #if MICROPY_MODULE_FROZEN_MPY |
206 | if (frozen_type == MP_FROZEN_MPY) { |
207 | do_execute_raw_code(module_obj, modref, file_str); |
208 | return; |
209 | } |
210 | #endif |
211 | |
212 | // If we support loading .mpy files then check if the file extension is of |
213 | // the correct format and, if so, load and execute the file. |
214 | #if MICROPY_HAS_FILE_READER && MICROPY_PERSISTENT_CODE_LOAD |
215 | if (file_str[file->len - 3] == 'm') { |
216 | mp_raw_code_t *raw_code = mp_raw_code_load_file(file_str); |
217 | do_execute_raw_code(module_obj, raw_code, file_str); |
218 | return; |
219 | } |
220 | #endif |
221 | |
222 | // If we can compile scripts then load the file and compile and execute it. |
223 | #if MICROPY_ENABLE_COMPILER |
224 | { |
225 | mp_lexer_t *lex = mp_lexer_new_from_file(file_str); |
226 | do_load_from_lexer(module_obj, lex); |
227 | return; |
228 | } |
229 | #else |
230 | // If we get here then the file was not frozen and we can't compile scripts. |
231 | mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("script compilation not supported" )); |
232 | #endif |
233 | } |
234 | |
235 | STATIC void chop_component(const char *start, const char **end) { |
236 | const char *p = *end; |
237 | while (p > start) { |
238 | if (*--p == '.') { |
239 | *end = p; |
240 | return; |
241 | } |
242 | } |
243 | *end = p; |
244 | } |
245 | |
246 | mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { |
247 | #if DEBUG_PRINT |
248 | DEBUG_printf("__import__:\n" ); |
249 | for (size_t i = 0; i < n_args; i++) { |
250 | DEBUG_printf(" " ); |
251 | mp_obj_print(args[i], PRINT_REPR); |
252 | DEBUG_printf("\n" ); |
253 | } |
254 | #endif |
255 | |
256 | mp_obj_t module_name = args[0]; |
257 | mp_obj_t fromtuple = mp_const_none; |
258 | mp_int_t level = 0; |
259 | if (n_args >= 4) { |
260 | fromtuple = args[3]; |
261 | if (n_args >= 5) { |
262 | level = MP_OBJ_SMALL_INT_VALUE(args[4]); |
263 | if (level < 0) { |
264 | mp_raise_ValueError(NULL); |
265 | } |
266 | } |
267 | } |
268 | |
269 | size_t mod_len; |
270 | const char *mod_str = mp_obj_str_get_data(module_name, &mod_len); |
271 | |
272 | if (level != 0) { |
273 | // What we want to do here is to take name of current module, |
274 | // chop <level> trailing components, and concatenate with passed-in |
275 | // module name, thus resolving relative import name into absolute. |
276 | // This even appears to be correct per |
277 | // http://legacy.python.org/dev/peps/pep-0328/#relative-imports-and-name |
278 | // "Relative imports use a module's __name__ attribute to determine that |
279 | // module's position in the package hierarchy." |
280 | level--; |
281 | mp_obj_t this_name_q = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___name__)); |
282 | assert(this_name_q != MP_OBJ_NULL); |
283 | #if MICROPY_CPYTHON_COMPAT |
284 | if (MP_OBJ_QSTR_VALUE(this_name_q) == MP_QSTR___main__) { |
285 | // This is a module run by -m command-line switch, get its real name from backup attribute |
286 | this_name_q = mp_obj_dict_get(MP_OBJ_FROM_PTR(mp_globals_get()), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); |
287 | } |
288 | #endif |
289 | mp_map_t *globals_map = &mp_globals_get()->map; |
290 | mp_map_elem_t *elem = mp_map_lookup(globals_map, MP_OBJ_NEW_QSTR(MP_QSTR___path__), MP_MAP_LOOKUP); |
291 | bool is_pkg = (elem != NULL); |
292 | |
293 | #if DEBUG_PRINT |
294 | DEBUG_printf("Current module/package: " ); |
295 | mp_obj_print(this_name_q, PRINT_REPR); |
296 | DEBUG_printf(", is_package: %d" , is_pkg); |
297 | DEBUG_printf("\n" ); |
298 | #endif |
299 | |
300 | size_t this_name_l; |
301 | const char *this_name = mp_obj_str_get_data(this_name_q, &this_name_l); |
302 | |
303 | const char *p = this_name + this_name_l; |
304 | if (!is_pkg) { |
305 | // We have module, but relative imports are anchored at package, so |
306 | // go there. |
307 | chop_component(this_name, &p); |
308 | } |
309 | |
310 | while (level--) { |
311 | chop_component(this_name, &p); |
312 | } |
313 | |
314 | // We must have some component left over to import from |
315 | if (p == this_name) { |
316 | mp_raise_ValueError(MP_ERROR_TEXT("can't perform relative import" )); |
317 | } |
318 | |
319 | uint new_mod_l = (mod_len == 0 ? (size_t)(p - this_name) : (size_t)(p - this_name) + 1 + mod_len); |
320 | char *new_mod = mp_local_alloc(new_mod_l); |
321 | memcpy(new_mod, this_name, p - this_name); |
322 | if (mod_len != 0) { |
323 | new_mod[p - this_name] = '.'; |
324 | memcpy(new_mod + (p - this_name) + 1, mod_str, mod_len); |
325 | } |
326 | |
327 | qstr new_mod_q = qstr_from_strn(new_mod, new_mod_l); |
328 | mp_local_free(new_mod); |
329 | DEBUG_printf("Resolved base name for relative import: '%s'\n" , qstr_str(new_mod_q)); |
330 | module_name = MP_OBJ_NEW_QSTR(new_mod_q); |
331 | mod_str = qstr_str(new_mod_q); |
332 | mod_len = new_mod_l; |
333 | } |
334 | |
335 | if (mod_len == 0) { |
336 | mp_raise_ValueError(NULL); |
337 | } |
338 | |
339 | // check if module already exists |
340 | qstr module_name_qstr = mp_obj_str_get_qstr(module_name); |
341 | mp_obj_t module_obj = mp_module_get(module_name_qstr); |
342 | if (module_obj != MP_OBJ_NULL) { |
343 | DEBUG_printf("Module already loaded\n" ); |
344 | // If it's not a package, return module right away |
345 | char *p = strchr(mod_str, '.'); |
346 | if (p == NULL) { |
347 | return module_obj; |
348 | } |
349 | // If fromlist is not empty, return leaf module |
350 | if (fromtuple != mp_const_none) { |
351 | return module_obj; |
352 | } |
353 | // Otherwise, we need to return top-level package |
354 | qstr pkg_name = qstr_from_strn(mod_str, p - mod_str); |
355 | return mp_module_get(pkg_name); |
356 | } |
357 | DEBUG_printf("Module not yet loaded\n" ); |
358 | |
359 | uint last = 0; |
360 | VSTR_FIXED(path, MICROPY_ALLOC_PATH_MAX) |
361 | module_obj = MP_OBJ_NULL; |
362 | mp_obj_t top_module_obj = MP_OBJ_NULL; |
363 | mp_obj_t outer_module_obj = MP_OBJ_NULL; |
364 | uint i; |
365 | for (i = 1; i <= mod_len; i++) { |
366 | if (i == mod_len || mod_str[i] == '.') { |
367 | // create a qstr for the module name up to this depth |
368 | qstr mod_name = qstr_from_strn(mod_str, i); |
369 | DEBUG_printf("Processing module: %s\n" , qstr_str(mod_name)); |
370 | DEBUG_printf("Previous path: =%.*s=\n" , vstr_len(&path), vstr_str(&path)); |
371 | |
372 | // find the file corresponding to the module name |
373 | mp_import_stat_t stat; |
374 | if (vstr_len(&path) == 0) { |
375 | // first module in the dotted-name; search for a directory or file |
376 | stat = find_file(mod_str, i, &path); |
377 | } else { |
378 | // latter module in the dotted-name; append to path |
379 | vstr_add_char(&path, PATH_SEP_CHAR); |
380 | vstr_add_strn(&path, mod_str + last, i - last); |
381 | stat = stat_dir_or_file(&path); |
382 | } |
383 | DEBUG_printf("Current path: %.*s\n" , vstr_len(&path), vstr_str(&path)); |
384 | |
385 | if (stat == MP_IMPORT_STAT_NO_EXIST) { |
386 | module_obj = MP_OBJ_NULL; |
387 | #if MICROPY_MODULE_WEAK_LINKS |
388 | // check if there is a weak link to this module |
389 | if (i == mod_len) { |
390 | module_obj = mp_module_search_umodule(mod_str); |
391 | if (module_obj != MP_OBJ_NULL) { |
392 | // found weak linked module |
393 | mp_module_call_init(mod_name, module_obj); |
394 | } |
395 | } |
396 | #endif |
397 | if (module_obj == MP_OBJ_NULL) { |
398 | // couldn't find the file, so fail |
399 | #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE |
400 | mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("module not found" )); |
401 | #else |
402 | mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("no module named '%q'" ), mod_name); |
403 | #endif |
404 | } |
405 | } else { |
406 | // found the file, so get the module |
407 | module_obj = mp_module_get(mod_name); |
408 | } |
409 | |
410 | if (module_obj == MP_OBJ_NULL) { |
411 | // module not already loaded, so load it! |
412 | |
413 | module_obj = mp_obj_new_module(mod_name); |
414 | |
415 | // if args[3] (fromtuple) has magic value False, set up |
416 | // this module for command-line "-m" option (set module's |
417 | // name to __main__ instead of real name). Do this only |
418 | // for *modules* however - packages never have their names |
419 | // replaced, instead they're -m'ed using a special __main__ |
420 | // submodule in them. (This all apparently is done to not |
421 | // touch package name itself, which is important for future |
422 | // imports). |
423 | if (i == mod_len && fromtuple == mp_const_false && stat != MP_IMPORT_STAT_DIR) { |
424 | mp_obj_module_t *o = MP_OBJ_TO_PTR(module_obj); |
425 | mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR___main__)); |
426 | #if MICROPY_CPYTHON_COMPAT |
427 | // Store module as "__main__" in the dictionary of loaded modules (returned by sys.modules). |
428 | mp_obj_dict_store(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_loaded_modules_dict)), MP_OBJ_NEW_QSTR(MP_QSTR___main__), module_obj); |
429 | // Store real name in "__main__" attribute. Chosen semi-randonly, to reuse existing qstr's. |
430 | mp_obj_dict_store(MP_OBJ_FROM_PTR(o->globals), MP_OBJ_NEW_QSTR(MP_QSTR___main__), MP_OBJ_NEW_QSTR(mod_name)); |
431 | #endif |
432 | } |
433 | |
434 | if (stat == MP_IMPORT_STAT_DIR) { |
435 | DEBUG_printf("%.*s is dir\n" , vstr_len(&path), vstr_str(&path)); |
436 | // https://docs.python.org/3/reference/import.html |
437 | // "Specifically, any module that contains a __path__ attribute is considered a package." |
438 | mp_store_attr(module_obj, MP_QSTR___path__, mp_obj_new_str(vstr_str(&path), vstr_len(&path))); |
439 | size_t orig_path_len = path.len; |
440 | vstr_add_char(&path, PATH_SEP_CHAR); |
441 | vstr_add_str(&path, "__init__.py" ); |
442 | if (stat_file_py_or_mpy(&path) != MP_IMPORT_STAT_FILE) { |
443 | // mp_warning("%s is imported as namespace package", vstr_str(&path)); |
444 | } else { |
445 | do_load(module_obj, &path); |
446 | } |
447 | path.len = orig_path_len; |
448 | } else { // MP_IMPORT_STAT_FILE |
449 | do_load(module_obj, &path); |
450 | // This should be the last component in the import path. If there are |
451 | // remaining components then it's an ImportError because the current path |
452 | // (the module that was just loaded) is not a package. This will be caught |
453 | // on the next iteration because the file will not exist. |
454 | } |
455 | } |
456 | if (outer_module_obj != MP_OBJ_NULL) { |
457 | qstr s = qstr_from_strn(mod_str + last, i - last); |
458 | mp_store_attr(outer_module_obj, s, module_obj); |
459 | } |
460 | outer_module_obj = module_obj; |
461 | if (top_module_obj == MP_OBJ_NULL) { |
462 | top_module_obj = module_obj; |
463 | } |
464 | last = i + 1; |
465 | } |
466 | } |
467 | |
468 | // If fromlist is not empty, return leaf module |
469 | if (fromtuple != mp_const_none) { |
470 | return module_obj; |
471 | } |
472 | // Otherwise, we need to return top-level package |
473 | return top_module_obj; |
474 | } |
475 | |
476 | #else // MICROPY_ENABLE_EXTERNAL_IMPORT |
477 | |
478 | mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) { |
479 | // Check that it's not a relative import |
480 | if (n_args >= 5 && MP_OBJ_SMALL_INT_VALUE(args[4]) != 0) { |
481 | mp_raise_NotImplementedError(MP_ERROR_TEXT("relative import" )); |
482 | } |
483 | |
484 | // Check if module already exists, and return it if it does |
485 | qstr module_name_qstr = mp_obj_str_get_qstr(args[0]); |
486 | mp_obj_t module_obj = mp_module_get(module_name_qstr); |
487 | if (module_obj != MP_OBJ_NULL) { |
488 | return module_obj; |
489 | } |
490 | |
491 | #if MICROPY_MODULE_WEAK_LINKS |
492 | // Check if there is a weak link to this module |
493 | module_obj = mp_module_search_umodule(qstr_str(module_name_qstr)); |
494 | if (module_obj != MP_OBJ_NULL) { |
495 | // Found weak-linked module |
496 | mp_module_call_init(module_name_qstr, module_obj); |
497 | return module_obj; |
498 | } |
499 | #endif |
500 | |
501 | // Couldn't find the module, so fail |
502 | #if MICROPY_ERROR_REPORTING == MICROPY_ERROR_REPORTING_TERSE |
503 | mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("module not found" )); |
504 | #else |
505 | mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("no module named '%q'" ), module_name_qstr); |
506 | #endif |
507 | } |
508 | |
509 | #endif // MICROPY_ENABLE_EXTERNAL_IMPORT |
510 | |
511 | MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin___import___obj, 1, 5, mp_builtin___import__); |
512 | |