micropython-stubber 1.16.2__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 (55) hide show
  1. {micropython_stubber-1.16.2.dist-info → micropython_stubber-1.17.0.dist-info}/METADATA +2 -1
  2. {micropython_stubber-1.16.2.dist-info → micropython_stubber-1.17.0.dist-info}/RECORD +54 -54
  3. stubber/__init__.py +1 -1
  4. stubber/basicgit.py +27 -33
  5. stubber/board/board_info.csv +137 -103
  6. stubber/board/createstubs.py +222 -189
  7. stubber/board/createstubs_db.py +284 -214
  8. stubber/board/createstubs_db_min.py +286 -265
  9. stubber/board/createstubs_db_mpy.mpy +0 -0
  10. stubber/board/createstubs_lvgl.py +171 -113
  11. stubber/board/createstubs_lvgl_min.py +738 -275
  12. stubber/board/createstubs_lvgl_mpy.mpy +0 -0
  13. stubber/board/createstubs_mem.py +237 -174
  14. stubber/board/createstubs_mem_min.py +263 -247
  15. stubber/board/createstubs_mem_mpy.mpy +0 -0
  16. stubber/board/createstubs_min.py +242 -227
  17. stubber/board/createstubs_mpy.mpy +0 -0
  18. stubber/board/fw_info.py +135 -0
  19. stubber/board/modulelist.txt +1 -2
  20. stubber/codemod/_partials/__init__.py +1 -1
  21. stubber/codemod/_partials/db_main.py +90 -72
  22. stubber/codemod/_partials/modules_reader.py +29 -17
  23. stubber/codemod/board.py +2 -4
  24. stubber/codemod/enrich.py +1 -1
  25. stubber/commands/build_cmd.py +6 -4
  26. stubber/commands/get_docstubs_cmd.py +6 -11
  27. stubber/commands/get_frozen_cmd.py +6 -11
  28. stubber/commands/switch_cmd.py +6 -4
  29. stubber/data/board_info.csv +134 -101
  30. stubber/data/board_info.json +1357 -901
  31. stubber/freeze/freeze_manifest_2.py +2 -1
  32. stubber/freeze/get_frozen.py +28 -13
  33. stubber/minify.py +56 -43
  34. stubber/publish/candidates.py +15 -23
  35. stubber/publish/defaults.py +2 -2
  36. stubber/publish/merge_docstubs.py +5 -7
  37. stubber/publish/missing_class_methods.py +2 -2
  38. stubber/publish/pathnames.py +2 -2
  39. stubber/publish/publish.py +2 -1
  40. stubber/publish/stubpackage.py +20 -40
  41. stubber/rst/lookup.py +9 -7
  42. stubber/rst/reader.py +2 -1
  43. stubber/stubber.py +5 -6
  44. stubber/update_fallback.py +3 -1
  45. stubber/update_module_list.py +1 -1
  46. stubber/utils/__init__.py +1 -1
  47. stubber/utils/config.py +7 -9
  48. stubber/utils/post.py +1 -1
  49. stubber/utils/repos.py +10 -7
  50. stubber/utils/versions.py +48 -7
  51. stubber/variants.py +3 -3
  52. stubber/board/logging.py +0 -99
  53. {micropython_stubber-1.16.2.dist-info → micropython_stubber-1.17.0.dist-info}/LICENSE +0 -0
  54. {micropython_stubber-1.16.2.dist-info → micropython_stubber-1.17.0.dist-info}/WHEEL +0 -0
  55. {micropython_stubber-1.16.2.dist-info → micropython_stubber-1.17.0.dist-info}/entry_points.txt +0 -0
@@ -4,11 +4,14 @@ Create stubs for (all) modules on a MicroPython board
4
4
  # Copyright (c) 2019-2023 Jos Verlinde
5
5
 
6
6
  import gc
7
- import logging
8
7
  import os
9
8
  import sys
9
+ from time import sleep
10
10
 
11
- from ujson import dumps
11
+ try:
12
+ from ujson import dumps
13
+ except:
14
+ from json import dumps
12
15
 
13
16
  try:
14
17
  from machine import reset # type: ignore
@@ -20,11 +23,49 @@ try:
20
23
  except ImportError:
