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