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