21
24
  from ucollections import OrderedDict # type: ignore
22
25
 
23
- __version__ = "v1.16.2"
26
+ __version__ = "v1.17.0"
24
27
  ENOENT = 2
25
28
  _MAX_CLASS_LEVEL = 2 # Max class nesting
26
- LIBS = [".", "/lib", "/sd/lib", "/flash/lib", "lib"]
27
- from time import sleep
29
+ LIBS = ["lib", "/lib", "/sd/lib", "/flash/lib", "."]
30
+
31
+
32
+ # our own logging module to avoid dependency on and interfering with logging module
33
+ class logging:
34
+ # DEBUG = 10
35
+ INFO = 20
36
+ WARNING = 30
37
+ ERROR = 40
38
+ level = INFO
39
+ prnt = print
40
+
41
+ @staticmethod
42
+ def getLogger(name):
43
+ return logging()
44
+
45
+ @classmethod
46
+ def basicConfig(cls, level):
47
+ cls.level = level
48
+
49
+ # def debug(self, msg):
50
+ # if self.level <= logging.DEBUG:
51
+ # self.prnt("DEBUG :", msg)
52
+
53
+ def info(self, msg):
54
+ if self.level <= logging.INFO:
55
+ self.prnt("INFO :", msg)
56
+
57
+ def warning(self, msg):
58
+ if self.level <= logging.WARNING:
59
+ self.prnt("WARN :", msg)
60
+
61
+ def error(self, msg):
62
+ if self.level <= logging.ERROR:
63
+ self.prnt("ERROR :", msg)
64
+
65
+
66
+ log = logging.getLogger("stubber")
67
+ logging.basicConfig(level=logging.INFO)
68
+ # logging.basicConfig(level=logging.DEBUG)
28
69
 
29
70
 
30
71
  class Stubber:
@@ -32,24 +73,21 @@ class Stubber:
32
73
 
33
74
  def __init__(self, path: str = None, firmware_id: str = None): # type: ignore
34
75
  try:
35
- if os.uname().release == "1.13.0" and os.uname().version < "v1.13-103":
76
+ if os.uname().release == "1.13.0" and os.uname().version < "v1.13-103": # type: ignore
36
77
  raise NotImplementedError("MicroPython 1.13.0 cannot be stubbed")
37
78
  except AttributeError:
38
79
  pass
39
- self.log = None
40
- self.log = logging.getLogger("stubber")
41
- self._report = [] # type: list[str]
42
80
  self.info = _info()
43
- self.log.info("Port: {}".format(self.info["port"]))
44
- self.log.info("Board: {}".format(self.info["board"]))
81
+ log.info("Port: {}".format(self.info["port"]))
82
+ log.info("Board: {}".format(self.info["board"]))
45
83
  gc.collect()
46
84
  if firmware_id:
47
85
  self._fwid = firmware_id.lower()
48
86
  else:
49
87
  if self.info["family"] == "micropython":
50
- self._fwid = "{family}-{ver}-{port}-{board}".format(**self.info)
88
+ self._fwid = "{family}-v{version}-{port}-{board}".format(**self.info).rstrip("-")
51
89
  else:
52
- self._fwid = "{family}-{ver}-{port}".format(**self.info)
90
+ self._fwid = "{family}-v{version}-{port}".format(**self.info)
53
91
  self._start_free = gc.mem_free() # type: ignore
54
92
 
55
93
  if path:
@@ -59,11 +97,11 @@ class Stubber:
59
97
  path = get_root()
60
98
 
61
99
  self.path = "{}/stubs/{}".format(path, self.flat_fwid).replace("//", "/")
62
- self.log.debug(self.path)
100
+ # log.debug(self.path)
63
101
  try:
64
102
  ensure_folder(path + "/")
65
103
  except OSError:
66
- self.log.error("error creating stub folder {}".format(path))
104
+ log.error("error creating stub folder {}".format(path))
67
105
  self.problematic = [
68
106
  "upip",
69
107
  "upysh",
@@ -82,20 +120,23 @@ class Stubber:
82
120
  ]
83
121
  # there is no option to discover modules from micropython, list is read from an external file.
84
122
  self.modules = [] # type: list[str]
123
+ self._json_name = None
124
+ self._json_first = False
85
125
 
