micropython-stubber 1.20.5__py3-none-any.whl → 1.23.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. {micropython_stubber-1.20.5.dist-info → micropython_stubber-1.23.0.dist-info}/LICENSE +30 -30
  2. {micropython_stubber-1.20.5.dist-info → micropython_stubber-1.23.0.dist-info}/METADATA +1 -1
  3. micropython_stubber-1.23.0.dist-info/RECORD +159 -0
  4. mpflash/README.md +184 -184
  5. mpflash/libusb_flash.ipynb +203 -203
  6. mpflash/mpflash/add_firmware.py +98 -98
  7. mpflash/mpflash/ask_input.py +236 -236
  8. mpflash/mpflash/bootloader/__init__.py +37 -36
  9. mpflash/mpflash/bootloader/manual.py +102 -102
  10. mpflash/mpflash/bootloader/micropython.py +10 -10
  11. mpflash/mpflash/bootloader/touch1200.py +45 -45
  12. mpflash/mpflash/cli_download.py +129 -129
  13. mpflash/mpflash/cli_flash.py +219 -219
  14. mpflash/mpflash/cli_group.py +98 -98
  15. mpflash/mpflash/cli_list.py +81 -81
  16. mpflash/mpflash/cli_main.py +41 -41
  17. mpflash/mpflash/common.py +164 -164
  18. mpflash/mpflash/config.py +43 -47
  19. mpflash/mpflash/connected.py +74 -74
  20. mpflash/mpflash/download.py +360 -360
  21. mpflash/mpflash/downloaded.py +130 -129
  22. mpflash/mpflash/errors.py +9 -9
  23. mpflash/mpflash/flash.py +55 -52
  24. mpflash/mpflash/flash_esp.py +59 -59
  25. mpflash/mpflash/flash_stm32.py +18 -24
  26. mpflash/mpflash/flash_stm32_cube.py +111 -111
  27. mpflash/mpflash/flash_stm32_dfu.py +104 -101
  28. mpflash/mpflash/flash_uf2.py +89 -67
  29. mpflash/mpflash/flash_uf2_boardid.py +15 -15
  30. mpflash/mpflash/flash_uf2_linux.py +129 -123
  31. mpflash/mpflash/flash_uf2_macos.py +37 -34
  32. mpflash/mpflash/flash_uf2_windows.py +38 -34
  33. mpflash/mpflash/list.py +89 -89
  34. mpflash/mpflash/logger.py +41 -41
  35. mpflash/mpflash/mpboard_id/__init__.py +93 -93
  36. mpflash/mpflash/mpboard_id/add_boards.py +255 -255
  37. mpflash/mpflash/mpboard_id/board.py +37 -37
  38. mpflash/mpflash/mpboard_id/board_id.py +86 -86
  39. mpflash/mpflash/mpboard_id/store.py +43 -43
  40. mpflash/mpflash/mpremoteboard/__init__.py +226 -221
  41. mpflash/mpflash/mpremoteboard/mpy_fw_info.py +141 -141
  42. mpflash/mpflash/mpremoteboard/runner.py +140 -140
  43. mpflash/mpflash/uf2disk.py +12 -12
  44. mpflash/mpflash/vendor/basicgit.py +288 -288
  45. mpflash/mpflash/vendor/click_aliases.py +91 -91
  46. mpflash/mpflash/vendor/dfu.py +165 -165
  47. mpflash/mpflash/vendor/pydfu.py +605 -605
  48. mpflash/mpflash/vendor/readme.md +2 -2
  49. mpflash/mpflash/vendor/versions.py +119 -117
  50. mpflash/mpflash/worklist.py +171 -170
  51. mpflash/poetry.lock +1588 -1588
  52. mpflash/pyproject.toml +64 -60
  53. mpflash/stm32_udev_rules.md +62 -62
  54. stubber/__init__.py +3 -3
  55. stubber/basicgit.py +294 -288
  56. stubber/board/board_info.csv +193 -193
  57. stubber/board/boot.py +34 -34
  58. stubber/board/createstubs.py +986 -986
  59. stubber/board/createstubs_db.py +825 -825
  60. stubber/board/createstubs_db_min.py +331 -331
  61. stubber/board/createstubs_db_mpy.mpy +0 -0
  62. stubber/board/createstubs_lvgl.py +741 -741
  63. stubber/board/createstubs_lvgl_min.py +741 -741
  64. stubber/board/createstubs_mem.py +766 -766
  65. stubber/board/createstubs_mem_min.py +306 -306
  66. stubber/board/createstubs_mem_mpy.mpy +0 -0
  67. stubber/board/createstubs_min.py +294 -294
  68. stubber/board/createstubs_mpy.mpy +0 -0
  69. stubber/board/fw_info.py +141 -141
  70. stubber/board/info.py +183 -183
  71. stubber/board/main.py +19 -19
  72. stubber/board/modulelist.txt +247 -247
  73. stubber/board/pyrightconfig.json +34 -34
  74. stubber/bulk/mcu_stubber.py +454 -454
  75. stubber/codemod/_partials/__init__.py +48 -48
  76. stubber/codemod/_partials/db_main.py +147 -147
  77. stubber/codemod/_partials/lvgl_main.py +77 -77
  78. stubber/codemod/_partials/modules_reader.py +80 -80
  79. stubber/codemod/add_comment.py +53 -53
  80. stubber/codemod/add_method.py +65 -65
  81. stubber/codemod/board.py +317 -317
  82. stubber/codemod/enrich.py +145 -145
  83. stubber/codemod/merge_docstub.py +284 -284
  84. stubber/codemod/modify_list.py +54 -54
  85. stubber/codemod/utils.py +57 -57
  86. stubber/commands/build_cmd.py +94 -94
  87. stubber/commands/cli.py +55 -51
  88. stubber/commands/clone_cmd.py +77 -66
  89. stubber/commands/config_cmd.py +29 -29
  90. stubber/commands/enrich_folder_cmd.py +71 -70
  91. stubber/commands/get_core_cmd.py +71 -69
  92. stubber/commands/get_docstubs_cmd.py +89 -87
  93. stubber/commands/get_frozen_cmd.py +114 -112
  94. stubber/commands/get_mcu_cmd.py +61 -56
  95. stubber/commands/merge_cmd.py +67 -66
  96. stubber/commands/publish_cmd.py +119 -119
  97. stubber/commands/stub_cmd.py +31 -30
  98. stubber/commands/switch_cmd.py +62 -54
  99. stubber/commands/variants_cmd.py +49 -48
  100. stubber/cst_transformer.py +178 -178
  101. stubber/data/board_info.csv +193 -193
  102. stubber/data/board_info.json +1729 -1729
  103. stubber/data/micropython_tags.csv +15 -15
  104. stubber/data/requirements-core-micropython.txt +38 -38
  105. stubber/data/requirements-core-pycopy.txt +39 -39
  106. stubber/downloader.py +36 -36
  107. stubber/freeze/common.py +68 -68
  108. stubber/freeze/freeze_folder.py +69 -69
  109. stubber/freeze/freeze_manifest_2.py +113 -113
  110. stubber/freeze/get_frozen.py +127 -127
  111. stubber/get_cpython.py +101 -101
  112. stubber/get_lobo.py +59 -59
  113. stubber/minify.py +418 -418
  114. stubber/publish/bump.py +86 -86
  115. stubber/publish/candidates.py +262 -262
  116. stubber/publish/database.py +18 -18
  117. stubber/publish/defaults.py +45 -45
  118. stubber/publish/enums.py +24 -24
  119. stubber/publish/helpers.py +29 -29
  120. stubber/publish/merge_docstubs.py +130 -130
  121. stubber/publish/missing_class_methods.py +49 -49
  122. stubber/publish/package.py +146 -146
  123. stubber/publish/pathnames.py +51 -51
  124. stubber/publish/publish.py +120 -120
  125. stubber/publish/pypi.py +38 -38
  126. stubber/publish/stubpackage.py +1029 -1029
  127. stubber/rst/__init__.py +9 -9
  128. stubber/rst/classsort.py +77 -77
  129. stubber/rst/lookup.py +530 -530
  130. stubber/rst/output_dict.py +401 -401
  131. stubber/rst/reader.py +822 -822
  132. stubber/rst/report_return.py +69 -69
  133. stubber/rst/rst_utils.py +540 -540
  134. stubber/stubber.py +38 -38
  135. stubber/stubs_from_docs.py +90 -90
  136. stubber/tools/manifestfile.py +655 -610
  137. stubber/tools/readme.md +7 -6
  138. stubber/update_fallback.py +117 -117
  139. stubber/update_module_list.py +123 -123
  140. stubber/utils/__init__.py +5 -5
  141. stubber/utils/config.py +127 -127
  142. stubber/utils/makeversionhdr.py +54 -54
  143. stubber/utils/manifest.py +92 -92
  144. stubber/utils/post.py +79 -79
  145. stubber/utils/repos.py +157 -154
  146. stubber/utils/stubmaker.py +139 -139
  147. stubber/utils/typed_config_toml.py +77 -77
  148. stubber/utils/versions.py +128 -120
  149. stubber/variants.py +106 -106
  150. micropython_stubber-1.20.5.dist-info/RECORD +0 -159
  151. {micropython_stubber-1.20.5.dist-info → micropython_stubber-1.23.0.dist-info}/WHEEL +0 -0
  152. {micropython_stubber-1.20.5.dist-info → micropython_stubber-1.23.0.dist-info}/entry_points.txt +0 -0
