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