86
126
  def get_obj_attributes(self, item_instance: object):
87
127
  "extract information of the objects members and attributes"
88
128
  # name_, repr_(value), type as text, item_instance
89
129
  _result = []
90
130
  _errors = []
91
- self.log.debug("get attributes {} {}".format(repr(item_instance), item_instance))
131
+ # log.debug("get attributes {} {}".format(repr(item_instance), item_instance))
92
132
  for name in dir(item_instance):
93
- if name.startswith("_") and not name in self.modules:
133
+ if name.startswith("__") and not name in self.modules:
94
134
  continue
95
- self.log.debug("get attribute {}".format(name))
135
+ # log.debug("get attribute {}".format(name))
96
136
  try:
97
137
  val = getattr(item_instance, name)
98
138
  # name , item_repr(value) , type as text, item_instance, order
139
+ # log.debug("attribute {}:{}".format(name, val))
99
140
  try:
100
141
  type_text = repr(type(val)).split("'")[1]
101
142
  except IndexError:
@@ -110,11 +151,7 @@ class Stubber:
110
151
  order = 4
111
152
  _result.append((name, repr(val), repr(type(val)), val, order))
112
153
  except AttributeError as e:
113
- _errors.append(
114
- "Couldn't get attribute '{}' from object '{}', Err: {}".format(
115
- name, item_instance, e
116
- )
117
- )
154
+ _errors.append("Couldn't get attribute '{}' from object '{}', Err: {}".format(name, item_instance, e))
118
155
  except MemoryError as e:
119
156
  print("MemoryError: {}".format(e))
120
157
  sleep(1)
@@ -132,21 +169,23 @@ class Stubber:
132
169
 
133
170
  def create_all_stubs(self):
134
171
  "Create stubs for all configured modules"
135
- self.log.info("Start micropython-stubber v{} on {}".format(__version__, self._fwid))
172
+ log.info("Start micropython-stubber {} on {}".format(__version__, self._fwid))
173
+ self.report_start()
136
174
  gc.collect()
137
175
  for module_name in self.modules:
138
176
  self.create_one_stub(module_name)
139
- self.log.info("Finally done")
177
+ self.report_end()
178
+ log.info("Finally done")
140
179
 
141
180
  def create_one_stub(self, module_name: str):
142
181
  if module_name in self.problematic:
143
- self.log.warning("Skip module: {:<25} : Known problematic".format(module_name))
182
+ log.warning("Skip module: {:<25} : Known problematic".format(module_name))
144
183
  return False
145
184
  if module_name in self.excluded:
146
- self.log.warning("Skip module: {:<25} : Excluded".format(module_name))
185
+ log.warning("Skip module: {:<25} : Excluded".format(module_name))
147
186
  return False
148
187
 
149
- file_name = "{}/{}.py".format(self.path, module_name.replace(".", "/"))
188
+ file_name = "{}/{}.pyi".format(self.path, module_name.replace(".", "/"))
150
189
  gc.collect()
151
190
  result = False
152
191
  try:
@@ -161,10 +200,10 @@ class Stubber:
161
200
 
162
201
  Args:
163
202
  - module_name (str): name of the module to document. This module will be imported.
