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