@@ -1,986 +1,986 @@
1
- """
2
- Create stubs for (all) modules on a MicroPython board
3
- """
4
-
5
- # Copyright (c) 2019-2024 Jos Verlinde
6
-
7
- import gc
8
- import os
9
- import sys
10
- from time import sleep
11
-
12
- try:
13
- from ujson import dumps
14
- except:
15
- from json import dumps
16
-
17
- try:
18
- from machine import reset # type: ignore
19
- except ImportError:
20
- pass
21
-
22
- try:
23
- from collections import OrderedDict
24
- except ImportError:
25
- from ucollections import OrderedDict # type: ignore
26
-
27
- __version__ = "v1.20.5"
28
- ENOENT = 2
29
- _MAX_CLASS_LEVEL = 2 # Max class nesting
30
- LIBS = ["lib", "/lib", "/sd/lib", "/flash/lib", "."]
31
-
32
-
33
- # our own logging module to avoid dependency on and interfering with logging module
34
- class logging:
35
- # DEBUG = 10
36
- INFO = 20
37
- WARNING = 30
38
- ERROR = 40
39
- level = INFO
40
- prnt = print
41
-
42
- @staticmethod
43
- def getLogger(name):
44
- return logging()
45
-
46
- @classmethod
47
- def basicConfig(cls, level):
48
- cls.level = level
49
-
50
- # def debug(self, msg):
51
- # if self.level <= logging.DEBUG:
52
- # self.prnt("DEBUG :", msg)
53
-
54
- def info(self, msg):
55
- if self.level <= logging.INFO:
56
- self.prnt("INFO :", msg)
57
-
58
- def warning(self, msg):
59
- if self.level <= logging.WARNING:
60
- self.prnt("WARN :", msg)
61
-
62
- def error(self, msg):
63
- if self.level <= logging.ERROR:
64
- self.prnt("ERROR :", msg)
65
-
66
-
67
- log = logging.getLogger("stubber")
68
- logging.basicConfig(level=logging.INFO)
69
- # logging.basicConfig(level=logging.DEBUG)
70
-
71
-
72
- class Stubber:
73
- "Generate stubs for modules in firmware"
74
-
75
- def __init__(self, path: str = None, firmware_id: str = None): # type: ignore
76
- try:
77
- if os.uname().release == "1.13.0" and os.uname().version < "v1.13-103": # type: ignore
78
- raise NotImplementedError("MicroPython 1.13.0 cannot be stubbed")
79
- except AttributeError:
80
- pass # Allow testing on CPython 3.11
81
- self.info = _info()
82
- log.info("Port: {}".format(self.info["port"]))
83
- log.info("Board: {}".format(self.info["board"]))
84
- gc.collect()
85
- if firmware_id:
86
- self._fwid = firmware_id.lower()
87
- else:
88
- if self.info["family"] == "micropython":
89
- self._fwid = "{family}-v{version}-{port}-{board}".format(**self.info).rstrip("-")
90
- else:
91
- self._fwid = "{family}-v{version}-{port}".format(**self.info)
92
- self._start_free = gc.mem_free() # type: ignore
93
-
94
- if path:
95
- if path.endswith("/"):
96
- path = path[:-1]
97
- else:
98
- path = get_root()
99
-
100
- self.path = "{}/stubs/{}".format(path, self.flat_fwid).replace("//", "/")
101
- # log.debug(self.path)
102
- try:
103
- ensure_folder(path + "/")
104
- except OSError:
105
- log.error("error creating stub folder {}".format(path))
106
- self.problematic = [
107
- "upip",
108
- "upysh",
109
- "webrepl_setup",
110
- "http_client",
111
- "http_client_ssl",
112
- "http_server",
113
- "http_server_ssl",
114
- ]
115
- self.excluded = [
116
- "webrepl",
117
- "_webrepl",
118
- "port_diag",
119
- "example_sub_led.py",
120
- "example_pub_button.py",
121
- ]
122
- # there is no option to discover modules from micropython, list is read from an external file.
123
- self.modules = [] # type: list[str]
124
- self._json_name = None
125
- self._json_first = False
126
-
127
- def get_obj_attributes(self, item_instance: object):
128
- "extract information of the objects members and attributes"
129
- # name_, repr_(value), type as text, item_instance
130
- _result = []
131
- _errors = []
132
- # log.debug("get attributes {} {}".format(repr(item_instance), item_instance))
133
- for name in dir(item_instance):
134
- if name.startswith("__") and not name in self.modules:
135
- continue
136
- # log.debug("get attribute {}".format(name))
137
- try:
138
- val = getattr(item_instance, name)
139
- # name , item_repr(value) , type as text, item_instance, order
140
- # log.debug("attribute {}:{}".format(name, val))
141
- try:
142
- type_text = repr(type(val)).split("'")[1]
143
- except IndexError:
144
- type_text = ""
145
- if type_text in {"int", "float", "str", "bool", "tuple", "list", "dict"}:
146
- order = 1
147
- elif type_text in {"function", "method"}:
148
- order = 2
149
- elif type_text in ("class"):
150
- order = 3
151
- else:
152
- order = 4
153
- _result.append((name, repr(val), repr(type(val)), val, order))
154
- except AttributeError as e:
155
- _errors.append("Couldn't get attribute '{}' from object '{}', Err: {}".format(name, item_instance, e))
156
- except MemoryError as e:
157
- print("MemoryError: {}".format(e))
158
- sleep(1)
159
- reset()
160
-
161
- # remove internal __
162
- # _result = sorted([i for i in _result if not (i[0].startswith("_"))], key=lambda x: x[4])
163
- _result = sorted([i for i in _result if not (i[0].startswith("__"))], key=lambda x: x[4])
164
- gc.collect()
165
- return _result, _errors
166
-
167
- def add_modules(self, modules):
168
- "Add additional modules to be exported"
169
- self.modules = sorted(set(self.modules) | set(modules))
170
-
171
- def create_all_stubs(self):
172
- "Create stubs for all configured modules"
173
- log.info("Start micropython-stubber {} on {}".format(__version__, self._fwid))
174
- self.report_start()
175
- gc.collect()
176
- for module_name in self.modules:
177
- self.create_one_stub(module_name)
178
- self.report_end()
179
- log.info("Finally done")
180
-
181
- def create_one_stub(self, module_name: str):
182
- if module_name in self.problematic:
183
- log.warning("Skip module: {:<25} : Known problematic".format(module_name))
184
- return False
185
- if module_name in self.excluded:
186
- log.warning("Skip module: {:<25} : Excluded".format(module_name))
187
- return False
188
-
189
- file_name = "{}/{}.pyi".format(self.path, module_name.replace(".", "/"))
190
- gc.collect()
191
- result = False
192
- try:
193
- result = self.create_module_stub(module_name, file_name)
194
- except OSError:
195
- return False
196
- gc.collect()
197
- return result
198
-
199
- def create_module_stub(self, module_name: str, file_name: str = None) -> bool: # type: ignore
200
- """Create a Stub of a single python module
201
-
202
- Args:
203
- - module_name (str): name of the module to document. This module will be imported.
204
- - file_name (Optional[str]): the 'path/filename.pyi' to write to. If omitted will be created based on the module name.
205
- """
206
- if file_name is None:
207
- fname = module_name.replace(".", "_") + ".pyi"
208
- file_name = self.path + "/" + fname
209
- else:
210
- fname = file_name.split("/")[-1]
211
-
212
- if "/" in module_name:
213
- # for nested modules
214
- module_name = module_name.replace("/", ".")
215
-
216
- # import the module (as new_module) to examine it
217
- new_module = None
218
- try:
219
- new_module = __import__(module_name, None, None, ("*"))
220
- m1 = gc.mem_free() # type: ignore
221
- log.info("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1))
222
-
223
- except ImportError:
224
- # log.debug("Skip module: {:<25} {:<79}".format(module_name, "Module not found."))
225
- return False
226
-
227
- # Start a new file
228
- ensure_folder(file_name)
229
- with open(file_name, "w") as fp:
230
- info_ = str(self.info).replace("OrderedDict(", "").replace("})", "}")
231
- s = '"""\nModule: \'{0}\' on {1}\n"""\n# MCU: {2}\n# Stubber: {3}\n'.format(
232
- module_name, self._fwid, info_, __version__
233
- )
234
- fp.write(s)
235
- fp.write(
236
- "from __future__ import annotations\nfrom typing import Any, Generator\nfrom _typeshed import Incomplete\n\n"
237
- )
238
- self.write_object_stub(fp, new_module, module_name, "")
239
-
240
- self.report_add(module_name, file_name)
241
-
242
- if module_name not in {"os", "sys", "logging", "gc"}:
243
- # try to unload the module unless we use it
244
- try:
245
- del new_module
246
- except (OSError, KeyError): # lgtm [py/unreachable-statement]
247
- log.warning("could not del new_module")
248
- # do not try to delete from sys.modules - most times it does not work anyway
249
- gc.collect()
250
- return True
251
-
252
- def write_object_stub(self, fp, object_expr: object, obj_name: str, indent: str, in_class: int = 0):
253
- "Write a module/object stub to an open file. Can be called recursive."
254
- gc.collect()
255
- if object_expr in self.problematic:
256
- log.warning("SKIPPING problematic module:{}".format(object_expr))
257
- return
258
-
259
- # # log.debug("DUMP : {}".format(object_expr))
260
- items, errors = self.get_obj_attributes(object_expr)
261
-
262
- if errors:
263
- log.error(errors)
264
-
265
- for item_name, item_repr, item_type_txt, item_instance, _ in items:
266
- # name_, repr_(value), type as text, item_instance, order
267
- if item_name in ["classmethod", "staticmethod", "BaseException", "Exception"]:
268
- # do not create stubs for these primitives
269
- continue
270
- if item_name[0].isdigit():
271
- log.warning("NameError: invalid name {}".format(item_name))
272
- continue
273
- # Class expansion only on first 3 levels (bit of a hack)
274
- if (
275
- item_type_txt == "<class 'type'>"
276
- and len(indent) <= _MAX_CLASS_LEVEL * 4
277
- # and not obj_name.endswith(".Pin")
278
- # avoid expansion of Pin.cpu / Pin.board to avoid crashes on most platforms
279
- ):
280
- # log.debug("{0}class {1}:".format(indent, item_name))
281
- superclass = ""
282
- is_exception = (
283
- item_name.endswith("Exception")
284
- or item_name.endswith("Error")
285
- or item_name
286
- in [
287
- "KeyboardInterrupt",
288
- "StopIteration",
289
- "SystemExit",
290
- ]
291
- )
292
- if is_exception:
293
- superclass = "Exception"
294
- s = "\n{}class {}({}):\n".format(indent, item_name, superclass)
295
- # s += indent + " ''\n"
296
- if is_exception:
297
- s += indent + " ...\n"
298
- fp.write(s)
299
- continue
300
- # write classdef
301
- fp.write(s)
302
- # first write the class literals and methods
303
- # log.debug("# recursion over class {0}".format(item_name))
304
- self.write_object_stub(
305
- fp,
306
- item_instance,
307
- "{0}.{1}".format(obj_name, item_name),
308
- indent + " ",
309
- in_class + 1,
310
- )
311
- # end with the __init__ method to make sure that the literals are defined
312
- # Add __init__
313
- s = indent + " def __init__(self, *argv, **kwargs) -> None:\n"
314
- s += indent + " ...\n\n"
315
- fp.write(s)
316
- elif any(word in item_type_txt for word in ["method", "function", "closure"]):
317
- # log.debug("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name))
318
- # module Function or class method
319
- # will accept any number of params
320
- # return type Any/Incomplete
321
- ret = "Incomplete"
322
- first = ""
323
- # Self parameter only on class methods/functions
324
- if in_class > 0:
325
- first = "self, "
326
- # class method - add function decoration
327
- if "bound_method" in item_type_txt or "bound_method" in item_repr:
328
- s = "{}@classmethod\n".format(indent) + "{}def {}(cls, *args, **kwargs) -> {}:\n".format(
329
- indent, item_name, ret
330
- )
331
- else:
332
- s = "{}def {}({}*args, **kwargs) -> {}:\n".format(indent, item_name, first, ret)
333
- s += indent + " ...\n\n"
334
- fp.write(s)
335
- # log.debug("\n" + s)
336
- elif item_type_txt == "<class 'module'>":
337
- # Skip imported modules
338
- # fp.write("# import {}\n".format(item_name))
339
- pass
340
-
341
- elif item_type_txt.startswith("<class '"):
342
- t = item_type_txt[8:-2]
343
- s = ""
344
-
345
- if t in ("str", "int", "float", "bool", "bytearray", "bytes"):
346
- # known type: use actual value
347
- # s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, item_repr, t)
348
- s = "{0}{1}: {3} = {2}\n".format(indent, item_name, item_repr, t)
349
- elif t in ("dict", "list", "tuple"):
350
- # dict, list , tuple: use empty value
351
- ev = {"dict": "{}", "list": "[]", "tuple": "()"}
352
- # s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, ev[t], t)
353
- s = "{0}{1}: {3} = {2}\n".format(indent, item_name, ev[t], t)
354
- else:
355
- # something else
356
- if t in ("object", "set", "frozenset", "Pin", "generator"): # "FileIO"
357
- # https://docs.python.org/3/tutorial/classes.html#item_instance-objects
358
- # use these types for the attribute
359
- if t == "generator":
360
- t = "Generator"
361
- s = "{0}{1}: {2} ## = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
362
- else:
363
- # Requires Python 3.6 syntax, which is OK for the stubs/pyi
364
- t = "Incomplete"
365
- if " at " in item_repr:
366
- item_repr = item_repr.split(" at ")[0] + " at ...>"
367
- if " at " in item_repr:
368
- item_repr = item_repr.split(" at ")[0] + " at ...>"
369
- s = "{0}{1}: {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
370
- fp.write(s)
371
- # log.debug("\n" + s)
372
- else:
373
- # keep only the name
374
- # log.debug("# all other, type = '{0}'".format(item_type_txt))
375
- fp.write("# all other, type = '{0}'\n".format(item_type_txt))
376
-
377
- fp.write(indent + item_name + " # type: Incomplete\n")
378
-
379
- # del items
380
- # del errors
381
- # try:
382
- # del item_name, item_repr, item_type_txt, item_instance # type: ignore
383
- # except (OSError, KeyError, NameError):
384
- # pass
385
-
386
- @property
387
- def flat_fwid(self):
388
- "Turn _fwid from 'v1.2.3' into '1_2_3' to be used in filename"
389
- s = self._fwid
390
- # path name restrictions
391
- chars = " .()/\\:$"
392
- for c in chars:
393
- s = s.replace(c, "_")
394
- return s
395
-
396
- def clean(self, path: str = None): # type: ignore
397
- "Remove all files from the stub folder"
398
- if path is None:
399
- path = self.path
400
- log.info("Clean/remove files in folder: {}".format(path))
401
- try:
402
- os.stat(path) # TEMP workaround mpremote listdir bug -
403
- items = os.listdir(path)
404
- except (OSError, AttributeError):
405
- # os.listdir fails on unix
406
- return
407
- for fn in items:
408
- item = "{}/{}".format(path, fn)
409
- try:
410
- os.remove(item)
411
- except OSError:
412
- try: # folder
413
- self.clean(item)
414
- os.rmdir(item)
415
- except OSError:
416
- pass
417
-
418
- def report_start(self, filename: str = "modules.json"):
419
- """Start a report of the modules that have been stubbed
420
- "create json with list of exported modules"""
421
- self._json_name = "{}/{}".format(self.path, filename)
422
- self._json_first = True
423
- ensure_folder(self._json_name)
424
- log.info("Report file: {}".format(self._json_name))
425
- gc.collect()
426
- try:
427
- # write json by node to reduce memory requirements
428
- with open(self._json_name, "w") as f:
429
- f.write("{")
430
- f.write(dumps({"firmware": self.info})[1:-1])
431
- f.write(",\n")
432
- f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1])
433
- f.write(",\n")
434
- f.write('"modules" :[\n')
435
-
436
- except OSError as e:
437
- log.error("Failed to create the report.")
438
- self._json_name = None
439
- raise e
440
-
441
- def report_add(self, module_name: str, stub_file: str):
442
- "Add a module to the report"
443
- # write json by node to reduce memory requirements
444
- if not self._json_name:
445
- raise Exception("No report file")
446
- try:
447
- with open(self._json_name, "a") as f:
448
- if not self._json_first:
449
- f.write(",\n")
450
- else:
451
- self._json_first = False
452
- line = '{{"module": "{}", "file": "{}"}}'.format(module_name, stub_file.replace("\\", "/"))
453
- f.write(line)
454
-
455
- except OSError:
456
- log.error("Failed to create the report.")
457
-
458
- def report_end(self):
459
- if not self._json_name:
460
- raise Exception("No report file")
461
- with open(self._json_name, "a") as f:
462
- f.write("\n]}")
463
- # is used as sucess indicator
464
- log.info("Path: {}".format(self.path))
465
-
466
-
467
- def ensure_folder(path: str):
468
- "Create nested folders if needed"
469
- i = start = 0
470
- while i != -1:
471
- i = path.find("/", start)
472
- if i != -1:
473
- p = path[0] if i == 0 else path[:i]
474
- # p = partial folder
475
- try:
476
- _ = os.stat(p)
477
- except OSError as e:
478
- # folder does not exist
479
- if e.args[0] == ENOENT:
480
- try:
481
- os.mkdir(p)
482
- except OSError as e2:
483
- log.error("failed to create folder {}".format(p))
484
- raise e2
485
- # next level deep
486
- start = i + 1
487
-
488
-
489
- def _build(s):
490
- # extract build from sys.version or os.uname().version if available
491
- # sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f'
492
- # sys.implementation.version: 'v1.13-103-gb137d064e'
493
- if not s:
494
- return ""
495
- s = s.split(" on ", 1)[0] if " on " in s else s
496
- if s.startswith("v"):
497
- if not "-" in s:
498
- return ""
499
- b = s.split("-")[1]
500
- return b
501
- if not "-preview" in s:
502
- return ""
503
- b = s.split("-preview")[1].split(".")[1]
504
- return b
505
-
506
-
507
- def _info(): # type:() -> dict[str, str]
508
- try:
509
- fam = sys.implementation[0] # type: ignore
510
- except TypeError:
511
- # testing on CPython 3.11
512
- fam = sys.implementation.name
513
-
514
- info = OrderedDict(
515
- {
516
- "family": fam,
517
- "version": "",
518
- "build": "",
519
- "ver": "",
520
- "port": sys.platform, # port: esp32 / win32 / linux / stm32
521
- "board": "UNKNOWN",
522
- "cpu": "",
523
- "mpy": "",
524
- "arch": "",
525
- }
526
- )
527
- # change port names to be consistent with the repo
528
- if info["port"].startswith("pyb"):
529
- info["port"] = "stm32"
530
- elif info["port"] == "win32":
531
- info["port"] = "windows"
532
- elif info["port"] == "linux":
533
- info["port"] = "unix"
534
- try:
535
- info["version"] = version_str(sys.implementation.version) # type: ignore
536
- except AttributeError:
537
- pass
538
- try:
539
- _machine = (
540
- sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore
541
- )
542
- # info["board"] = "with".join(_machine.split("with")[:-1]).strip()
543
- info["board"] = _machine
544
- info["cpu"] = _machine.split("with")[-1].strip()
545
- info["mpy"] = (
546
- sys.implementation._mpy # type: ignore
547
- if "_mpy" in dir(sys.implementation)
548
- else sys.implementation.mpy if "mpy" in dir(sys.implementation) else "" # type: ignore
549
- )
550
- except (AttributeError, IndexError):
551
- pass
552
- info["board"] = get_boardname()
553
-
554
- try:
555
- if "uname" in dir(os): # old
556
- # extract build from uname().version if available
557
- info["build"] = _build(os.uname()[3]) # type: ignore
558
- if not info["build"]:
559
- # extract build from uname().release if available
560
- info["build"] = _build(os.uname()[2]) # type: ignore
561
- elif "version" in dir(sys): # new
562
- # extract build from sys.version if available
563
- info["build"] = _build(sys.version)
564
- except (AttributeError, IndexError, TypeError):
565
- pass
566
- # avoid build hashes
567
- # if info["build"] and len(info["build"]) > 5:
568
- # info["build"] = ""
569
-
570
- if info["version"] == "" and sys.platform not in ("unix", "win32"):
571
- try:
572
- u = os.uname() # type: ignore
573
- info["version"] = u.release
574
- except (IndexError, AttributeError, TypeError):
575
- pass
576
- # detect families
577
- for fam_name, mod_name, mod_thing in [
578
- ("pycopy", "pycopy", "const"),
579
- ("pycom", "pycom", "FAT"),
580
- ("ev3-pybricks", "pybricks.hubs", "EV3Brick"),
581
- ]:
582
- try:
583
- _t = __import__(mod_name, None, None, (mod_thing))
584
- info["family"] = fam_name
585
- del _t
586
- break
587
- except (ImportError, KeyError):
588
- pass
589
-
590
- if info["family"] == "ev3-pybricks":
591
- info["release"] = "2.0.0"
592
-
593
- if info["family"] == "micropython":
594
- info["version"]
595
- if (
596
- info["version"]
597
- and info["version"].endswith(".0")
598
- and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.20.5 do not have a micro .0
599
- and info["version"] <= "1.19.9"
600
- ):
601
- # versions from 1.10.0 to 1.20.5 do not have a micro .0
602
- info["version"] = info["version"][:-2]
603
-
604
- # spell-checker: disable
605
- if "mpy" in info and info["mpy"]: # mpy on some v1.11+ builds
606
- sys_mpy = int(info["mpy"])
607
- # .mpy architecture
608
- arch = [
609
- None,
610
- "x86",
611
- "x64",
612
- "armv6",
613
- "armv6m",
614
- "armv7m",
615
- "armv7em",
616
- "armv7emsp",
617
- "armv7emdp",
618
- "xtensa",
619
- "xtensawin",
620
- ][sys_mpy >> 10]
621
- if arch:
622
- info["arch"] = arch
623
- # .mpy version.minor
624
- info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3)
625
- if info["build"] and not info["version"].endswith("-preview"):
626
- info["version"] = info["version"] + "-preview"
627
- # simple to use version[-build] string
628
- info["ver"] = f"{info['version']}-{info['build']}" if info["build"] else f"{info['version']}"
629
-
630
- return info
631
-
632
-
633
- def version_str(version: tuple): # -> str:
634
- v_str = ".".join([str(n) for n in version[:3]])
635
- if len(version) > 3 and version[3]:
636
- v_str += "-" + version[3]
637
- return v_str
638
-
639
-
640
- def get_boardname() -> str:
641
- "Read the board name from the boardname.py file that may have been created upfront"
642
- try:
643
- from boardname import BOARDNAME # type: ignore
644
-
645
- log.info("Found BOARDNAME: {}".format(BOARDNAME))
646
- except ImportError:
647
- log.warning("BOARDNAME not found")
648
- BOARDNAME = ""
649
- return BOARDNAME
650
-
651
-
652
- def get_root() -> str: # sourcery skip: use-assigned-variable
653
- "Determine the root folder of the device"
654
- try:
655
- c = os.getcwd()
656
- except (OSError, AttributeError):
657
- # unix port
658
- c = "."
659
- r = c
660
- for r in [c, "/sd", "/flash", "/", "."]:
661
- try:
662
- _ = os.stat(r)
663
- break
664
- except OSError:
665
- continue
666
- return r
667
-
668
-
669
- def file_exists(filename: str):
670
- try:
671
- if os.stat(filename)[0] >> 14:
672
- return True
673
- return False
674
- except OSError:
675
- return False
676
-
677
-
678
- def show_help():
679
- print("-p, --path path to store the stubs in, defaults to '.'")
680
- sys.exit(1)
681
-
682
-
683
- def read_path() -> str:
684
- "get --path from cmdline. [unix/win]"
685
- path = ""
686
- if len(sys.argv) == 3:
687
- cmd = (sys.argv[1]).lower()
688
- if cmd in ("--path", "-p"):
689
- path = sys.argv[2]
690
- else:
691
- show_help()
692
- elif len(sys.argv) == 2:
693
- show_help()
694
- return path
695
-
696
-
697
- def is_micropython() -> bool:
698
- "runtime test to determine full or micropython"
699
- # pylint: disable=unused-variable,eval-used
700
- try:
701
- # either test should fail on micropython
702
-
703
- # b) https://docs.micropython.org/en/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented
704
- # Micropython: NotImplementedError
705
- b = bytes("abc", encoding="utf8") # type: ignore # lgtm [py/unused-local-variable]
706
-
707
- # c) https://docs.micropython.org/en/latest/genrst/core_language.html#function-objects-do-not-have-the-module-attribute
708
- # Micropython: AttributeError
709
- c = is_micropython.__module__ # type: ignore # lgtm [py/unused-local-variable]
710
- return False
711
- except (NotImplementedError, AttributeError):
712
- return True
713
-
714
-
715
- def main():
716
- stubber = Stubber(path=read_path())
717
- # stubber = Stubber(path="/sd")
718
- # Option: Specify a firmware name & version
719
- # stubber = Stubber(firmware_id='HoverBot v1.2.1')
720
- stubber.clean()
721
- # there is no option to discover modules from micropython, need to hardcode
722
- # below contains combined modules from Micropython ESP8622, ESP32, Loboris, Pycom and ulab , lvgl
723
- # spell-checker: disable
724
- # modules to stub : 131
725
- stubber.modules = [
726
- "WM8960",
727
- "_OTA",
728
- "_asyncio",
729
- "_boot_fat",
730
- "_coap",
731
- "_espnow",
732
- "_flash_control_OTA",
733
- "_main_pybytes",
734
- "_mqtt",
735
- "_mqtt_core",
736
- "_msg_handl",
737
- "_onewire",
738
- "_periodical_pin",
739
- "_pybytes",
740
- "_pybytes_ca",
741
- "_pybytes_config",
742
- "_pybytes_config_reader",
743
- "_pybytes_connection",
744
- "_pybytes_constants",
745
- "_pybytes_debug",
746
- "_pybytes_library",
747
- "_pybytes_machine_learning",
748
- "_pybytes_main",
749
- "_pybytes_protocol",
750
- "_pybytes_pyconfig",
751
- "_pybytes_pymesh_config",
752
- "_rp2",
753
- "_terminal",
754
- "_thread",
755
- "_uasyncio",
756
- "_urequest",
757
- "adcfft",
758
- "aioble/__init__",
759
- "aioble/central",
760
- "aioble/client",
761
- "aioble/core",
762
- "aioble/device",
763
- "aioble/l2cap",
764
- "aioble/peripheral",
765
- "aioble/security",
766
- "aioble/server",
767
- "aioespnow",
768
- "ak8963",
769
- "apa102",
770
- "apa106",
771
- "argparse",
772
- "array",
773
- "asyncio/__init__",
774
- "asyncio/core",
775
- "asyncio/event",
776
- "asyncio/funcs",
777
- "asyncio/lock",
778
- "asyncio/stream",
779
- "binascii",
780
- "bluetooth",
781
- "breakout_as7262",
782
- "breakout_bh1745",
783
- "breakout_bme280",
784
- "breakout_bme68x",
785
- "breakout_bmp280",
786
- "breakout_dotmatrix",
787
- "breakout_encoder",
788
- "breakout_icp10125",
789
- "breakout_ioexpander",
790
- "breakout_ltr559",
791
- "breakout_matrix11x7",
792
- "breakout_mics6814",
793
- "breakout_msa301",
794
- "breakout_paa5100",
795
- "breakout_pmw3901",
796
- "breakout_potentiometer",
797
- "breakout_rgbmatrix5x5",
798
- "breakout_rtc",
799
- "breakout_scd41",
800
- "breakout_sgp30",
801
- "breakout_trackball",
802
- "breakout_vl53l5cx",
803
- "btree",
804
- "cmath",
805
- "collections",
806
- "crypto",
807
- "cryptolib",
808
- "curl",
809
- "deflate",
810
- "dht",
811
- "display",
812
- "display_driver_utils",
813
- "ds18x20",
814
- "encoder",
815
- "errno",
816
- "esp",
817
- "esp32",
818
- "espidf",
819
- "espnow",
820
- "ffi",
821
- "flashbdev",
822
- "framebuf",
823
- "freesans20",
824
- "fs_driver",
825
- "functools",
826
- "galactic",
827
- "gc",
828
- "gfx_pack",
829
- "gsm",
830
- "hashlib",
831
- "heapq",
832
- "hub75",
833
- "ili9341",
834
- "ili9XXX",
835
- "imagetools",
836
- "inisetup",
837
- "interstate75",
838
- "io",
839
- "jpegdec",
840
- "js",
841
- "jsffi",
842
- "json",
843
- "lcd160cr",
844
- "lodepng",
845
- "logging",
846
- "lsm6dsox",
847
- "lv_colors",
848
- "lv_utils",
849
- "lvgl",
850
- "lwip",
851
- "machine",
852
- "math",
853
- "microWebSocket",
854
- "microWebSrv",
855
- "microWebTemplate",
856
- "micropython",
857
- "mip",
858
- "mip/__init__",
859
- "mip/__main__",
860
- "motor",
861
- "mpu6500",
862
- "mpu9250",
863
- "neopixel",
864
- "network",
865
- "ntptime",
866
- "onewire",
867
- "openamp",
868
- "os",
869
- "pcf85063a",
870
- "picoexplorer",
871
- "picographics",
872
- "picokeypad",
873
- "picoscroll",
874
- "picounicorn",
875
- "picowireless",
876
- "pimoroni",
877
- "pimoroni_bus",
878
- "pimoroni_i2c",
879
- "plasma",
880
- "platform",
881
- "pyb",
882
- "pycom",
883
- "pye",
884
- "qrcode",
885
- "queue",
886
- "random",
887
- "requests",
888
- "requests/__init__",
889
- "rp2",
890
- "rtch",
891
- "samd",
892
- "select",
893
- "servo",
894
- "socket",
895
- "ssd1306",
896
- "ssh",
897
- "ssl",
898
- "stm",
899
- "struct",
900
- "sys",
901
- "termios",
902
- "time",
903
- "tls",
904
- "tpcalib",
905
- "uarray",
906
- "uasyncio/__init__",
907
- "uasyncio/core",
908
- "uasyncio/event",
909
- "uasyncio/funcs",
910
- "uasyncio/lock",
911
- "uasyncio/stream",
912
- "uasyncio/tasks",
913
- "ubinascii",
914
- "ubluetooth",
915
- "ucollections",
916
- "ucrypto",
917
- "ucryptolib",
918
- "uctypes",
919
- "uerrno",
920
- "uftpd",
921
- "uhashlib",
922
- "uheapq",
923
- "uio",
924
- "ujson",
925
- "ulab",
926
- "ulab/approx",
927
- "ulab/compare",
928
- "ulab/fft",
929
- "ulab/filter",
930
- "ulab/linalg",
931
- "ulab/numerical",
932
- "ulab/poly",
933
- "ulab/user",
934
- "ulab/vector",
935
- "umachine",
936
- "umqtt/__init__",
937
- "umqtt/robust",
938
- "umqtt/simple",
939
- "uos",
940
- "uplatform",
941
- "uqueue",
942
- "urandom",
943
- "ure",
944
- "urequests",
945
- "urllib/urequest",
946
- "usb/device",
947
- "usb/device/cdc",
948
- "usb/device/hid",
949
- "usb/device/keyboard",
950
- "usb/device/midi",
951
- "usb/device/mouse",
952
- "uselect",
953
- "usocket",
954
- "ussl",
955
- "ustruct",
956
- "usys",
957
- "utelnetserver",
958
- "utime",
959
- "utimeq",
960
- "uwebsocket",
961
- "uzlib",
962
- "version",
963
- "vfs",
964
- "websocket",
965
- "websocket_helper",
966
- "wipy",
967
- "writer",
968
- "xpt2046",
969
- "ymodem",
970
- "zephyr",
971
- "zlib",
972
- ] # spell-checker: enable
973
-
974
- gc.collect()
975
-
976
- stubber.create_all_stubs()
977
-
978
-
979
- if __name__ == "__main__" or is_micropython():
980
- if not file_exists("no_auto_stubber.txt"):
981
- try:
982
- gc.threshold(4 * 1024) # type: ignore
983
- gc.enable()
984
- except BaseException:
985
- pass
986
- main()
1
+ """
2
+ Create stubs for (all) modules on a MicroPython board
3
+ """
4
+
5
+ # Copyright (c) 2019-2024 Jos Verlinde
6
+
7
+ import gc
8
+ import os
9
+ import sys
10
+ from time import sleep
11
+
12
+ try:
13
+ from ujson import dumps
14
+ except:
15
+ from json import dumps
16
+
17
+ try:
18
+ from machine import reset # type: ignore
19
+ except ImportError:
20
+ pass
21
+
22
+ try:
23
+ from collections import OrderedDict
24
+ except ImportError:
25
+ from ucollections import OrderedDict # type: ignore
26
+
27
+ __version__ = "v1.23.0"
28
+ ENOENT = 2
29
+ _MAX_CLASS_LEVEL = 2 # Max class nesting
30
+ LIBS = ["lib", "/lib", "/sd/lib", "/flash/lib", "."]
31
+
32
+
33
+ # our own logging module to avoid dependency on and interfering with logging module
34
+ class logging:
35
+ # DEBUG = 10
36
+ INFO = 20
37
+ WARNING = 30
38
+ ERROR = 40
39
+ level = INFO
40
+ prnt = print
41
+
42
+ @staticmethod
43
+ def getLogger(name):
44
+ return logging()
45
+
46
+ @classmethod
47
+ def basicConfig(cls, level):
48
+ cls.level = level
49
+
50
+ # def debug(self, msg):
51
+ # if self.level <= logging.DEBUG:
52
+ # self.prnt("DEBUG :", msg)
53
+
54
+ def info(self, msg):
55
+ if self.level <= logging.INFO:
56
+ self.prnt("INFO :", msg)
57
+
58
+ def warning(self, msg):
59
+ if self.level <= logging.WARNING:
60
+ self.prnt("WARN :", msg)
61
+
62
+ def error(self, msg):
63
+ if self.level <= logging.ERROR:
64
+ self.prnt("ERROR :", msg)
65
+
66
+
67
+ log = logging.getLogger("stubber")
68
+ logging.basicConfig(level=logging.INFO)
69
+ # logging.basicConfig(level=logging.DEBUG)
70
+
71
+
72
+ class Stubber:
73
+ "Generate stubs for modules in firmware"
74
+
75
+ def __init__(self, path: str = None, firmware_id: str = None): # type: ignore
76
+ try:
77
+ if os.uname().release == "1.13.0" and os.uname().version < "v1.13-103": # type: ignore
78
+ raise NotImplementedError("MicroPython 1.13.0 cannot be stubbed")
79
+ except AttributeError:
80
+ pass # Allow testing on CPython 3.11
81
+ self.info = _info()
82
+ log.info("Port: {}".format(self.info["port"]))
83
+ log.info("Board: {}".format(self.info["board"]))
84
+ gc.collect()
85
+ if firmware_id:
86
+ self._fwid = firmware_id.lower()
87
+ else:
88
+ if self.info["family"] == "micropython":
89
+ self._fwid = "{family}-v{version}-{port}-{board}".format(**self.info).rstrip("-")
90
+ else:
91
+ self._fwid = "{family}-v{version}-{port}".format(**self.info)
92
+ self._start_free = gc.mem_free() # type: ignore
93
+
94
+ if path:
95
+ if path.endswith("/"):
96
+ path = path[:-1]
97
+ else:
98
+ path = get_root()
99
+
100
+ self.path = "{}/stubs/{}".format(path, self.flat_fwid).replace("//", "/")
101
+ # log.debug(self.path)
102
+ try:
103
+ ensure_folder(path + "/")
104
+ except OSError:
105
+ log.error("error creating stub folder {}".format(path))
106
+ self.problematic = [
107
+ "upip",
108
+ "upysh",
109
+ "webrepl_setup",
110
+ "http_client",
111
+ "http_client_ssl",
112
+ "http_server",
113
+ "http_server_ssl",
114
+ ]
115
+ self.excluded = [
116
+ "webrepl",
117
+ "_webrepl",
118
+ "port_diag",
119
+ "example_sub_led.py",
120
+ "example_pub_button.py",
121
+ ]
122
+ # there is no option to discover modules from micropython, list is read from an external file.
123
+ self.modules = [] # type: list[str]
124
+ self._json_name = None
125
+ self._json_first = False
126
+
127
+ def get_obj_attributes(self, item_instance: object):
128
+ "extract information of the objects members and attributes"
129
+ # name_, repr_(value), type as text, item_instance
130
+ _result = []
131
+ _errors = []
132
+ # log.debug("get attributes {} {}".format(repr(item_instance), item_instance))
133
+ for name in dir(item_instance):
134
+ if name.startswith("__") and not name in self.modules:
135
+ continue
136
+ # log.debug("get attribute {}".format(name))
137
+ try:
138
+ val = getattr(item_instance, name)
139
+ # name , item_repr(value) , type as text, item_instance, order
140
+ # log.debug("attribute {}:{}".format(name, val))
141
+ try:
142
+ type_text = repr(type(val)).split("'")[1]
143
+ except IndexError:
144
+ type_text = ""
145
+ if type_text in {"int", "float", "str", "bool", "tuple", "list", "dict"}:
146
+ order = 1
147
+ elif type_text in {"function", "method"}:
148
+ order = 2
149
+ elif type_text in ("class"):
150
+ order = 3
151
+ else:
152
+ order = 4
153
+ _result.append((name, repr(val), repr(type(val)), val, order))
154
+ except AttributeError as e:
155
+ _errors.append("Couldn't get attribute '{}' from object '{}', Err: {}".format(name, item_instance, e))
156
+ except MemoryError as e:
157
+ print("MemoryError: {}".format(e))
158
+ sleep(1)
159
+ reset()
160
+
161
+ # remove internal __
162
+ # _result = sorted([i for i in _result if not (i[0].startswith("_"))], key=lambda x: x[4])
163
+ _result = sorted([i for i in _result if not (i[0].startswith("__"))], key=lambda x: x[4])
164
+ gc.collect()
165
+ return _result, _errors
166
+
167
+ def add_modules(self, modules):
168
+ "Add additional modules to be exported"
169
+ self.modules = sorted(set(self.modules) | set(modules))
170
+
171
+ def create_all_stubs(self):
172
+ "Create stubs for all configured modules"
173
+ log.info("Start micropython-stubber {} on {}".format(__version__, self._fwid))
174
+ self.report_start()
175
+ gc.collect()
176
+ for module_name in self.modules:
177
+ self.create_one_stub(module_name)
178
+ self.report_end()
179
+ log.info("Finally done")
180
+
181
+ def create_one_stub(self, module_name: str):
182
+ if module_name in self.problematic:
183
+ log.warning("Skip module: {:<25} : Known problematic".format(module_name))
184
+ return False
185
+ if module_name in self.excluded:
186
+ log.warning("Skip module: {:<25} : Excluded".format(module_name))
187
+ return False
188
+
189
+ file_name = "{}/{}.pyi".format(self.path, module_name.replace(".", "/"))
190
+ gc.collect()
191
+ result = False
192
+ try:
193
+ result = self.create_module_stub(module_name, file_name)
194
+ except OSError:
195
+ return False
196
+ gc.collect()
197
+ return result
198
+
199
+ def create_module_stub(self, module_name: str, file_name: str = None) -> bool: # type: ignore
200
+ """Create a Stub of a single python module
201
+
202
+ Args:
203
+ - module_name (str): name of the module to document. This module will be imported.
204
+ - file_name (Optional[str]): the 'path/filename.pyi' to write to. If omitted will be created based on the module name.
205
+ """
206
+ if file_name is None:
207
+ fname = module_name.replace(".", "_") + ".pyi"
208
+ file_name = self.path + "/" + fname
209
+ else:
210
+ fname = file_name.split("/")[-1]
211
+
212
+ if "/" in module_name:
213
+ # for nested modules
214
+ module_name = module_name.replace("/", ".")
215
+
216
+ # import the module (as new_module) to examine it
217
+ new_module = None
218
+ try:
219
+ new_module = __import__(module_name, None, None, ("*"))
220
+ m1 = gc.mem_free() # type: ignore
221
+ log.info("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1))
222
+
223
+ except ImportError:
224
+ # log.debug("Skip module: {:<25} {:<79}".format(module_name, "Module not found."))
225
+ return False
226
+
227
+ # Start a new file
228
+ ensure_folder(file_name)
229
+ with open(file_name, "w") as fp:
230
+ info_ = str(self.info).replace("OrderedDict(", "").replace("})", "}")
231
+ s = '"""\nModule: \'{0}\' on {1}\n"""\n# MCU: {2}\n# Stubber: {3}\n'.format(
232
+ module_name, self._fwid, info_, __version__
233
+ )
234
+ fp.write(s)
235
+ fp.write(
236
+ "from __future__ import annotations\nfrom typing import Any, Generator\nfrom _typeshed import Incomplete\n\n"
237
+ )
238
+ self.write_object_stub(fp, new_module, module_name, "")
239
+
240
+ self.report_add(module_name, file_name)
241
+
242
+ if module_name not in {"os", "sys", "logging", "gc"}:
243
+ # try to unload the module unless we use it
244
+ try:
245
+ del new_module
246
+ except (OSError, KeyError): # lgtm [py/unreachable-statement]
247
+ log.warning("could not del new_module")
248
+ # do not try to delete from sys.modules - most times it does not work anyway
249
+ gc.collect()
250
+ return True
251
+
252
+ def write_object_stub(self, fp, object_expr: object, obj_name: str, indent: str, in_class: int = 0):
253
+ "Write a module/object stub to an open file. Can be called recursive."
254
+ gc.collect()
255
+ if object_expr in self.problematic:
256
+ log.warning("SKIPPING problematic module:{}".format(object_expr))
257
+ return
258
+
259
+ # # log.debug("DUMP : {}".format(object_expr))
260
+ items, errors = self.get_obj_attributes(object_expr)
261
+
262
+ if errors:
263
+ log.error(errors)
264
+
265
+ for item_name, item_repr, item_type_txt, item_instance, _ in items:
266
+ # name_, repr_(value), type as text, item_instance, order
267
+ if item_name in ["classmethod", "staticmethod", "BaseException", "Exception"]:
268
+ # do not create stubs for these primitives
269
+ continue
270
+ if item_name[0].isdigit():
271
+ log.warning("NameError: invalid name {}".format(item_name))
272
+ continue
273
+ # Class expansion only on first 3 levels (bit of a hack)
274
+ if (
275
+ item_type_txt == "<class 'type'>"
276
+ and len(indent) <= _MAX_CLASS_LEVEL * 4
277
+ # and not obj_name.endswith(".Pin")
278
+ # avoid expansion of Pin.cpu / Pin.board to avoid crashes on most platforms
279
+ ):
280
+ # log.debug("{0}class {1}:".format(indent, item_name))
281
+ superclass = ""
282
+ is_exception = (
283
+ item_name.endswith("Exception")
284
+ or item_name.endswith("Error")
285
+ or item_name
286
+ in [
287
+ "KeyboardInterrupt",
288
+ "StopIteration",
289
+ "SystemExit",
290
+ ]
291
+ )
292
+ if is_exception:
293
+ superclass = "Exception"
294
+ s = "\n{}class {}({}):\n".format(indent, item_name, superclass)
295
+ # s += indent + " ''\n"
296
+ if is_exception:
297
+ s += indent + " ...\n"
298
+ fp.write(s)
299
+ continue
300
+ # write classdef
301
+ fp.write(s)
302
+ # first write the class literals and methods
303
+ # log.debug("# recursion over class {0}".format(item_name))
304
+ self.write_object_stub(
305
+ fp,
306
+ item_instance,
307
+ "{0}.{1}".format(obj_name, item_name),
308
+ indent + " ",
309
+ in_class + 1,
310
+ )
311
+ # end with the __init__ method to make sure that the literals are defined
312
+ # Add __init__
313
+ s = indent + " def __init__(self, *argv, **kwargs) -> None:\n"
314
+ s += indent + " ...\n\n"
315
+ fp.write(s)
316
+ elif any(word in item_type_txt for word in ["method", "function", "closure"]):
317
+ # log.debug("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name))
318
+ # module Function or class method
319
+ # will accept any number of params
320
+ # return type Any/Incomplete
321
+ ret = "Incomplete"
322
+ first = ""
323
+ # Self parameter only on class methods/functions
324
+ if in_class > 0:
325
+ first = "self, "
326
+ # class method - add function decoration
327
+ if "bound_method" in item_type_txt or "bound_method" in item_repr:
328
+ s = "{}@classmethod\n".format(indent) + "{}def {}(cls, *args, **kwargs) -> {}:\n".format(
329
+ indent, item_name, ret
330
+ )
331
+ else:
332
+ s = "{}def {}({}*args, **kwargs) -> {}:\n".format(indent, item_name, first, ret)
333
+ s += indent + " ...\n\n"
334
+ fp.write(s)
335
+ # log.debug("\n" + s)
336
+ elif item_type_txt == "<class 'module'>":
337
+ # Skip imported modules
338
+ # fp.write("# import {}\n".format(item_name))
339
+ pass
340
+
341
+ elif item_type_txt.startswith("<class '"):
342
+ t = item_type_txt[8:-2]
343
+ s = ""
344
+
345
+ if t in ("str", "int", "float", "bool", "bytearray", "bytes"):
346
+ # known type: use actual value
347
+ # s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, item_repr, t)
348
+ s = "{0}{1}: {3} = {2}\n".format(indent, item_name, item_repr, t)
349
+ elif t in ("dict", "list", "tuple"):
350
+ # dict, list , tuple: use empty value
351
+ ev = {"dict": "{}", "list": "[]", "tuple": "()"}
352
+ # s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, ev[t], t)
353
+ s = "{0}{1}: {3} = {2}\n".format(indent, item_name, ev[t], t)
354
+ else:
355
+ # something else
356
+ if t in ("object", "set", "frozenset", "Pin", "generator"): # "FileIO"
357
+ # https://docs.python.org/3/tutorial/classes.html#item_instance-objects
358
+ # use these types for the attribute
359
+ if t == "generator":
360
+ t = "Generator"
361
+ s = "{0}{1}: {2} ## = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
362
+ else:
363
+ # Requires Python 3.6 syntax, which is OK for the stubs/pyi
364
+ t = "Incomplete"
365
+ if " at " in item_repr:
366
+ item_repr = item_repr.split(" at ")[0] + " at ...>"
367
+ if " at " in item_repr:
368
+ item_repr = item_repr.split(" at ")[0] + " at ...>"
369
+ s = "{0}{1}: {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
370
+ fp.write(s)
371
+ # log.debug("\n" + s)
372
+ else:
373
+ # keep only the name
374
+ # log.debug("# all other, type = '{0}'".format(item_type_txt))
375
+ fp.write("# all other, type = '{0}'\n".format(item_type_txt))
376
+
377
+ fp.write(indent + item_name + " # type: Incomplete\n")
378
+
379
+ # del items
380
+ # del errors
381
+ # try:
382
+ # del item_name, item_repr, item_type_txt, item_instance # type: ignore
383
+ # except (OSError, KeyError, NameError):
384
+ # pass
385
+
386
+ @property
387
+ def flat_fwid(self):
388
+ "Turn _fwid from 'v1.2.3' into '1_2_3' to be used in filename"
389
+ s = self._fwid
390
+ # path name restrictions
391
+ chars = " .()/\\:$"
392
+ for c in chars:
393
+ s = s.replace(c, "_")
394
+ return s
395
+
396
+ def clean(self, path: str = None): # type: ignore
397
+ "Remove all files from the stub folder"
398
+ if path is None:
399
+ path = self.path
400
+ log.info("Clean/remove files in folder: {}".format(path))
401
+ try:
402
+ os.stat(path) # TEMP workaround mpremote listdir bug -
403
+ items = os.listdir(path)
404
+ except (OSError, AttributeError):
405
+ # os.listdir fails on unix
406
+ return
407
+ for fn in items:
408
+ item = "{}/{}".format(path, fn)
409
+ try:
410
+ os.remove(item)
411
+ except OSError:
412
+ try: # folder
413
+ self.clean(item)
414
+ os.rmdir(item)
415
+ except OSError:
416
+ pass
417
+
418
+ def report_start(self, filename: str = "modules.json"):
419
+ """Start a report of the modules that have been stubbed
420
+ "create json with list of exported modules"""
421
+ self._json_name = "{}/{}".format(self.path, filename)
422
+ self._json_first = True
423
+ ensure_folder(self._json_name)
424
+ log.info("Report file: {}".format(self._json_name))
425
+ gc.collect()
426
+ try:
427
+ # write json by node to reduce memory requirements
428
+ with open(self._json_name, "w") as f:
429
+ f.write("{")
430
+ f.write(dumps({"firmware": self.info})[1:-1])
431
+ f.write(",\n")
432
+ f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1])
433
+ f.write(",\n")
434
+ f.write('"modules" :[\n')
435
+
436
+ except OSError as e:
437
+ log.error("Failed to create the report.")
438
+ self._json_name = None
439
+ raise e
440
+
441
+ def report_add(self, module_name: str, stub_file: str):
442
+ "Add a module to the report"
443
+ # write json by node to reduce memory requirements
444
+ if not self._json_name:
445
+ raise Exception("No report file")
446
+ try:
447
+ with open(self._json_name, "a") as f:
448
+ if not self._json_first:
449
+ f.write(",\n")
450
+ else:
451
+ self._json_first = False
452
+ line = '{{"module": "{}", "file": "{}"}}'.format(module_name, stub_file.replace("\\", "/"))
453
+ f.write(line)
454
+
455
+ except OSError:
456
+ log.error("Failed to create the report.")
457
+
458
+ def report_end(self):
459
+ if not self._json_name:
460
+ raise Exception("No report file")
461
+ with open(self._json_name, "a") as f:
462
+ f.write("\n]}")
463
+ # is used as sucess indicator
464
+ log.info("Path: {}".format(self.path))
465
+
466
+
467
+ def ensure_folder(path: str):
468
+ "Create nested folders if needed"
469
+ i = start = 0
470
+ while i != -1:
471
+ i = path.find("/", start)
472
+ if i != -1:
473
+ p = path[0] if i == 0 else path[:i]
474
+ # p = partial folder
475
+ try:
476
+ _ = os.stat(p)
477
+ except OSError as e:
478
+ # folder does not exist
479
+ if e.args[0] == ENOENT:
480
+ try:
481
+ os.mkdir(p)
482
+ except OSError as e2:
483
+ log.error("failed to create folder {}".format(p))
484
+ raise e2
485
+ # next level deep
486
+ start = i + 1
487
+
488
+
489
+ def _build(s):
490
+ # extract build from sys.version or os.uname().version if available
491
+ # sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f'
492
+ # sys.implementation.version: 'v1.13-103-gb137d064e'
493
+ if not s:
494
+ return ""
495
+ s = s.split(" on ", 1)[0] if " on " in s else s
496
+ if s.startswith("v"):
497
+ if not "-" in s:
498
+ return ""
499
+ b = s.split("-")[1]
500
+ return b
501
+ if not "-preview" in s:
502
+ return ""
503
+ b = s.split("-preview")[1].split(".")[1]
504
+ return b
505
+
506
+
507
+ def _info(): # type:() -> dict[str, str]
508
+ try:
509
+ fam = sys.implementation[0] # type: ignore
510
+ except TypeError:
511
+ # testing on CPython 3.11
512
+ fam = sys.implementation.name
513
+
514
+ info = OrderedDict(
515
+ {
516
+ "family": fam,
517
+ "version": "",
518
+ "build": "",
519
+ "ver": "",
520
+ "port": sys.platform, # port: esp32 / win32 / linux / stm32
521
+ "board": "UNKNOWN",
522
+ "cpu": "",
523
+ "mpy": "",
524
+ "arch": "",
525
+ }
526
+ )
527
+ # change port names to be consistent with the repo
528
+ if info["port"].startswith("pyb"):
529
+ info["port"] = "stm32"
530
+ elif info["port"] == "win32":
531
+ info["port"] = "windows"
532
+ elif info["port"] == "linux":
533
+ info["port"] = "unix"
534
+ try:
535
+ info["version"] = version_str(sys.implementation.version) # type: ignore
536
+ except AttributeError:
537
+ pass
538
+ try:
539
+ _machine = (
540
+ sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore
541
+ )
542
+ # info["board"] = "with".join(_machine.split("with")[:-1]).strip()
543
+ info["board"] = _machine
544
+ info["cpu"] = _machine.split("with")[-1].strip()
545
+ info["mpy"] = (
546
+ sys.implementation._mpy # type: ignore
547
+ if "_mpy" in dir(sys.implementation)
548
+ else sys.implementation.mpy if "mpy" in dir(sys.implementation) else "" # type: ignore
549
+ )
550
+ except (AttributeError, IndexError):
551
+ pass
552
+ info["board"] = get_boardname()
553
+
554
+ try:
555
+ if "uname" in dir(os): # old
556
+ # extract build from uname().version if available
557
+ info["build"] = _build(os.uname()[3]) # type: ignore
558
+ if not info["build"]:
559
+ # extract build from uname().release if available
560
+ info["build"] = _build(os.uname()[2]) # type: ignore
561
+ elif "version" in dir(sys): # new
562
+ # extract build from sys.version if available
563
+ info["build"] = _build(sys.version)
564
+ except (AttributeError, IndexError, TypeError):
565
+ pass
566
+ # avoid build hashes
567
+ # if info["build"] and len(info["build"]) > 5:
568
+ # info["build"] = ""
569
+
570
+ if info["version"] == "" and sys.platform not in ("unix", "win32"):
571
+ try:
572
+ u = os.uname() # type: ignore
573
+ info["version"] = u.release
574
+ except (IndexError, AttributeError, TypeError):
575
+ pass
576
+ # detect families
577
+ for fam_name, mod_name, mod_thing in [
578
+ ("pycopy", "pycopy", "const"),
579
+ ("pycom", "pycom", "FAT"),
580
+ ("ev3-pybricks", "pybricks.hubs", "EV3Brick"),
581
+ ]:
582
+ try:
583
+ _t = __import__(mod_name, None, None, (mod_thing))
584
+ info["family"] = fam_name
585
+ del _t
586
+ break
587
+ except (ImportError, KeyError):
588
+ pass
589
+
590
+ if info["family"] == "ev3-pybricks":
591
+ info["release"] = "2.0.0"
592
+
593
+ if info["family"] == "micropython":
594
+ info["version"]
595
+ if (
596
+ info["version"]
597
+ and info["version"].endswith(".0")
598
+ and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.23.0 do not have a micro .0
599
+ and info["version"] <= "1.19.9"
600
+ ):
601
+ # versions from 1.10.0 to 1.23.0 do not have a micro .0
602
+ info["version"] = info["version"][:-2]
603
+
604
+ # spell-checker: disable
605
+ if "mpy" in info and info["mpy"]: # mpy on some v1.11+ builds
606
+ sys_mpy = int(info["mpy"])
607
+ # .mpy architecture
608
+ arch = [
609
+ None,
610
+ "x86",
611
+ "x64",
612
+ "armv6",
613
+ "armv6m",
614
+ "armv7m",
615
+ "armv7em",
616
+ "armv7emsp",
617
+ "armv7emdp",
618
+ "xtensa",
619
+ "xtensawin",
620
+ ][sys_mpy >> 10]
621
+ if arch:
622
+ info["arch"] = arch
623
+ # .mpy version.minor
624
+ info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3)
625
+ if info["build"] and not info["version"].endswith("-preview"):
626
+ info["version"] = info["version"] + "-preview"
627
+ # simple to use version[-build] string
628
+ info["ver"] = f"{info['version']}-{info['build']}" if info["build"] else f"{info['version']}"
629
+
630
+ return info
631
+
632
+
633
+ def version_str(version: tuple): # -> str:
634
+ v_str = ".".join([str(n) for n in version[:3]])
635
+ if len(version) > 3 and version[3]:
636
+ v_str += "-" + version[3]
637
+ return v_str
638
+
639
+
640
+ def get_boardname() -> str:
641
+ "Read the board name from the boardname.py file that may have been created upfront"
642
+ try:
643
+ from boardname import BOARDNAME # type: ignore
644
+
645
+ log.info("Found BOARDNAME: {}".format(BOARDNAME))
646
+ except ImportError:
647
+ log.warning("BOARDNAME not found")
648
+ BOARDNAME = ""
649
+ return BOARDNAME
650
+
651
+
652
+ def get_root() -> str: # sourcery skip: use-assigned-variable
653
+ "Determine the root folder of the device"
654
+ try:
655
+ c = os.getcwd()
656
+ except (OSError, AttributeError):
657
+ # unix port
658
+ c = "."
659
+ r = c
660
+ for r in [c, "/sd", "/flash", "/", "."]:
661
+ try:
662
+ _ = os.stat(r)
663
+ break
664
+ except OSError:
665
+ continue
666
+ return r
667
+
668
+
669
+ def file_exists(filename: str):
670
+ try:
671
+ if os.stat(filename)[0] >> 14:
672
+ return True
673
+ return False
674
+ except OSError:
675
+ return False
676
+
677
+
678
+ def show_help():
679
+ print("-p, --path path to store the stubs in, defaults to '.'")
680
+ sys.exit(1)
681
+
682
+
683
+ def read_path() -> str:
684
+ "get --path from cmdline. [unix/win]"
685
+ path = ""
686
+ if len(sys.argv) == 3:
687
+ cmd = (sys.argv[1]).lower()
688
+ if cmd in ("--path", "-p"):
689
+ path = sys.argv[2]
690
+ else:
691
+ show_help()
692
+ elif len(sys.argv) == 2:
693
+ show_help()
694
+ return path
695
+
696
+
697
+ def is_micropython() -> bool:
698
+ "runtime test to determine full or micropython"
699
+ # pylint: disable=unused-variable,eval-used
700
+ try:
701
+ # either test should fail on micropython
702
+
703
+ # b) https://docs.micropython.org/en/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented
704
+ # Micropython: NotImplementedError
705
+ b = bytes("abc", encoding="utf8") # type: ignore # lgtm [py/unused-local-variable]
706
+
707
+ # c) https://docs.micropython.org/en/latest/genrst/core_language.html#function-objects-do-not-have-the-module-attribute
708
+ # Micropython: AttributeError
709
+ c = is_micropython.__module__ # type: ignore # lgtm [py/unused-local-variable]
710
+ return False
711
+ except (NotImplementedError, AttributeError):
712
+ return True
713
+
714
+
715
+ def main():
716
+ stubber = Stubber(path=read_path())
717
+ # stubber = Stubber(path="/sd")
718
+ # Option: Specify a firmware name & version
719
+ # stubber = Stubber(firmware_id='HoverBot v1.2.1')
720
+ stubber.clean()
721
+ # there is no option to discover modules from micropython, need to hardcode
722
+ # below contains combined modules from Micropython ESP8622, ESP32, Loboris, Pycom and ulab , lvgl
723
+ # spell-checker: disable
724
+ # modules to stub : 131
725
+ stubber.modules = [
726
+ "WM8960",
727
+ "_OTA",
728
+ "_asyncio",
729
+ "_boot_fat",
730
+ "_coap",
731
+ "_espnow",
732
+ "_flash_control_OTA",
733
+ "_main_pybytes",
734
+ "_mqtt",
735
+ "_mqtt_core",
736
+ "_msg_handl",
737
+ "_onewire",
738
+ "_periodical_pin",
739
+ "_pybytes",
740
+ "_pybytes_ca",
741
+ "_pybytes_config",
742
+ "_pybytes_config_reader",
743
+ "_pybytes_connection",
744
+ "_pybytes_constants",
745
+ "_pybytes_debug",
746
+ "_pybytes_library",
747
+ "_pybytes_machine_learning",
748
+ "_pybytes_main",
749
+ "_pybytes_protocol",
750
+ "_pybytes_pyconfig",
751
+ "_pybytes_pymesh_config",
752
+ "_rp2",
753
+ "_terminal",
754
+ "_thread",
755
+ "_uasyncio",
756
+ "_urequest",
757
+ "adcfft",
758
+ "aioble/__init__",
759
+ "aioble/central",
760
+ "aioble/client",
761
+ "aioble/core",
762
+ "aioble/device",
763
+ "aioble/l2cap",
764
+ "aioble/peripheral",
765
+ "aioble/security",
766
+ "aioble/server",
767
+ "aioespnow",
768
+ "ak8963",
769
+ "apa102",
770
+ "apa106",
771
+ "argparse",
772
+ "array",
773
+ "asyncio/__init__",
774
+ "asyncio/core",
775
+ "asyncio/event",
776
+ "asyncio/funcs",
777
+ "asyncio/lock",
778
+ "asyncio/stream",
779
+ "binascii",
780
+ "bluetooth",
781
+ "breakout_as7262",
782
+ "breakout_bh1745",
783
+ "breakout_bme280",
784
+ "breakout_bme68x",
785
+ "breakout_bmp280",
786
+ "breakout_dotmatrix",
787
+ "breakout_encoder",
788
+ "breakout_icp10125",
789
+ "breakout_ioexpander",
790
+ "breakout_ltr559",
791
+ "breakout_matrix11x7",
792
+ "breakout_mics6814",
793
+ "breakout_msa301",
794
+ "breakout_paa5100",
795
+ "breakout_pmw3901",
796
+ "breakout_potentiometer",
797
+ "breakout_rgbmatrix5x5",
798
+ "breakout_rtc",
799
+ "breakout_scd41",
800
+ "breakout_sgp30",
801
+ "breakout_trackball",
802
+ "breakout_vl53l5cx",
803
+ "btree",
804
+ "cmath",
805
+ "collections",
806
+ "crypto",
807
+ "cryptolib",
808
+ "curl",
809
+ "deflate",
810
+ "dht",
811
+ "display",
812
+ "display_driver_utils",
813
+ "ds18x20",
814
+ "encoder",
815
+ "errno",
816
+ "esp",
817
+ "esp32",
818
+ "espidf",
819
+ "espnow",
820
+ "ffi",
821
+ "flashbdev",
822
+ "framebuf",
823
+ "freesans20",
824
+ "fs_driver",
825
+ "functools",
826
+ "galactic",
827
+ "gc",
828
+ "gfx_pack",
829
+ "gsm",
830
+ "hashlib",
831
+ "heapq",
832
+ "hub75",
833
+ "ili9341",
834
+ "ili9XXX",
835
+ "imagetools",
836
+ "inisetup",
837
+ "interstate75",
838
+ "io",
839
+ "jpegdec",
840
+ "js",
841
+ "jsffi",
842
+ "json",
843
+ "lcd160cr",
844
+ "lodepng",
845
+ "logging",
846
+ "lsm6dsox",
847
+ "lv_colors",
848
+ "lv_utils",
849
+ "lvgl",
850
+ "lwip",
851
+ "machine",
852
+ "math",
853
+ "microWebSocket",
854
+ "microWebSrv",
855
+ "microWebTemplate",
856
+ "micropython",
857
+ "mip",
858
+ "mip/__init__",
859
+ "mip/__main__",
860
+ "motor",
861
+ "mpu6500",
862
+ "mpu9250",
863
+ "neopixel",
864
+ "network",
865
+ "ntptime",
866
+ "onewire",
867
+ "openamp",
868
+ "os",
869
+ "pcf85063a",
870
+ "picoexplorer",
871
+ "picographics",
872
+ "picokeypad",
873
+ "picoscroll",
874
+ "picounicorn",
875
+ "picowireless",
876
+ "pimoroni",
877
+ "pimoroni_bus",
878
+ "pimoroni_i2c",
879
+ "plasma",
880
+ "platform",
881
+ "pyb",
882
+ "pycom",
883
+ "pye",
884
+ "qrcode",
885
+ "queue",
886
+ "random",
887
+ "requests",
888
+ "requests/__init__",
889
+ "rp2",
890
+ "rtch",
891
+ "samd",
892
+ "select",
893
+ "servo",
894
+ "socket",
895
+ "ssd1306",
896
+ "ssh",
897
+ "ssl",
898
+ "stm",
899
+ "struct",
900
+ "sys",
901
+ "termios",
902
+ "time",
903
+ "tls",
904
+ "tpcalib",
905
+ "uarray",
906
+ "uasyncio/__init__",
907
+ "uasyncio/core",
908
+ "uasyncio/event",
909
+ "uasyncio/funcs",
910
+ "uasyncio/lock",
911
+ "uasyncio/stream",
912
+ "uasyncio/tasks",
913
+ "ubinascii",
914
+ "ubluetooth",
915
+ "ucollections",
916
+ "ucrypto",
917
+ "ucryptolib",
918
+ "uctypes",
919
+ "uerrno",
920
+ "uftpd",
921
+ "uhashlib",
922
+ "uheapq",
923
+ "uio",
924
+ "ujson",
925
+ "ulab",
926
+ "ulab/approx",
927
+ "ulab/compare",
928
+ "ulab/fft",
929
+ "ulab/filter",
930
+ "ulab/linalg",
931
+ "ulab/numerical",
932
+ "ulab/poly",
933
+ "ulab/user",
934
+ "ulab/vector",
935
+ "umachine",
936
+ "umqtt/__init__",
937
+ "umqtt/robust",
938
+ "umqtt/simple",
939
+ "uos",
940
+ "uplatform",
941
+ "uqueue",
942
+ "urandom",
943
+ "ure",
944
+ "urequests",
945
+ "urllib/urequest",
946
+ "usb/device",
947
+ "usb/device/cdc",
948
+ "usb/device/hid",
949
+ "usb/device/keyboard",
950
+ "usb/device/midi",
951
+ "usb/device/mouse",
952
+ "uselect",
953
+ "usocket",
954
+ "ussl",
955
+ "ustruct",
956
+ "usys",
957
+ "utelnetserver",
958
+ "utime",
959
+ "utimeq",
960
+ "uwebsocket",
961
+ "uzlib",
962
+ "version",
963
+ "vfs",
964
+ "websocket",
965
+ "websocket_helper",
966
+ "wipy",
967
+ "writer",
968
+ "xpt2046",
969
+ "ymodem",
970
+ "zephyr",
971
+ "zlib",
972
+ ] # spell-checker: enable
973
+
974
+ gc.collect()
975
+
976
+ stubber.create_all_stubs()
977
+
978
+
979
+ if __name__ == "__main__" or is_micropython():
980
+ if not file_exists("no_auto_stubber.txt"):
981
+ try:
982
+ gc.threshold(4 * 1024) # type: ignore
983
+ gc.enable()
984
+ except BaseException:
985
+ pass
986
+ main()