164
- - file_name (Optional[str]): the 'path/filename.py' to write to. If omitted will be created based on the module name.
203
+ - file_name (Optional[str]): the 'path/filename.pyi' to write to. If omitted will be created based on the module name.
165
204
  """
166
205
  if file_name is None:
167
- fname = module_name.replace(".", "_") + ".py"
206
+ fname = module_name.replace(".", "_") + ".pyi"
168
207
  file_name = self.path + "/" + fname
169
208
  else:
170
209
  fname = file_name.split("/")[-1]
@@ -178,12 +217,10 @@ class Stubber:
178
217
  try:
179
218
  new_module = __import__(module_name, None, None, ("*"))
180
219
  m1 = gc.mem_free() # type: ignore
181
- self.log.info(
182
- "Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1)
183
- )
220
+ log.info("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1))
184
221
 
185
222
  except ImportError:
186
- self.log.warning("Skip module: {:<25} {:<79}".format(module_name, "Module not found."))
223
+ # log.debug("Skip module: {:<25} {:<79}".format(module_name, "Module not found."))
187
224
  return False
188
225
 
189
226
  # Start a new file
@@ -195,40 +232,35 @@ class Stubber:
195
232
  module_name, self._fwid, info_, __version__
196
233
  )
197
234
  fp.write(s)
198
- fp.write("from typing import Any\nfrom _typeshed import Incomplete\n\n")
235
+ fp.write(
236
+ "from __future__ import annotations\nfrom typing import Any, Generator\nfrom _typeshed import Incomplete\n\n"
237
+ )
199
238
  self.write_object_stub(fp, new_module, module_name, "")
200
239
 
201
- self._report.append(
202
- '{{"module": "{}", "file": "{}"}}'.format(module_name, file_name.replace("\\", "/"))
203
- )
240
+ self.report_add(module_name, file_name)
204
241
 
205
242
  if module_name not in {"os", "sys", "logging", "gc"}:
206
243
  # try to unload the module unless we use it
207
244
  try:
208
245
  del new_module
209
246
  except (OSError, KeyError): # lgtm [py/unreachable-statement]
210
- self.log.warning("could not del new_module")
211
- try:
212
- del sys.modules[module_name]
213
- except KeyError:
214
- self.log.debug("could not del sys.modules[{}]".format(module_name))
247
+ log.warning("could not del new_module")
248
+ # do not try to delete from sys.modules - most times it does not work anyway
215
249
  gc.collect()
216
250
  return True
217
251
 
218
- def write_object_stub(
219
- self, fp, object_expr: object, obj_name: str, indent: str, in_class: int = 0
220
- ):
252
+ def write_object_stub(self, fp, object_expr: object, obj_name: str, indent: str, in_class: int = 0):
221
253
  "Write a module/object stub to an open file. Can be called recursive."
222
254
  gc.collect()
223
255
  if object_expr in self.problematic:
224
- self.log.warning("SKIPPING problematic module:{}".format(object_expr))
256
+ log.warning("SKIPPING problematic module:{}".format(object_expr))
225
257
  return
226
258
 
227
- # self.log.debug("DUMP : {}".format(object_expr))
259
+ # # log.debug("DUMP : {}".format(object_expr))
228
260
  items, errors = self.get_obj_attributes(object_expr)
229
261
 
230
262
  if errors:
231
- self.log.error(errors)
263
+ log.error(errors)
232
264
 
233
265
  for item_name, item_repr, item_type_txt, item_instance, _ in items:
234
266
  # name_, repr_(value), type as text, item_instance, order
@@ -236,11 +268,16 @@ class Stubber:
236
268
  # do not create stubs for these primitives
237
269
  continue
238
270
  if item_name[0].isdigit():
239
- self.log.warning("NameError: invalid name {}".format(item_name))
271
+ log.warning("NameError: invalid name {}".format(item_name))
240
272
  continue
241
273
  # Class expansion only on first 3 levels (bit of a hack)
242
- if item_type_txt == "<class 'type'>" and len(indent) <= _MAX_CLASS_LEVEL * 4:
243
- self.log.debug("{0}class {1}:".format(indent, item_name))
274
+ if (
275
+ item_type_txt == "<class 'type'>"
276
+ and len(indent) <= _MAX_CLASS_LEVEL * 4
277
+ # and not obj_name.endswith(".Pin")
278
+ # avoid expansion of Pin.cpu / Pin.board to avoid crashes on most platforms
279
+ ):
280
+ # log.debug("{0}class {1}:".format(indent, item_name))
244
281
  superclass = ""
245
282
  is_exception = (
246
283
  item_name.endswith("Exception")
@@ -259,11 +296,11 @@ class Stubber:
259
296
  if is_exception:
260
297
  s += indent + " ...\n"
261
298
  fp.write(s)
262
- return
299
+ continue
263
300
  # write classdef
264
301
  fp.write(s)
265
302
  # first write the class literals and methods
266
- self.log.debug("# recursion over class {0}".format(item_name))
303
+ # log.debug("# recursion over class {0}".format(item_name))
267
304
  self.write_object_stub(
268
305
  fp,
269
306
  item_instance,
@@ -277,11 +314,7 @@ class Stubber:
277
314
  s += indent + " ...\n\n"
278
315
  fp.write(s)
279
316
  elif any(word in item_type_txt for word in ["method", "function", "closure"]):
280
- self.log.debug(
281
- "# def {1} function/method/closure, type = '{0}'".format(
282
- item_type_txt, item_name
283
- )
284
- )
317
+ # log.debug("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name))
285
318
  # module Function or class method
286
319
  # will accept any number of params
287
320
  # return type Any/Incomplete
@@ -292,16 +325,14 @@ class Stubber:
292
325
  first = "self, "
293
326
  # class method - add function decoration
294
327
  if "bound_method" in item_type_txt or "bound_method" in item_repr:
295
- s = "{}@classmethod\n".format(
296
- indent
297
- ) + "{}def {}(cls, *args, **kwargs) -> {}:\n".format(indent, item_name, ret)
298
- else:
299
- s = "{}def {}({}*args, **kwargs) -> {}:\n".format(
300
- indent, item_name, first, ret
328
+ s = "{}@classmethod\n".format(indent) + "{}def {}(cls, *args, **kwargs) -> {}:\n".format(
329
+ indent, item_name, ret
301
330
  )
331
+ else:
332
+ s = "{}def {}({}*args, **kwargs) -> {}:\n".format(indent, item_name, first, ret)
302
333
  s += indent + " ...\n\n"
303
334
  fp.write(s)
304
- self.log.debug("\n" + s)
335
+ # log.debug("\n" + s)
305
336
  elif item_type_txt == "<class 'module'>":
306
337
  # Skip imported modules
307
338
  # fp.write("# import {}\n".format(item_name))
@@ -311,38 +342,42 @@ class Stubber:
311
342
  t = item_type_txt[8:-2]
312
343
  s = ""
313
344
 
314
- if t in ["str", "int", "float", "bool", "bytearray", "bytes"]:
345
+ if t in ("str", "int", "float", "bool", "bytearray", "bytes"):
315
346
  # known type: use actual value
316
- s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, item_repr, t)
317
- elif t in ["dict", "list", "tuple"]:
347
+ # s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, item_repr, t)
348
+ s = "{0}{1}: {3} = {2}\n".format(indent, item_name, item_repr, t)
349
+ elif t in ("dict", "list", "tuple"):
318
350
  # dict, list , tuple: use empty value
319
351
  ev = {"dict": "{}", "list": "[]", "tuple": "()"}
320
- s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, ev[t], t)
352
+ # s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, ev[t], t)
353
+ s = "{0}{1}: {3} = {2}\n".format(indent, item_name, ev[t], t)
321
354
  else:
322
355
  # something else
323
- if t not in ["object", "set", "frozenset"]:
324
- # Possibly default others to item_instance object ?
356
+ if t in ("object", "set", "frozenset", "Pin", "generator"): # "FileIO"
325
357
  # https://docs.python.org/3/tutorial/classes.html#item_instance-objects
358
+ # use these types for the attribute
359
+ if t == "generator":
360
+ t = "Generator"
361
+ s = "{0}{1}: {2} ## = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
362
+ else:
363
+ # Requires Python 3.6 syntax, which is OK for the stubs/pyi
326
364
  t = "Incomplete"
327
- # Requires Python 3.6 syntax, which is OK for the stubs/pyi
328
- s = "{0}{1} : {2} ## {3} = {4}\n".format(
329
- indent, item_name, t, item_type_txt, item_repr
330
- )
365
+ s = "{0}{1}: {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
331
366
  fp.write(s)
332
- self.log.debug("\n" + s)
367
+ # log.debug("\n" + s)
333
368
  else:
334
369
  # keep only the name
335
- self.log.debug("# all other, type = '{0}'".format(item_type_txt))
370
+ # log.debug("# all other, type = '{0}'".format(item_type_txt))
336
371
  fp.write("# all other, type = '{0}'\n".format(item_type_txt))
337
372
 
338
373
  fp.write(indent + item_name + " # type: Incomplete\n")
339
374
 
340
- del items
341
- del errors
342
- try:
343
- del item_name, item_repr, item_type_txt, item_instance # type: ignore
344
- except (OSError, KeyError, NameError):
345
- pass
375
+ # del items
376
+ # del errors
377
+ # try:
378
+ # del item_name, item_repr, item_type_txt, item_instance # type: ignore
379
+ # except (OSError, KeyError, NameError):
380
+ # pass
346
381
 
347
382
  @property
348
383
  def flat_fwid(self):
@@ -358,7 +393,7 @@ class Stubber:
358
393
  "Remove all files from the stub folder"
359
394
  if path is None:
360
395
  path = self.path
361
- self.log.info("Clean/remove files in folder: {}".format(path))
396
+ log.info("Clean/remove files in folder: {}".format(path))
362
397
  try:
363
398
  os.stat(path) # TEMP workaround mpremote listdir bug -
364
399
  items = os.listdir(path)
@@ -376,45 +411,53 @@ class Stubber:
376
411
  except OSError:
377
412
  pass
378
413
 
379
- def report(self, filename: str = "modules.json"):
380
- "create json with list of exported modules"
381
- self.log.info(
382
- "Created stubs for {} modules on board {}\nPath: {}".format(
383
- len(self._report), self._fwid, self.path
384
- )
385
- )
386
- f_name = "{}/{}".format(self.path, filename)
387
- self.log.info("Report file: {}".format(f_name))
414
+ def report_start(self, filename: str = "modules.json"):
415
+ """Start a report of the modules that have been stubbed
416
+ "create json with list of exported modules"""
417
+ self._json_name = "{}/{}".format(self.path, filename)
418
+ self._json_first = True
419
+ ensure_folder(self._json_name)
420
+ log.info("Report file: {}".format(self._json_name))
388
421
  gc.collect()
