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