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,741 +1,741 @@
1
- """
2
- Create stubs for the lvgl modules on a MicroPython board.
3
-
4
- Note that the stubs can be very large, and it may be best to directly store them on an SD card if your device supports this.
5
-
6
- This variant was generated from createstubs.py by micropython-stubber v1.16.3
7
- """
8
- # Copyright (c) 2019-2023 Jos Verlinde
9
-
10
- import gc
11
- import os
12
- import sys
13
- from time import sleep
14
-
15
- try:
16
- from ujson import dumps
17
- except:
18
- from json import dumps
19
-
20
- try:
21
- from machine import reset # type: ignore
22
- except ImportError:
23
- pass
24
-
25
- try:
26
- from collections import OrderedDict
27
- except ImportError:
28
- from ucollections import OrderedDict # type: ignore
29
-
30
- __version__ = "v1.16.3"
31
- ENOENT = 2
32
- _MAX_CLASS_LEVEL = 2 # Max class nesting
33
- LIBS = ["lib", "/lib", "/sd/lib", "/flash/lib", "."]
34
-
35
-
36
- # our own logging module to avoid dependency on and interfering with logging module
37
- class logging:
38
- DEBUG = 10
39
- TRACE = 15
40
- INFO = 20
41
- WARNING = 30
42
- ERROR = 40
43
- CRITICAL = 50
44
- level = INFO
45
- prnt = print
46
-
47
- @staticmethod
48
- def getLogger(name):
49
- return logging()
50
-
51
- @classmethod
52
- def basicConfig(cls, level):
53
- cls.level = level
54
- pass
55
-
56
- def trace(self, msg):
57
- if self.level <= logging.TRACE:
58
- self.prnt("TRACE :", msg)
59
-
60
- def debug(self, msg):
61
- if self.level <= logging.DEBUG:
62
- self.prnt("DEBUG :", msg)
63
-
64
- def info(self, msg):
65
- if self.level <= logging.INFO:
66
- self.prnt("INFO :", msg)
67
-
68
- def warning(self, msg):
69
- if self.level <= logging.WARNING:
70
- self.prnt("WARN :", msg)
71
-
72
- def error(self, msg):
73
- if self.level <= logging.ERROR:
74
- self.prnt("ERROR :", msg)
75
-
76
- def critical(self, msg):
77
- if self.level <= logging.CRITICAL:
78
- self.prnt("CRIT :", msg)
79
-
80
-
81
- log = logging.getLogger("stubber")
82
- logging.basicConfig(level=logging.INFO)
83
- # logging.basicConfig(level=logging.TRACE)
84
- # logging.basicConfig(level=logging.DEBUG)
85
-
86
-
87
- class Stubber:
88
- "Generate stubs for modules in firmware"
89
-
90
- def __init__(self, path: str = None, firmware_id: str = None): # type: ignore
91
- try:
92
- if os.uname().release == "1.13.0" and os.uname().version < "v1.13-103": # type: ignore
93
- raise NotImplementedError("MicroPython 1.13.0 cannot be stubbed")
94
- except AttributeError:
95
- pass
96
- self._report = [] # type: list[str]
97
- self.info = _info()
98
- log.info("Port: {}".format(self.info["port"]))
99
- log.info("Board: {}".format(self.info["board"]))
100
- gc.collect()
101
- if firmware_id:
102
- self._fwid = firmware_id.lower()
103
- else:
104
- if self.info["family"] == "micropython":
105
- self._fwid = "{family}-v{version}-{port}-{board}".format(**self.info).rstrip("-")
106
- else:
107
- self._fwid = "{family}-v{version}-{port}".format(**self.info)
108
- self._start_free = gc.mem_free() # type: ignore
109
-
110
- if path:
111
- if path.endswith("/"):
112
- path = path[:-1]
113
- else:
114
- path = get_root()
115
-
116
- self.path = "{}/stubs/{}".format(path, self.flat_fwid).replace("//", "/")
117
- log.debug(self.path)
118
- try:
119
- ensure_folder(path + "/")
120
- except OSError:
121
- log.error("error creating stub folder {}".format(path))
122
- self.problematic = [
123
- "upip",
124
- "upysh",
125
- "webrepl_setup",
126
- "http_client",
127
- "http_client_ssl",
128
- "http_server",
129
- "http_server_ssl",
130
- ]
131
- self.excluded = [
132
- "webrepl",
133
- "_webrepl",
134
- "port_diag",
135
- "example_sub_led.py",
136
- "example_pub_button.py",
137
- ]
138
- # there is no option to discover modules from micropython, list is read from an external file.
139
- self.modules = [] # type: list[str]
140
-
141
- def get_obj_attributes(self, item_instance: object):
142
- "extract information of the objects members and attributes"
143
- # name_, repr_(value), type as text, item_instance
144
- _result = []
145
- _errors = []
146
- log.debug("get attributes {} {}".format(repr(item_instance), item_instance))
147
- for name in dir(item_instance):
148
- if name.startswith("_") and not name in self.modules:
149
- continue
150
- log.debug("get attribute {}".format(name))
151
- try:
152
- val = getattr(item_instance, name)
153
- # name , item_repr(value) , type as text, item_instance, order
154
- log.debug("attribute {}:{}".format(name, val))
155
- try:
156
- type_text = repr(type(val)).split("'")[1]
157
- except IndexError:
158
- type_text = ""
159
- if type_text in {"int", "float", "str", "bool", "tuple", "list", "dict"}:
160
- order = 1
161
- elif type_text in {"function", "method"}:
162
- order = 2
163
- elif type_text in ("class"):
164
- order = 3
165
- else:
166
- order = 4
167
- _result.append((name, repr(val), repr(type(val)), val, order))
168
- except AttributeError as e:
169
- _errors.append("Couldn't get attribute '{}' from object '{}', Err: {}".format(name, item_instance, e))
170
- except MemoryError as e:
171
- print("MemoryError: {}".format(e))
172
- sleep(1)
173
- reset()
174
-
175
- # remove internal __
176
- # _result = sorted([i for i in _result if not (i[0].startswith("_"))], key=lambda x: x[4])
177
- _result = sorted([i for i in _result if not (i[0].startswith("__"))], key=lambda x: x[4])
178
- gc.collect()
179
- return _result, _errors
180
-
181
- def add_modules(self, modules):
182
- "Add additional modules to be exported"
183
- self.modules = sorted(set(self.modules) | set(modules))
184
-
185
- def create_all_stubs(self):
186
- "Create stubs for all configured modules"
187
- log.info("Start micropython-stubber v{} on {}".format(__version__, self._fwid))
188
- gc.collect()
189
- for module_name in self.modules:
190
- self.create_one_stub(module_name)
191
- log.info("Finally done")
192
-
193
- def create_one_stub(self, module_name: str):
194
- if module_name in self.problematic:
195
- log.warning("Skip module: {:<25} : Known problematic".format(module_name))
196
- return False
197
- if module_name in self.excluded:
198
- log.warning("Skip module: {:<25} : Excluded".format(module_name))
199
- return False
200
-
201
- file_name = "{}/{}.py".format(self.path, module_name.replace(".", "/"))
202
- gc.collect()
203
- result = False
204
- try:
205
- result = self.create_module_stub(module_name, file_name)
206
- except OSError:
207
- return False
208
- gc.collect()
209
- return result
210
-
211
- def create_module_stub(self, module_name: str, file_name: str = None) -> bool: # type: ignore
212
- """Create a Stub of a single python module
213
-
214
- Args:
215
- - module_name (str): name of the module to document. This module will be imported.
216
- - file_name (Optional[str]): the 'path/filename.py' to write to. If omitted will be created based on the module name.
217
- """
218
- if file_name is None:
219
- fname = module_name.replace(".", "_") + ".py"
220
- file_name = self.path + "/" + fname
221
- else:
222
- fname = file_name.split("/")[-1]
223
-
224
- if "/" in module_name:
225
- # for nested modules
226
- module_name = module_name.replace("/", ".")
227
-
228
- # import the module (as new_module) to examine it
229
- new_module = None
230
- try:
231
- new_module = __import__(module_name, None, None, ("*"))
232
- m1 = gc.mem_free() # type: ignore
233
- log.info("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1))
234
-
235
- except ImportError:
236
- log.trace("Skip module: {:<25} {:<79}".format(module_name, "Module not found."))
237
- return False
238
-
239
- # Start a new file
240
- ensure_folder(file_name)
241
- with open(file_name, "w") as fp:
242
- # todo: improve header
243
- info_ = str(self.info).replace("OrderedDict(", "").replace("})", "}")
244
- s = '"""\nModule: \'{0}\' on {1}\n"""\n# MCU: {2}\n# Stubber: {3}\n'.format(module_name, self._fwid, info_, __version__)
245
- fp.write(s)
246
- fp.write("from __future__ import annotations\nfrom typing import Any\nfrom _typeshed import Incomplete\n\n")
247
- self.write_object_stub(fp, new_module, module_name, "")
248
-
249
- self._report.append('{{"module": "{}", "file": "{}"}}'.format(module_name, file_name.replace("\\", "/")))
250
-
251
- if module_name not in {"os", "sys", "logging", "gc"}:
252
- # try to unload the module unless we use it
253
- try:
254
- del new_module
255
- except (OSError, KeyError): # lgtm [py/unreachable-statement]
256
- log.warning("could not del new_module")
257
- # lets not try - most times it does not work anyway
258
- # try:
259
- # del sys.modules[module_name]
260
- # except KeyError:
261
- # log.warning("could not del sys.modules[{}]".format(module_name))
262
- gc.collect()
263
- return True
264
-
265
- def write_object_stub(self, fp, object_expr: object, obj_name: str, indent: str, in_class: int = 0):
266
- "Write a module/object stub to an open file. Can be called recursive."
267
- gc.collect()
268
- if object_expr in self.problematic:
269
- log.warning("SKIPPING problematic module:{}".format(object_expr))
270
- return
271
-
272
- # log.debug("DUMP : {}".format(object_expr))
273
- items, errors = self.get_obj_attributes(object_expr)
274
-
275
- if errors:
276
- log.error(errors)
277
-
278
- for item_name, item_repr, item_type_txt, item_instance, _ in items:
279
- # name_, repr_(value), type as text, item_instance, order
280
- if item_name in ["classmethod", "staticmethod", "BaseException", "Exception"]:
281
- # do not create stubs for these primitives
282
- continue
283
- if item_name[0].isdigit():
284
- log.warning("NameError: invalid name {}".format(item_name))
285
- continue
286
- # Class expansion only on first 3 levels (bit of a hack)
287
- if (
288
- item_type_txt == "<class 'type'>"
289
- and len(indent) <= _MAX_CLASS_LEVEL * 4
290
- # and not obj_name.endswith(".Pin")
291
- # avoid expansion of Pin.cpu / Pin.board to avoid crashes on most platforms
292
- ):
293
- log.trace("{0}class {1}:".format(indent, item_name))
294
- superclass = ""
295
- is_exception = (
296
- item_name.endswith("Exception")
297
- or item_name.endswith("Error")
298
- or item_name
299
- in [
300
- "KeyboardInterrupt",
301
- "StopIteration",
302
- "SystemExit",
303
- ]
304
- )
305
- if is_exception:
306
- superclass = "Exception"
307
- s = "\n{}class {}({}):\n".format(indent, item_name, superclass)
308
- # s += indent + " ''\n"
309
- if is_exception:
310
- s += indent + " ...\n"
311
- fp.write(s)
312
- continue
313
- # write classdef
314
- fp.write(s)
315
- # first write the class literals and methods
316
- log.debug("# recursion over class {0}".format(item_name))
317
- self.write_object_stub(
318
- fp,
319
- item_instance,
320
- "{0}.{1}".format(obj_name, item_name),
321
- indent + " ",
322
- in_class + 1,
323
- )
324
- # end with the __init__ method to make sure that the literals are defined
325
- # Add __init__
326
- s = indent + " def __init__(self, *argv, **kwargs) -> None:\n"
327
- s += indent + " ...\n\n"
328
- fp.write(s)
329
- elif any(word in item_type_txt for word in ["method", "function", "closure"]):
330
- log.debug("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name))
331
- # module Function or class method
332
- # will accept any number of params
333
- # return type Any/Incomplete
334
- ret = "Incomplete"
335
- first = ""
336
- # Self parameter only on class methods/functions
337
- if in_class > 0:
338
- first = "self, "
339
- # class method - add function decoration
340
- if "bound_method" in item_type_txt or "bound_method" in item_repr:
341
- s = "{}@classmethod\n".format(indent) + "{}def {}(cls, *args, **kwargs) -> {}:\n".format(indent, item_name, ret)
342
- else:
343
- s = "{}def {}({}*args, **kwargs) -> {}:\n".format(indent, item_name, first, ret)
344
- s += indent + " ...\n\n"
345
- fp.write(s)
346
- log.debug("\n" + s)
347
- elif item_type_txt == "<class 'module'>":
348
- # Skip imported modules
349
- # fp.write("# import {}\n".format(item_name))
350
- pass
351
-
352
- elif item_type_txt.startswith("<class '"):
353
- t = item_type_txt[8:-2]
354
- s = ""
355
-
356
- if t in ["str", "int", "float", "bool", "bytearray", "bytes"]:
357
- # known type: use actual value
358
- s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, item_repr, t)
359
- elif t in ["dict", "list", "tuple"]:
360
- # dict, list , tuple: use empty value
361
- ev = {"dict": "{}", "list": "[]", "tuple": "()"}
362
- s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, ev[t], t)
363
- else:
364
- # something else
365
- if t in ["object", "set", "frozenset", "Pin", "FileIO"]:
366
- # https://docs.python.org/3/tutorial/classes.html#item_instance-objects
367
- # use these types for the attribute
368
- s = "{0}{1} : {2} ## = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
369
- else:
370
- # Requires Python 3.6 syntax, which is OK for the stubs/pyi
371
- t = "Incomplete"
372
- s = "{0}{1} : {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
373
- fp.write(s)
374
- log.debug("\n" + s)
375
- else:
376
- # keep only the name
377
- log.debug("# all other, type = '{0}'".format(item_type_txt))
378
- fp.write("# all other, type = '{0}'\n".format(item_type_txt))
379
-
380
- fp.write(indent + item_name + " # type: Incomplete\n")
381
-
382
- # del items
383
- # del errors
384
- # try:
385
- # del item_name, item_repr, item_type_txt, item_instance # type: ignore
386
- # except (OSError, KeyError, NameError):
387
- # pass
388
-
389
- @property
390
- def flat_fwid(self):
391
- "Turn _fwid from 'v1.2.3' into '1_2_3' to be used in filename"
392
- s = self._fwid
393
- # path name restrictions
394
- chars = " .()/\\:$"
395
- for c in chars:
396
- s = s.replace(c, "_")
397
- return s
398
-
399
- def clean(self, path: str = None): # type: ignore
400
- "Remove all files from the stub folder"
401
- if path is None:
402
- path = self.path
403
- log.info("Clean/remove files in folder: {}".format(path))
404
- try:
405
- os.stat(path) # TEMP workaround mpremote listdir bug -
406
- items = os.listdir(path)
407
- except (OSError, AttributeError):
408
- # os.listdir fails on unix
409
- return
410
- for fn in items:
411
- item = "{}/{}".format(path, fn)
412
- try:
413
- os.remove(item)
414
- except OSError:
415
- try: # folder
416
- self.clean(item)
417
- os.rmdir(item)
418
- except OSError:
419
- pass
420
-
421
- def report(self, filename: str = "modules.json"):
422
- "create json with list of exported modules"
423
- log.info("Created stubs for {} modules on board {}\nPath: {}".format(len(self._report), self._fwid, self.path))
424
- f_name = "{}/{}".format(self.path, filename)
425
- log.info("Report file: {}".format(f_name))
426
- gc.collect()
427
- try:
428
- # write json by node to reduce memory requirements
429
- with open(f_name, "w") as f:
430
- self.write_json_header(f)
431
- first = True
432
- for n in self._report:
433
- self.write_json_node(f, n, first)
434
- first = False
435
- self.write_json_end(f)
436
- used = self._start_free - gc.mem_free() # type: ignore
437
- log.trace("Memory used: {0} Kb".format(used // 1024))
438
- except OSError:
439
- log.error("Failed to create the report.")
440
-
441
- def write_json_header(self, f):
442
- f.write("{")
443
- f.write(dumps({"firmware": self.info})[1:-1])
444
- f.write(",\n")
445
- f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1])
446
- f.write(",\n")
447
- f.write('"modules" :[\n')
448
-
449
- def write_json_node(self, f, n, first):
450
- if not first:
451
- f.write(",\n")
452
- f.write(n)
453
-
454
- def write_json_end(self, f):
455
- f.write("\n]}")
456
-
457
-
458
- def ensure_folder(path: str):
459
- "Create nested folders if needed"
460
- i = start = 0
461
- while i != -1:
462
- i = path.find("/", start)
463
- if i != -1:
464
- p = path[0] if i == 0 else path[:i]
465
- # p = partial folder
466
- try:
467
- _ = os.stat(p)
468
- except OSError as e:
469
- # folder does not exist
470
- if e.args[0] == ENOENT:
471
- try:
472
- os.mkdir(p)
473
- except OSError as e2:
474
- log.error("failed to create folder {}".format(p))
475
- raise e2
476
- # next level deep
477
- start = i + 1
478
-
479
-
480
- def _build(s):
481
- # extract build from sys.version or os.uname().version if available
482
- # sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f'
483
- # sys.implementation.version: 'v1.13-103-gb137d064e'
484
- if not s:
485
- return ""
486
- s = s.split(" on ", 1)[0] if " on " in s else s
487
- if s.startswith("v"):
488
- if not "-" in s:
489
- return ""
490
- b = s.split("-")[1]
491
- return b
492
- if not "-preview" in s:
493
- return ""
494
- b = s.split("-preview")[1].split(".")[1]
495
- return b
496
-
497
-
498
- def _info(): # type:() -> dict[str, str]
499
- info = OrderedDict(
500
- {
501
- "family": sys.implementation.name,
502
- "version": "",
503
- "build": "",
504
- "ver": "",
505
- "port": sys.platform, # port: esp32 / win32 / linux / stm32
506
- "board": "UNKNOWN",
507
- "cpu": "",
508
- "mpy": "",
509
- "arch": "",
510
- }
511
- )
512
- # change port names to be consistent with the repo
513
- if info["port"].startswith("pyb"):
514
- info["port"] = "stm32"
515
- elif info["port"] == "win32":
516
- info["port"] = "windows"
517
- elif info["port"] == "linux":
518
- info["port"] = "unix"
519
- try:
520
- info["version"] = version_str(sys.implementation.version) # type: ignore
521
- except AttributeError:
522
- pass
523
- try:
524
- _machine = sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore
525
- # info["board"] = "with".join(_machine.split("with")[:-1]).strip()
526
- info["board"] = _machine
527
- info["cpu"] = _machine.split("with")[-1].strip()
528
- info["mpy"] = (
529
- sys.implementation._mpy
530
- if "_mpy" in dir(sys.implementation)
531
- else sys.implementation.mpy
532
- if "mpy" in dir(sys.implementation)
533
- else ""
534
- )
535
- except (AttributeError, IndexError):
536
- pass
537
- info["board"] = get_boardname()
538
-
539
- try:
540
- if "uname" in dir(os): # old
541
- # extract build from uname().version if available
542
- info["build"] = _build(os.uname()[3]) # type: ignore
543
- if not info["build"]:
544
- # extract build from uname().release if available
545
- info["build"] = _build(os.uname()[2]) # type: ignore
546
- elif "version" in dir(sys): # new
547
- # extract build from sys.version if available
548
- info["build"] = _build(sys.version)
549
- except (AttributeError, IndexError, TypeError):
550
- pass
551
- # avoid build hashes
552
- # if info["build"] and len(info["build"]) > 5:
553
- # info["build"] = ""
554
-
555
- if info["version"] == "" and sys.platform not in ("unix", "win32"):
556
- try:
557
- u = os.uname() # type: ignore
558
- info["version"] = u.release
559
- except (IndexError, AttributeError, TypeError):
560
- pass
561
- # detect families
562
- for fam_name, mod_name, mod_thing in [
563
- ("pycopy", "pycopy", "const"),
564
- ("pycom", "pycom", "FAT"),
565
- ("ev3-pybricks", "pybricks.hubs", "EV3Brick"),
566
- ]:
567
- try:
568
- _t = __import__(mod_name, None, None, (mod_thing))
569
- info["family"] = fam_name
570
- del _t
571
- break
572
- except (ImportError, KeyError):
573
- pass
574
-
575
- if info["family"] == "ev3-pybricks":
576
- info["release"] = "2.0.0"
577
-
578
- if info["family"] == "micropython":
579
- info["version"]
580
- if (
581
- info["version"]
582
- and info["version"].endswith(".0")
583
- and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
584
- and info["version"] <= "1.19.9"
585
- ):
586
- # versions from 1.10.0 to 1.20.0 do not have a micro .0
587
- info["version"] = info["version"][:-2]
588
-
589
- # spell-checker: disable
590
- if "mpy" in info and info["mpy"]: # mpy on some v1.11+ builds
591
- sys_mpy = int(info["mpy"])
592
- # .mpy architecture
593
- arch = [
594
- None,
595
- "x86",
596
- "x64",
597
- "armv6",
598
- "armv6m",
599
- "armv7m",
600
- "armv7em",
601
- "armv7emsp",
602
- "armv7emdp",
603
- "xtensa",
604
- "xtensawin",
605
- ][sys_mpy >> 10]
606
- if arch:
607
- info["arch"] = arch
608
- # .mpy version.minor
609
- info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3)
610
- if info["build"] and not info["version"].endswith("-preview"):
611
- info["version"] = info["version"] + "-preview"
612
- # simple to use version[-build] string
613
- info["ver"] = f"{info['version']}-{info['build']}" if info["build"] else f"{info['version']}"
614
-
615
- return info
616
-
617
-
618
- def version_str(version: tuple): # -> str:
619
- v_str = ".".join([str(n) for n in version[:3]])
620
- if len(version) > 3 and version[3]:
621
- v_str += "-" + version[3]
622
- return v_str
623
-
624
-
625
- def get_boardname() -> str:
626
- "Read the board name from the boardname.py file that may have been created upfront"
627
- try:
628
- from boardname import BOARDNAME # type: ignore
629
-
630
- log.info("Found BOARDNAME: {}".format(BOARDNAME))
631
- except ImportError:
632
- log.warning("BOARDNAME not found")
633
- BOARDNAME = ""
634
- return BOARDNAME
635
-
636
-
637
- def get_root() -> str: # sourcery skip: use-assigned-variable
638
- "Determine the root folder of the device"
639
- try:
640
- c = os.getcwd()
641
- except (OSError, AttributeError):
642
- # unix port
643
- c = "."
644
- r = c
645
- for r in [c, "/sd", "/flash", "/", "."]:
646
- try:
647
- _ = os.stat(r)
648
- break
649
- except OSError:
650
- continue
651
- return r
652
-
653
-
654
- def file_exists(filename: str):
655
- try:
656
- if os.stat(filename)[0] >> 14:
657
- return True
658
- return False
659
- except OSError:
660
- return False
661
-
662
-
663
- def show_help():
664
- print("-p, --path path to store the stubs in, defaults to '.'")
665
- sys.exit(1)
666
-
667
-
668
- def read_path() -> str:
669
- "get --path from cmdline. [unix/win]"
670
- path = ""
671
- if len(sys.argv) == 3:
672
- cmd = (sys.argv[1]).lower()
673
- if cmd in ("--path", "-p"):
674
- path = sys.argv[2]
675
- else:
676
- show_help()
677
- elif len(sys.argv) == 2:
678
- show_help()
679
- return path
680
-
681
-
682
- def is_micropython() -> bool:
683
- "runtime test to determine full or micropython"
684
- # pylint: disable=unused-variable,eval-used
685
- try:
686
- # either test should fail on micropython
687
- # a) https://docs.micropython.org/en/latest/genrst/syntax.html#spaces
688
- # Micropython : SyntaxError
689
- # a = eval("1and 0") # lgtm [py/unused-local-variable]
690
- # Eval blocks some minification aspects
691
-
692
- # b) https://docs.micropython.org/en/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented
693
- # Micropython: NotImplementedError
694
- b = bytes("abc", encoding="utf8") # type: ignore # lgtm [py/unused-local-variable]
695
-
696
- # c) https://docs.micropython.org/en/latest/genrst/core_language.html#function-objects-do-not-have-the-module-attribute
697
- # Micropython: AttributeError
698
- c = is_micropython.__module__ # type: ignore # lgtm [py/unused-local-variable]
699
- return False
700
- except (NotImplementedError, AttributeError):
701
- return True
702
-
703
-
704
- def main():
705
- try:
706
- import lvgl # type: ignore
707
- except Exception:
708
- print("\n\nNOTE: The `lvgl` module could not be found on this firmware\n\n")
709
- return
710
- # Specify firmware name & version
711
- fw_id = "lvgl"
712
- try:
713
- fw_id = "lvgl-{0}_{1}_{2}-{3}-{4}".format(
714
- lvgl.version_major(),
715
- lvgl.version_minor(),
716
- lvgl.version_patch(),
717
- lvgl.version_info(),
718
- sys.platform,
719
- )
720
- except Exception:
721
- fw_id = "lvgl-{0}_{1}_{2}_{3}-{4}".format(8, 1, 0, "dev", sys.platform)
722
- finally:
723
- stubber = Stubber(firmware_id=fw_id)
724
- stubber.clean()
725
- # modules to stub : only lvgl specifics
726
- stubber.modules = ["io", "lodepng", "rtch", "lvgl"] # spell-checker: enable
727
-
728
- gc.collect()
729
-
730
- stubber.create_all_stubs()
731
- stubber.report()
732
-
733
-
734
- if __name__ == "__main__" or is_micropython():
735
- if not file_exists("no_auto_stubber.txt"):
736
- try:
737
- gc.threshold(4 * 1024) # type: ignore
738
- gc.enable()
739
- except BaseException:
740
- pass
741
- main()
1
+ """
2
+ Create stubs for the lvgl modules on a MicroPython board.
3
+
4
+ Note that the stubs can be very large, and it may be best to directly store them on an SD card if your device supports this.
5
+
6
+ This variant was generated from createstubs.py by micropython-stubber v1.16.3
7
+ """
8
+ # Copyright (c) 2019-2023 Jos Verlinde
9
+
10
+ import gc
11
+ import os
12
+ import sys
13
+ from time import sleep
14
+
15
+ try:
16
+ from ujson import dumps
17
+ except:
18
+ from json import dumps
19
+
20
+ try:
21
+ from machine import reset # type: ignore
22
+ except ImportError:
23
+ pass
24
+
25
+ try:
26
+ from collections import OrderedDict
27
+ except ImportError:
28
+ from ucollections import OrderedDict # type: ignore
29
+
30
+ __version__ = "v1.16.3"
31
+ ENOENT = 2
32
+ _MAX_CLASS_LEVEL = 2 # Max class nesting
33
+ LIBS = ["lib", "/lib", "/sd/lib", "/flash/lib", "."]
34
+
35
+
36
+ # our own logging module to avoid dependency on and interfering with logging module
37
+ class logging:
38
+ DEBUG = 10
39
+ TRACE = 15
40
+ INFO = 20
41
+ WARNING = 30
42
+ ERROR = 40
43
+ CRITICAL = 50
44
+ level = INFO
45
+ prnt = print
46
+
47
+ @staticmethod
48
+ def getLogger(name):
49
+ return logging()
50
+
51
+ @classmethod
52
+ def basicConfig(cls, level):
53
+ cls.level = level
54
+ pass
55
+
56
+ def trace(self, msg):
57
+ if self.level <= logging.TRACE:
58
+ self.prnt("TRACE :", msg)
59
+
60
+ def debug(self, msg):
61
+ if self.level <= logging.DEBUG:
62
+ self.prnt("DEBUG :", msg)
63
+
64
+ def info(self, msg):
65
+ if self.level <= logging.INFO:
66
+ self.prnt("INFO :", msg)
67
+
68
+ def warning(self, msg):
69
+ if self.level <= logging.WARNING:
70
+ self.prnt("WARN :", msg)
71
+
72
+ def error(self, msg):
73
+ if self.level <= logging.ERROR:
74
+ self.prnt("ERROR :", msg)
75
+
76
+ def critical(self, msg):
77
+ if self.level <= logging.CRITICAL:
78
+ self.prnt("CRIT :", msg)
79
+
80
+
81
+ log = logging.getLogger("stubber")
82
+ logging.basicConfig(level=logging.INFO)
83
+ # logging.basicConfig(level=logging.TRACE)
84
+ # logging.basicConfig(level=logging.DEBUG)
85
+
86
+
87
+ class Stubber:
88
+ "Generate stubs for modules in firmware"
89
+
90
+ def __init__(self, path: str = None, firmware_id: str = None): # type: ignore
91
+ try:
92
+ if os.uname().release == "1.13.0" and os.uname().version < "v1.13-103": # type: ignore
93
+ raise NotImplementedError("MicroPython 1.13.0 cannot be stubbed")
94
+ except AttributeError:
95
+ pass
96
+ self._report = [] # type: list[str]
97
+ self.info = _info()
98
+ log.info("Port: {}".format(self.info["port"]))
99
+ log.info("Board: {}".format(self.info["board"]))
100
+ gc.collect()
101
+ if firmware_id:
102
+ self._fwid = firmware_id.lower()
103
+ else:
104
+ if self.info["family"] == "micropython":
105
+ self._fwid = "{family}-v{version}-{port}-{board}".format(**self.info).rstrip("-")
106
+ else:
107
+ self._fwid = "{family}-v{version}-{port}".format(**self.info)
108
+ self._start_free = gc.mem_free() # type: ignore
109
+
110
+ if path:
111
+ if path.endswith("/"):
112
+ path = path[:-1]
113
+ else:
114
+ path = get_root()
115
+
116
+ self.path = "{}/stubs/{}".format(path, self.flat_fwid).replace("//", "/")
117
+ log.debug(self.path)
118
+ try:
119
+ ensure_folder(path + "/")
120
+ except OSError:
121
+ log.error("error creating stub folder {}".format(path))
122
+ self.problematic = [
123
+ "upip",
124
+ "upysh",
125
+ "webrepl_setup",
126
+ "http_client",
127
+ "http_client_ssl",
128
+ "http_server",
129
+ "http_server_ssl",
130
+ ]
131
+ self.excluded = [
132
+ "webrepl",
133
+ "_webrepl",
134
+ "port_diag",
135
+ "example_sub_led.py",
136
+ "example_pub_button.py",
137
+ ]
138
+ # there is no option to discover modules from micropython, list is read from an external file.
139
+ self.modules = [] # type: list[str]
140
+
141
+ def get_obj_attributes(self, item_instance: object):
142
+ "extract information of the objects members and attributes"
143
+ # name_, repr_(value), type as text, item_instance
144
+ _result = []
145
+ _errors = []
146
+ log.debug("get attributes {} {}".format(repr(item_instance), item_instance))
147
+ for name in dir(item_instance):
148
+ if name.startswith("_") and not name in self.modules:
149
+ continue
150
+ log.debug("get attribute {}".format(name))
151
+ try:
152
+ val = getattr(item_instance, name)
153
+ # name , item_repr(value) , type as text, item_instance, order
154
+ log.debug("attribute {}:{}".format(name, val))
155
+ try:
156
+ type_text = repr(type(val)).split("'")[1]
157
+ except IndexError:
158
+ type_text = ""
159
+ if type_text in {"int", "float", "str", "bool", "tuple", "list", "dict"}:
160
+ order = 1
161
+ elif type_text in {"function", "method"}:
162
+ order = 2
163
+ elif type_text in ("class"):
164
+ order = 3
165
+ else:
166
+ order = 4
167
+ _result.append((name, repr(val), repr(type(val)), val, order))
168
+ except AttributeError as e:
169
+ _errors.append("Couldn't get attribute '{}' from object '{}', Err: {}".format(name, item_instance, e))
170
+ except MemoryError as e:
171
+ print("MemoryError: {}".format(e))
172
+ sleep(1)
173
+ reset()
174
+
175
+ # remove internal __
176
+ # _result = sorted([i for i in _result if not (i[0].startswith("_"))], key=lambda x: x[4])
177
+ _result = sorted([i for i in _result if not (i[0].startswith("__"))], key=lambda x: x[4])
178
+ gc.collect()
179
+ return _result, _errors
180
+
181
+ def add_modules(self, modules):
182
+ "Add additional modules to be exported"
183
+ self.modules = sorted(set(self.modules) | set(modules))
184
+
185
+ def create_all_stubs(self):
186
+ "Create stubs for all configured modules"
187
+ log.info("Start micropython-stubber v{} on {}".format(__version__, self._fwid))
188
+ gc.collect()
189
+ for module_name in self.modules:
190
+ self.create_one_stub(module_name)
191
+ log.info("Finally done")
192
+
193
+ def create_one_stub(self, module_name: str):
194
+ if module_name in self.problematic:
195
+ log.warning("Skip module: {:<25} : Known problematic".format(module_name))
196
+ return False
197
+ if module_name in self.excluded:
198
+ log.warning("Skip module: {:<25} : Excluded".format(module_name))
199
+ return False
200
+
201
+ file_name = "{}/{}.py".format(self.path, module_name.replace(".", "/"))
202
+ gc.collect()
203
+ result = False
204
+ try:
205
+ result = self.create_module_stub(module_name, file_name)
206
+ except OSError:
207
+ return False
208
+ gc.collect()
209
+ return result
210
+
211
+ def create_module_stub(self, module_name: str, file_name: str = None) -> bool: # type: ignore
212
+ """Create a Stub of a single python module
213
+
214
+ Args:
215
+ - module_name (str): name of the module to document. This module will be imported.
216
+ - file_name (Optional[str]): the 'path/filename.py' to write to. If omitted will be created based on the module name.
217
+ """
218
+ if file_name is None:
219
+ fname = module_name.replace(".", "_") + ".py"
220
+ file_name = self.path + "/" + fname
221
+ else:
222
+ fname = file_name.split("/")[-1]
223
+
224
+ if "/" in module_name:
225
+ # for nested modules
226
+ module_name = module_name.replace("/", ".")
227
+
228
+ # import the module (as new_module) to examine it
229
+ new_module = None
230
+ try:
231
+ new_module = __import__(module_name, None, None, ("*"))
232
+ m1 = gc.mem_free() # type: ignore
233
+ log.info("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1))
234
+
235
+ except ImportError:
236
+ log.trace("Skip module: {:<25} {:<79}".format(module_name, "Module not found."))
237
+ return False
238
+
239
+ # Start a new file
240
+ ensure_folder(file_name)
241
+ with open(file_name, "w") as fp:
242
+ # todo: improve header
243
+ info_ = str(self.info).replace("OrderedDict(", "").replace("})", "}")
244
+ s = '"""\nModule: \'{0}\' on {1}\n"""\n# MCU: {2}\n# Stubber: {3}\n'.format(module_name, self._fwid, info_, __version__)
245
+ fp.write(s)
246
+ fp.write("from __future__ import annotations\nfrom typing import Any\nfrom _typeshed import Incomplete\n\n")
247
+ self.write_object_stub(fp, new_module, module_name, "")
248
+
249
+ self._report.append('{{"module": "{}", "file": "{}"}}'.format(module_name, file_name.replace("\\", "/")))
250
+
251
+ if module_name not in {"os", "sys", "logging", "gc"}:
252
+ # try to unload the module unless we use it
253
+ try:
254
+ del new_module
255
+ except (OSError, KeyError): # lgtm [py/unreachable-statement]
256
+ log.warning("could not del new_module")
257
+ # lets not try - most times it does not work anyway
258
+ # try:
259
+ # del sys.modules[module_name]
260
+ # except KeyError:
261
+ # log.warning("could not del sys.modules[{}]".format(module_name))
262
+ gc.collect()
263
+ return True
264
+
265
+ def write_object_stub(self, fp, object_expr: object, obj_name: str, indent: str, in_class: int = 0):
266
+ "Write a module/object stub to an open file. Can be called recursive."
267
+ gc.collect()
268
+ if object_expr in self.problematic:
269
+ log.warning("SKIPPING problematic module:{}".format(object_expr))
270
+ return
271
+
272
+ # log.debug("DUMP : {}".format(object_expr))
273
+ items, errors = self.get_obj_attributes(object_expr)
274
+
275
+ if errors:
276
+ log.error(errors)
277
+
278
+ for item_name, item_repr, item_type_txt, item_instance, _ in items:
279
+ # name_, repr_(value), type as text, item_instance, order
280
+ if item_name in ["classmethod", "staticmethod", "BaseException", "Exception"]:
281
+ # do not create stubs for these primitives
282
+ continue
283
+ if item_name[0].isdigit():
284
+ log.warning("NameError: invalid name {}".format(item_name))
285
+ continue
286
+ # Class expansion only on first 3 levels (bit of a hack)
287
+ if (
288
+ item_type_txt == "<class 'type'>"
289
+ and len(indent) <= _MAX_CLASS_LEVEL * 4
290
+ # and not obj_name.endswith(".Pin")
291
+ # avoid expansion of Pin.cpu / Pin.board to avoid crashes on most platforms
292
+ ):
293
+ log.trace("{0}class {1}:".format(indent, item_name))
294
+ superclass = ""
295
+ is_exception = (
296
+ item_name.endswith("Exception")
297
+ or item_name.endswith("Error")
298
+ or item_name
299
+ in [
300
+ "KeyboardInterrupt",
301
+ "StopIteration",
302
+ "SystemExit",
303
+ ]
304
+ )
305
+ if is_exception:
306
+ superclass = "Exception"
307
+ s = "\n{}class {}({}):\n".format(indent, item_name, superclass)
308
+ # s += indent + " ''\n"
309
+ if is_exception:
310
+ s += indent + " ...\n"
311
+ fp.write(s)
312
+ continue
313
+ # write classdef
314
+ fp.write(s)
315
+ # first write the class literals and methods
316
+ log.debug("# recursion over class {0}".format(item_name))
317
+ self.write_object_stub(
318
+ fp,
319
+ item_instance,
320
+ "{0}.{1}".format(obj_name, item_name),
321
+ indent + " ",
322
+ in_class + 1,
323
+ )
324
+ # end with the __init__ method to make sure that the literals are defined
325
+ # Add __init__
326
+ s = indent + " def __init__(self, *argv, **kwargs) -> None:\n"
327
+ s += indent + " ...\n\n"
328
+ fp.write(s)
329
+ elif any(word in item_type_txt for word in ["method", "function", "closure"]):
330
+ log.debug("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name))
331
+ # module Function or class method
332
+ # will accept any number of params
333
+ # return type Any/Incomplete
334
+ ret = "Incomplete"
335
+ first = ""
336
+ # Self parameter only on class methods/functions
337
+ if in_class > 0:
338
+ first = "self, "
339
+ # class method - add function decoration
340
+ if "bound_method" in item_type_txt or "bound_method" in item_repr:
341
+ s = "{}@classmethod\n".format(indent) + "{}def {}(cls, *args, **kwargs) -> {}:\n".format(indent, item_name, ret)
342
+ else:
343
+ s = "{}def {}({}*args, **kwargs) -> {}:\n".format(indent, item_name, first, ret)
344
+ s += indent + " ...\n\n"
345
+ fp.write(s)
346
+ log.debug("\n" + s)
347
+ elif item_type_txt == "<class 'module'>":
348
+ # Skip imported modules
349
+ # fp.write("# import {}\n".format(item_name))
350
+ pass
351
+
352
+ elif item_type_txt.startswith("<class '"):
353
+ t = item_type_txt[8:-2]
354
+ s = ""
355
+
356
+ if t in ["str", "int", "float", "bool", "bytearray", "bytes"]:
357
+ # known type: use actual value
358
+ s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, item_repr, t)
359
+ elif t in ["dict", "list", "tuple"]:
360
+ # dict, list , tuple: use empty value
361
+ ev = {"dict": "{}", "list": "[]", "tuple": "()"}
362
+ s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, ev[t], t)
363
+ else:
364
+ # something else
365
+ if t in ["object", "set", "frozenset", "Pin", "FileIO"]:
366
+ # https://docs.python.org/3/tutorial/classes.html#item_instance-objects
367
+ # use these types for the attribute
368
+ s = "{0}{1} : {2} ## = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
369
+ else:
370
+ # Requires Python 3.6 syntax, which is OK for the stubs/pyi
371
+ t = "Incomplete"
372
+ s = "{0}{1} : {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
373
+ fp.write(s)
374
+ log.debug("\n" + s)
375
+ else:
376
+ # keep only the name
377
+ log.debug("# all other, type = '{0}'".format(item_type_txt))
378
+ fp.write("# all other, type = '{0}'\n".format(item_type_txt))
379
+
380
+ fp.write(indent + item_name + " # type: Incomplete\n")
381
+
382
+ # del items
383
+ # del errors
384
+ # try:
385
+ # del item_name, item_repr, item_type_txt, item_instance # type: ignore
386
+ # except (OSError, KeyError, NameError):
387
+ # pass
388
+
389
+ @property
390
+ def flat_fwid(self):
391
+ "Turn _fwid from 'v1.2.3' into '1_2_3' to be used in filename"
392
+ s = self._fwid
393
+ # path name restrictions
394
+ chars = " .()/\\:$"
395
+ for c in chars:
396
+ s = s.replace(c, "_")
397
+ return s
398
+
399
+ def clean(self, path: str = None): # type: ignore
400
+ "Remove all files from the stub folder"
401
+ if path is None:
402
+ path = self.path
403
+ log.info("Clean/remove files in folder: {}".format(path))
404
+ try:
405
+ os.stat(path) # TEMP workaround mpremote listdir bug -
406
+ items = os.listdir(path)
407
+ except (OSError, AttributeError):
408
+ # os.listdir fails on unix
409
+ return
410
+ for fn in items:
411
+ item = "{}/{}".format(path, fn)
412
+ try:
413
+ os.remove(item)
414
+ except OSError:
415
+ try: # folder
416
+ self.clean(item)
417
+ os.rmdir(item)
418
+ except OSError:
419
+ pass
420
+
421
+ def report(self, filename: str = "modules.json"):
422
+ "create json with list of exported modules"
423
+ log.info("Created stubs for {} modules on board {}\nPath: {}".format(len(self._report), self._fwid, self.path))
424
+ f_name = "{}/{}".format(self.path, filename)
425
+ log.info("Report file: {}".format(f_name))
426
+ gc.collect()
427
+ try:
428
+ # write json by node to reduce memory requirements
429
+ with open(f_name, "w") as f:
430
+ self.write_json_header(f)
431
+ first = True
432
+ for n in self._report:
433
+ self.write_json_node(f, n, first)
434
+ first = False
435
+ self.write_json_end(f)
436
+ used = self._start_free - gc.mem_free() # type: ignore
437
+ log.trace("Memory used: {0} Kb".format(used // 1024))
438
+ except OSError:
439
+ log.error("Failed to create the report.")
440
+
441
+ def write_json_header(self, f):
442
+ f.write("{")
443
+ f.write(dumps({"firmware": self.info})[1:-1])
444
+ f.write(",\n")
445
+ f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1])
446
+ f.write(",\n")
447
+ f.write('"modules" :[\n')
448
+
449
+ def write_json_node(self, f, n, first):
450
+ if not first:
451
+ f.write(",\n")
452
+ f.write(n)
453
+
454
+ def write_json_end(self, f):
455
+ f.write("\n]}")
456
+
457
+
458
+ def ensure_folder(path: str):
459
+ "Create nested folders if needed"
460
+ i = start = 0
461
+ while i != -1:
462
+ i = path.find("/", start)
463
+ if i != -1:
464
+ p = path[0] if i == 0 else path[:i]
465
+ # p = partial folder
466
+ try:
467
+ _ = os.stat(p)
468
+ except OSError as e:
469
+ # folder does not exist
470
+ if e.args[0] == ENOENT:
471
+ try:
472
+ os.mkdir(p)
473
+ except OSError as e2:
474
+ log.error("failed to create folder {}".format(p))
475
+ raise e2
476
+ # next level deep
477
+ start = i + 1
478
+
479
+
480
+ def _build(s):
481
+ # extract build from sys.version or os.uname().version if available
482
+ # sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f'
483
+ # sys.implementation.version: 'v1.13-103-gb137d064e'
484
+ if not s:
485
+ return ""
486
+ s = s.split(" on ", 1)[0] if " on " in s else s
487
+ if s.startswith("v"):
488
+ if not "-" in s:
489
+ return ""
490
+ b = s.split("-")[1]
491
+ return b
492
+ if not "-preview" in s:
493
+ return ""
494
+ b = s.split("-preview")[1].split(".")[1]
495
+ return b
496
+
497
+
498
+ def _info(): # type:() -> dict[str, str]
499
+ info = OrderedDict(
500
+ {
501
+ "family": sys.implementation.name,
502
+ "version": "",
503
+ "build": "",
504
+ "ver": "",
505
+ "port": sys.platform, # port: esp32 / win32 / linux / stm32
506
+ "board": "UNKNOWN",
507
+ "cpu": "",
508
+ "mpy": "",
509
+ "arch": "",
510
+ }
511
+ )
512
+ # change port names to be consistent with the repo
513
+ if info["port"].startswith("pyb"):
514
+ info["port"] = "stm32"
515
+ elif info["port"] == "win32":
516
+ info["port"] = "windows"
517
+ elif info["port"] == "linux":
518
+ info["port"] = "unix"
519
+ try:
520
+ info["version"] = version_str(sys.implementation.version) # type: ignore
521
+ except AttributeError:
522
+ pass
523
+ try:
524
+ _machine = sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore
525
+ # info["board"] = "with".join(_machine.split("with")[:-1]).strip()
526
+ info["board"] = _machine
527
+ info["cpu"] = _machine.split("with")[-1].strip()
528
+ info["mpy"] = (
529
+ sys.implementation._mpy
530
+ if "_mpy" in dir(sys.implementation)
531
+ else sys.implementation.mpy
532
+ if "mpy" in dir(sys.implementation)
533
+ else ""
534
+ )
535
+ except (AttributeError, IndexError):
536
+ pass
537
+ info["board"] = get_boardname()
538
+
539
+ try:
540
+ if "uname" in dir(os): # old
541
+ # extract build from uname().version if available
542
+ info["build"] = _build(os.uname()[3]) # type: ignore
543
+ if not info["build"]:
544
+ # extract build from uname().release if available
545
+ info["build"] = _build(os.uname()[2]) # type: ignore
546
+ elif "version" in dir(sys): # new
547
+ # extract build from sys.version if available
548
+ info["build"] = _build(sys.version)
549
+ except (AttributeError, IndexError, TypeError):
550
+ pass
551
+ # avoid build hashes
552
+ # if info["build"] and len(info["build"]) > 5:
553
+ # info["build"] = ""
554
+
555
+ if info["version"] == "" and sys.platform not in ("unix", "win32"):
556
+ try:
557
+ u = os.uname() # type: ignore
558
+ info["version"] = u.release
559
+ except (IndexError, AttributeError, TypeError):
560
+ pass
561
+ # detect families
562
+ for fam_name, mod_name, mod_thing in [
563
+ ("pycopy", "pycopy", "const"),
564
+ ("pycom", "pycom", "FAT"),
565
+ ("ev3-pybricks", "pybricks.hubs", "EV3Brick"),
566
+ ]:
567
+ try:
568
+ _t = __import__(mod_name, None, None, (mod_thing))
569
+ info["family"] = fam_name
570
+ del _t
571
+ break
572
+ except (ImportError, KeyError):
573
+ pass
574
+
575
+ if info["family"] == "ev3-pybricks":
576
+ info["release"] = "2.0.0"
577
+
578
+ if info["family"] == "micropython":
579
+ info["version"]
580
+ if (
581
+ info["version"]
582
+ and info["version"].endswith(".0")
583
+ and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
584
+ and info["version"] <= "1.19.9"
585
+ ):
586
+ # versions from 1.10.0 to 1.20.0 do not have a micro .0
587
+ info["version"] = info["version"][:-2]
588
+
589
+ # spell-checker: disable
590
+ if "mpy" in info and info["mpy"]: # mpy on some v1.11+ builds
591
+ sys_mpy = int(info["mpy"])
592
+ # .mpy architecture
593
+ arch = [
594
+ None,
595
+ "x86",
596
+ "x64",
597
+ "armv6",
598
+ "armv6m",
599
+ "armv7m",
600
+ "armv7em",
601
+ "armv7emsp",
602
+ "armv7emdp",
603
+ "xtensa",
604
+ "xtensawin",
605
+ ][sys_mpy >> 10]
606
+ if arch:
607
+ info["arch"] = arch
608
+ # .mpy version.minor
609
+ info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3)
610
+ if info["build"] and not info["version"].endswith("-preview"):
611
+ info["version"] = info["version"] + "-preview"
612
+ # simple to use version[-build] string
613
+ info["ver"] = f"{info['version']}-{info['build']}" if info["build"] else f"{info['version']}"
614
+
615
+ return info
616
+
617
+
618
+ def version_str(version: tuple): # -> str:
619
+ v_str = ".".join([str(n) for n in version[:3]])
620
+ if len(version) > 3 and version[3]:
621
+ v_str += "-" + version[3]
622
+ return v_str
623
+
624
+
625
+ def get_boardname() -> str:
626
+ "Read the board name from the boardname.py file that may have been created upfront"
627
+ try:
628
+ from boardname import BOARDNAME # type: ignore
629
+
630
+ log.info("Found BOARDNAME: {}".format(BOARDNAME))
631
+ except ImportError:
632
+ log.warning("BOARDNAME not found")
633
+ BOARDNAME = ""
634
+ return BOARDNAME
635
+
636
+
637
+ def get_root() -> str: # sourcery skip: use-assigned-variable
638
+ "Determine the root folder of the device"
639
+ try:
640
+ c = os.getcwd()
641
+ except (OSError, AttributeError):
642
+ # unix port
643
+ c = "."
644
+ r = c
645
+ for r in [c, "/sd", "/flash", "/", "."]:
646
+ try:
647
+ _ = os.stat(r)
648
+ break
649
+ except OSError:
650
+ continue
651
+ return r
652
+
653
+
654
+ def file_exists(filename: str):
655
+ try:
656
+ if os.stat(filename)[0] >> 14:
657
+ return True
658
+ return False
659
+ except OSError:
660
+ return False
661
+
662
+
663
+ def show_help():
664
+ print("-p, --path path to store the stubs in, defaults to '.'")
665
+ sys.exit(1)
666
+
667
+
668
+ def read_path() -> str:
669
+ "get --path from cmdline. [unix/win]"
670
+ path = ""
671
+ if len(sys.argv) == 3:
672
+ cmd = (sys.argv[1]).lower()
673
+ if cmd in ("--path", "-p"):
674
+ path = sys.argv[2]
675
+ else:
676
+ show_help()
677
+ elif len(sys.argv) == 2:
678
+ show_help()
679
+ return path
680
+
681
+
682
+ def is_micropython() -> bool:
683
+ "runtime test to determine full or micropython"
684
+ # pylint: disable=unused-variable,eval-used
685
+ try:
686
+ # either test should fail on micropython
687
+ # a) https://docs.micropython.org/en/latest/genrst/syntax.html#spaces
688
+ # Micropython : SyntaxError
689
+ # a = eval("1and 0") # lgtm [py/unused-local-variable]
690
+ # Eval blocks some minification aspects
691
+
692
+ # b) https://docs.micropython.org/en/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented
693
+ # Micropython: NotImplementedError
694
+ b = bytes("abc", encoding="utf8") # type: ignore # lgtm [py/unused-local-variable]
695
+
696
+ # c) https://docs.micropython.org/en/latest/genrst/core_language.html#function-objects-do-not-have-the-module-attribute
697
+ # Micropython: AttributeError
698
+ c = is_micropython.__module__ # type: ignore # lgtm [py/unused-local-variable]
699
+ return False
700
+ except (NotImplementedError, AttributeError):
701
+ return True
702
+
703
+
704
+ def main():
705
+ try:
706
+ import lvgl # type: ignore
707
+ except Exception:
708
+ print("\n\nNOTE: The `lvgl` module could not be found on this firmware\n\n")
709
+ return
710
+ # Specify firmware name & version
711
+ fw_id = "lvgl"
712
+ try:
713
+ fw_id = "lvgl-{0}_{1}_{2}-{3}-{4}".format(
714
+ lvgl.version_major(),
715
+ lvgl.version_minor(),
716
+ lvgl.version_patch(),
717
+ lvgl.version_info(),
718
+ sys.platform,
719
+ )
720
+ except Exception:
721
+ fw_id = "lvgl-{0}_{1}_{2}_{3}-{4}".format(8, 1, 0, "dev", sys.platform)
722
+ finally:
723
+ stubber = Stubber(firmware_id=fw_id)
724
+ stubber.clean()
725
+ # modules to stub : only lvgl specifics
726
+ stubber.modules = ["io", "lodepng", "rtch", "lvgl"] # spell-checker: enable
727
+
728
+ gc.collect()
729
+
730
+ stubber.create_all_stubs()
731
+ stubber.report()
732
+
733
+
734
+ if __name__ == "__main__" or is_micropython():
735
+ if not file_exists("no_auto_stubber.txt"):
736
+ try:
737
+ gc.threshold(4 * 1024) # type: ignore
738
+ gc.enable()
739
+ except BaseException:
740
+ pass
741
+ main()