389
422
  try:
390
423
  # write json by node to reduce memory requirements
391
- with open(f_name, "w") as f:
392
- self.write_json_header(f)
393
- first = True
394
- for n in self._report:
395
- self.write_json_node(f, n, first)
396
- first = False
397
- self.write_json_end(f)
398
- used = self._start_free - gc.mem_free() # type: ignore
399
- self.log.info("Memory used: {0} Kb".format(used // 1024))
400
- except OSError:
401
- self.log.error("Failed to create the report.")
402
-
403
- def write_json_header(self, f):
404
- f.write("{")
405
- f.write(dumps({"firmware": self.info})[1:-1])
406
- f.write(",\n")
407
- f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1])
408
- f.write(",\n")
409
- f.write('"modules" :[\n')
424
+ with open(self._json_name, "w") as f:
425
+ f.write("{")
426
+ f.write(dumps({"firmware": self.info})[1:-1])
427
+ f.write(",\n")
428
+ f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1])
429
+ f.write(",\n")
430
+ f.write('"modules" :[\n')
431
+
432
+ except OSError as e:
433
+ log.error("Failed to create the report.")
434
+ self._json_name = None
435
+ raise e
436
+
437
+ def report_add(self, module_name: str, stub_file: str):
438
+ "Add a module to the report"
439
+ # write json by node to reduce memory requirements
440
+ if not self._json_name:
441
+ raise Exception("No report file")
442
+ try:
443
+ with open(self._json_name, "a") as f:
444
+ if not self._json_first:
445
+ f.write(",\n")
446
+ else:
447
+ self._json_first = False
448
+ line = '{{"module": "{}", "file": "{}"}}'.format(module_name, stub_file.replace("\\", "/"))
449
+ f.write(line)
410
450
 
