micropython-stubber 1.23.1.post1__py3-none-any.whl → 1.23.3__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 (149) hide show
  1. {micropython_stubber-1.23.1.post1.dist-info → micropython_stubber-1.23.3.dist-info}/LICENSE +30 -30
  2. {micropython_stubber-1.23.1.post1.dist-info → micropython_stubber-1.23.3.dist-info}/METADATA +5 -5
  3. micropython_stubber-1.23.3.dist-info/RECORD +158 -0
  4. mpflash/README.md +220 -220
  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/basicgit.py +304 -284
  9. mpflash/mpflash/bootloader/__init__.py +2 -2
  10. mpflash/mpflash/bootloader/activate.py +60 -60
  11. mpflash/mpflash/bootloader/detect.py +82 -82
  12. mpflash/mpflash/bootloader/manual.py +101 -101
  13. mpflash/mpflash/bootloader/micropython.py +12 -12
  14. mpflash/mpflash/bootloader/touch1200.py +36 -36
  15. mpflash/mpflash/cli_download.py +129 -129
  16. mpflash/mpflash/cli_flash.py +224 -216
  17. mpflash/mpflash/cli_group.py +111 -111
  18. mpflash/mpflash/cli_list.py +87 -87
  19. mpflash/mpflash/cli_main.py +39 -39
  20. mpflash/mpflash/common.py +207 -166
  21. mpflash/mpflash/config.py +44 -44
  22. mpflash/mpflash/connected.py +96 -77
  23. mpflash/mpflash/download.py +364 -364
  24. mpflash/mpflash/downloaded.py +130 -130
  25. mpflash/mpflash/errors.py +9 -9
  26. mpflash/mpflash/flash/__init__.py +55 -55
  27. mpflash/mpflash/flash/esp.py +59 -59
  28. mpflash/mpflash/flash/stm32.py +19 -19
  29. mpflash/mpflash/flash/stm32_dfu.py +104 -104
  30. mpflash/mpflash/flash/uf2/__init__.py +88 -88
  31. mpflash/mpflash/flash/uf2/boardid.py +15 -15
  32. mpflash/mpflash/flash/uf2/linux.py +136 -130
  33. mpflash/mpflash/flash/uf2/macos.py +42 -42
  34. mpflash/mpflash/flash/uf2/uf2disk.py +12 -12
  35. mpflash/mpflash/flash/uf2/windows.py +43 -43
  36. mpflash/mpflash/flash/worklist.py +170 -170
  37. mpflash/mpflash/list.py +106 -106
  38. mpflash/mpflash/logger.py +41 -41
  39. mpflash/mpflash/mpboard_id/__init__.py +93 -93
  40. mpflash/mpflash/mpboard_id/add_boards.py +251 -251
  41. mpflash/mpflash/mpboard_id/board.py +37 -37
  42. mpflash/mpflash/mpboard_id/board_id.py +86 -86
  43. mpflash/mpflash/mpboard_id/store.py +48 -43
  44. mpflash/mpflash/mpremoteboard/__init__.py +266 -266
  45. mpflash/mpflash/mpremoteboard/mpy_fw_info.py +152 -141
  46. mpflash/mpflash/mpremoteboard/runner.py +140 -140
  47. mpflash/mpflash/vendor/click_aliases.py +91 -91
  48. mpflash/mpflash/vendor/dfu.py +165 -165
  49. mpflash/mpflash/vendor/pydfu.py +605 -605
  50. mpflash/mpflash/vendor/readme.md +2 -2
  51. mpflash/mpflash/versions.py +135 -135
  52. mpflash/poetry.lock +1687 -1599
  53. mpflash/pyproject.toml +65 -65
  54. mpflash/stm32_udev_rules.md +62 -62
  55. stubber/__init__.py +3 -3
  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_lvgl.py +741 -741
  62. stubber/board/createstubs_lvgl_min.py +741 -741
  63. stubber/board/createstubs_mem.py +766 -766
  64. stubber/board/createstubs_mem_min.py +306 -306
  65. stubber/board/createstubs_min.py +294 -294
  66. stubber/board/fw_info.py +141 -141
  67. stubber/board/info.py +183 -183
  68. stubber/board/main.py +19 -19
  69. stubber/board/modulelist.txt +247 -247
  70. stubber/board/pyrightconfig.json +34 -34
  71. stubber/bulk/mcu_stubber.py +437 -437
  72. stubber/codemod/_partials/__init__.py +48 -48
  73. stubber/codemod/_partials/db_main.py +147 -147
  74. stubber/codemod/_partials/lvgl_main.py +77 -77
  75. stubber/codemod/_partials/modules_reader.py +80 -80
  76. stubber/codemod/add_comment.py +53 -53
  77. stubber/codemod/add_method.py +65 -65
  78. stubber/codemod/board.py +317 -317
  79. stubber/codemod/enrich.py +145 -145
  80. stubber/codemod/merge_docstub.py +284 -284
  81. stubber/codemod/modify_list.py +54 -54
  82. stubber/codemod/utils.py +56 -56
  83. stubber/commands/build_cmd.py +94 -94
  84. stubber/commands/cli.py +49 -49
  85. stubber/commands/clone_cmd.py +78 -78
  86. stubber/commands/config_cmd.py +29 -29
  87. stubber/commands/enrich_folder_cmd.py +71 -71
  88. stubber/commands/get_core_cmd.py +71 -71
  89. stubber/commands/get_docstubs_cmd.py +92 -92
  90. stubber/commands/get_frozen_cmd.py +117 -117
  91. stubber/commands/get_mcu_cmd.py +102 -102
  92. stubber/commands/merge_cmd.py +66 -66
  93. stubber/commands/publish_cmd.py +118 -118
  94. stubber/commands/stub_cmd.py +31 -31
  95. stubber/commands/switch_cmd.py +62 -62
  96. stubber/commands/variants_cmd.py +48 -48
  97. stubber/cst_transformer.py +178 -178
  98. stubber/data/board_info.csv +193 -193
  99. stubber/data/board_info.json +1729 -1729
  100. stubber/data/micropython_tags.csv +15 -15
  101. stubber/data/requirements-core-micropython.txt +38 -38
  102. stubber/data/requirements-core-pycopy.txt +39 -39
  103. stubber/downloader.py +37 -37
  104. stubber/freeze/common.py +72 -72
  105. stubber/freeze/freeze_folder.py +69 -69
  106. stubber/freeze/freeze_manifest_2.py +126 -126
  107. stubber/freeze/get_frozen.py +131 -131
  108. stubber/get_cpython.py +112 -112
  109. stubber/get_lobo.py +59 -59
  110. stubber/minify.py +423 -423
  111. stubber/publish/bump.py +86 -86
  112. stubber/publish/candidates.py +275 -275
  113. stubber/publish/database.py +18 -18
  114. stubber/publish/defaults.py +40 -40
  115. stubber/publish/enums.py +24 -24
  116. stubber/publish/helpers.py +29 -29
  117. stubber/publish/merge_docstubs.py +132 -132
  118. stubber/publish/missing_class_methods.py +51 -51
  119. stubber/publish/package.py +150 -150
  120. stubber/publish/pathnames.py +51 -51
  121. stubber/publish/publish.py +120 -120
  122. stubber/publish/pypi.py +42 -42
  123. stubber/publish/stubpackage.py +1051 -1051
  124. stubber/rst/__init__.py +9 -9
  125. stubber/rst/classsort.py +78 -78
  126. stubber/rst/lookup.py +531 -531
  127. stubber/rst/output_dict.py +401 -401
  128. stubber/rst/reader.py +814 -814
  129. stubber/rst/report_return.py +77 -77
  130. stubber/rst/rst_utils.py +541 -541
  131. stubber/stubber.py +38 -38
  132. stubber/stubs_from_docs.py +90 -90
  133. stubber/tools/manifestfile.py +654 -654
  134. stubber/tools/readme.md +6 -6
  135. stubber/update_fallback.py +117 -117
  136. stubber/update_module_list.py +123 -123
  137. stubber/utils/__init__.py +6 -6
  138. stubber/utils/config.py +137 -137
  139. stubber/utils/makeversionhdr.py +54 -54
  140. stubber/utils/manifest.py +90 -90
  141. stubber/utils/post.py +80 -80
  142. stubber/utils/repos.py +156 -156
  143. stubber/utils/stubmaker.py +139 -139
  144. stubber/utils/typed_config_toml.py +80 -80
  145. stubber/variants.py +106 -106
  146. micropython_stubber-1.23.1.post1.dist-info/RECORD +0 -159
  147. mpflash/basicgit.py +0 -288
  148. {micropython_stubber-1.23.1.post1.dist-info → micropython_stubber-1.23.3.dist-info}/WHEEL +0 -0
  149. {micropython_stubber-1.23.1.post1.dist-info → micropython_stubber-1.23.3.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.23.1"
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.1-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.1 do not have a micro .0
599
- and info["version"] <= "1.19.9"
600
- ):
601
- # versions from 1.10.0 to 1.23.1 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.3"
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.3-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.3 do not have a micro .0
599
+ and info["version"] <= "1.19.9"
600
+ ):
601
+ # versions from 1.10.0 to 1.23.3 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 ["/sd", "/flash", "/", c, "."]:
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()