411
- def write_json_node(self, f, n, first):
412
- if not first:
413
- f.write(",\n")
414
- f.write(n)
451
+ except OSError:
452
+ log.error("Failed to create the report.")
415
453
 
416
- def write_json_end(self, f):
417
- f.write("\n]}")
454
+ def report_end(self):
455
+ if not self._json_name:
456
+ raise Exception("No report file")
457
+ with open(self._json_name, "a") as f:
458
+ f.write("\n]}")
459
+ # is used as sucess indicator
460
+ log.info("Path: {}".format(self.path))
418
461
 
419
462
 
420
463
  def ensure_folder(path: str):
@@ -440,23 +483,32 @@ def ensure_folder(path: str):
440
483
 
441
484
 
442
485
  def _build(s):
443
- # extract a build nr from a string
486
+ # extract build from sys.version or os.uname().version if available
487
+ # sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f'
488
+ # sys.implementation.version: 'v1.13-103-gb137d064e'
444
489
  if not s:
445
490
  return ""
446
- if " on " in s:
447
- s = s.split(" on ", 1)[0]
448
- return s.split("-")[1] if "-" in s else ""
491
+ s = s.split(" on ", 1)[0] if " on " in s else s
492
+ if s.startswith("v"):
493
+ if not "-" in s:
494
+ return ""
495
+ b = s.split("-")[1]
496
+ return b
497
+ if not "-preview" in s:
498
+ return ""
499
+ b = s.split("-preview")[1].split(".")[1]
500
+ return b
449
501
 
450
502
 
451
503
  def _info(): # type:() -> dict[str, str]
452
504
  info = OrderedDict(
453
505
  {
454
- "family": sys.implementation.name,
506
+ "family": sys.implementation.name, # type: ignore
455
507
  "version": "",
456
508
  "build": "",
457
509
  "ver": "",
458
510
  "port": sys.platform, # port: esp32 / win32 / linux / stm32
459
- "board": "GENERIC",
511
+ "board": "UNKNOWN",
460
512
  "cpu": "",
461
513
  "mpy": "",
462
514
  "arch": "",
@@ -470,60 +522,46 @@ def _info(): # type:() -> dict[str, str]
470
522
  elif info["port"] == "linux":
471
523
  info["port"] = "unix"
472
524
  try:
473
- info["version"] = ".".join([str(n) for n in sys.implementation.version])
525
+ info["version"] = version_str(sys.implementation.version) # type: ignore
474
526
  except AttributeError:
475
527
  pass
476
528
  try:
477
- machine = (
478
- sys.implementation._machine
479
- if "_machine" in dir(sys.implementation)
480
- else os.uname().machine
529
+ _machine = (
530
+ sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore
481
531
  )
482
- info["board"] = machine.strip()
483
- info["cpu"] = machine.split("with")[1].strip()
532
+ # info["board"] = "with".join(_machine.split("with")[:-1]).strip()
533
+ info["board"] = _machine
534
+ info["cpu"] = _machine.split("with")[-1].strip()
484
535
  info["mpy"] = (
485
- sys.implementation._mpy
536
+ sys.implementation._mpy # type: ignore
486
537
  if "_mpy" in dir(sys.implementation)
487
- else sys.implementation.mpy
538
+ else sys.implementation.mpy # type: ignore
488
539
  if "mpy" in dir(sys.implementation)
489
540
  else ""
490
541
  )
491
542
  except (AttributeError, IndexError):
492
543
  pass
493
- gc.collect()
494
- for filename in [d + "/board_info.csv" for d in LIBS]:
495
- # print("look up the board name in the file", filename)
496
- if file_exists(filename):
497
- # print("Found board info file: {}".format(filename))
498
- b = info["board"].strip()
499
- if find_board(info, b, filename):
500
- break
501
- if "with" in b:
502
- b = b.split("with")[0].strip()
503
- if find_board(info, b, filename):
504
- break
505
- info["board"] = "GENERIC"
506
- info["board"] = info["board"].replace(" ", "_")
507
- gc.collect()
544
+ info["board"] = get_boardname()
508
545
 
509
546
  try:
510
- # extract build from uname().version if available
511
- info["build"] = _build(os.uname()[3])
512
- if not info["build"]:
513
- # extract build from uname().release if available
514
- info["build"] = _build(os.uname()[2])
515
- if not info["build"] and ";" in sys.version:
516
- # extract build from uname().release if available
517
- info["build"] = _build(sys.version.split(";")[1])
518
- except (AttributeError, IndexError):
547
+ if "uname" in dir(os): # old
548
+ # extract build from uname().version if available
549
+ info["build"] = _build(os.uname()[3]) # type: ignore
550
+ if not info["build"]:
551
+ # extract build from uname().release if available
552
+ info["build"] = _build(os.uname()[2]) # type: ignore
553
+ elif "version" in dir(sys): # new
554
+ # extract build from sys.version if available
555
+ info["build"] = _build(sys.version)
556
+ except (AttributeError, IndexError, TypeError):
519
557
  pass
520
558
  # avoid build hashes
521
- if info["build"] and len(info["build"]) > 5:
522
- info["build"] = ""
559
+ # if info["build"] and len(info["build"]) > 5:
560
+ # info["build"] = ""
523
561
 
524
562
  if info["version"] == "" and sys.platform not in ("unix", "win32"):
525
563
  try:
526
- u = os.uname()
564
+ u = os.uname() # type: ignore
527
565
  info["version"] = u.release
528
566
  except (IndexError, AttributeError, TypeError):
529
567
  pass
@@ -545,14 +583,14 @@ def _info(): # type:() -> dict[str, str]
545
583
  info["release"] = "2.0.0"
546
584
 
547
585
  if info["family"] == "micropython":
586
+ info["version"]
548
587
  if (
549
588
  info["version"]
550
589
  and info["version"].endswith(".0")
551
- and info["version"]
552
- >= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
590
+ and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
553
591
  and info["version"] <= "1.19.9"
554
592
  ):
555
- # drop the .0 for newer releases
593
+ # versions from 1.10.0 to 1.20.0 do not have a micro .0
556
594
  info["version"] = info["version"][:-2]
557
595
 
558
596
  # spell-checker: disable
@@ -576,25 +614,31 @@ def _info(): # type:() -> dict[str, str]
576
614
  info["arch"] = arch
577
615
  # .mpy version.minor
578
616
  info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3)
617
+ if info["build"] and not info["version"].endswith("-preview"):
618
+ info["version"] = info["version"] + "-preview"
579
619
  # simple to use version[-build] string
580
- info["ver"] = f"v{info['version']}-{info['build']}" if info["build"] else f"v{info['version']}"
620
+ info["ver"] = f"{info['version']}-{info['build']}" if info["build"] else f"{info['version']}"
581
621
 
582
622
  return info
583
623
 
584
624
 
585
- def find_board(info: dict, board_descr: str, filename: str):
586
- "Find the board in the provided board_info.csv file"
587
- with open(filename, "r") as file:
588
- # ugly code to make testable in python and micropython
589
- while 1:
590
- line = file.readline()
591
- if not line:
592
- break
593
- descr_, board_ = line.split(",")[0].strip(), line.split(",")[1].strip()
594
- if descr_ == board_descr:
595
- info["board"] = board_
596
- return True
597
- return False
625
+ def version_str(version: tuple): # -> str:
626
+ v_str = ".".join([str(n) for n in version[:3]])
627
+ if len(version) > 3 and version[3]:
628
+ v_str += "-" + version[3]
629
+ return v_str
630
+
631
+
632
+ def get_boardname() -> str:
633
+ "Read the board name from the boardname.py file that may have been created upfront"
634
+ try:
635
+ from boardname import BOARDNAME # type: ignore
636
+
637
+ log.info("Found BOARDNAME: {}".format(BOARDNAME))
638
+ except ImportError:
639
+ log.warning("BOARDNAME not found")
640
+ BOARDNAME = ""
641
+ return BOARDNAME
598
642
 
599
643
 
600
644
  def get_root() -> str: # sourcery skip: use-assigned-variable
@@ -647,10 +691,6 @@ def is_micropython() -> bool:
647
691
  # pylint: disable=unused-variable,eval-used
648
692
  try:
649
693
  # either test should fail on micropython
650
- # a) https://docs.micropython.org/en/latest/genrst/syntax.html#spaces
651
- # Micropython : SyntaxError
652
- # a = eval("1and 0") # lgtm [py/unused-local-variable]
653
- # Eval blocks some minification aspects
654
694
 
655
695
  # b) https://docs.micropython.org/en/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented
656
696
  # Micropython: NotImplementedError
@@ -915,16 +955,9 @@ def main():
915
955
  gc.collect()
916
956
 
917
957
  stubber.create_all_stubs()
918
- stubber.report()
919
958
 
920
959
 
921
960
  if __name__ == "__main__" or is_micropython():
922
- try:
923
- log = logging.getLogger("stubber")
924
- logging.basicConfig(level=logging.INFO)
925
- # logging.basicConfig(level=logging.DEBUG)
926
- except NameError:
927
- pass
928
961
  if not file_exists("no_auto_stubber.txt"):
929
962
  try:
930
963
  gc.threshold(4 * 1024) # type: ignore