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
@@ -4,7 +4,6 @@ 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
10
9
  from time import sleep
@@ -24,11 +23,49 @@ try:
24
23
  except ImportError:
25
24
  from ucollections import OrderedDict # type: ignore
26
25
 
27
-
28
- __version__ = "v1.16.3"
26
+ __version__ = "v1.17.0"
29
27
  ENOENT = 2
30
28
  _MAX_CLASS_LEVEL = 2 # Max class nesting
31
- LIBS = [".", "/lib", "/sd/lib", "/flash/lib", "lib"]
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)
32
69
 
33
70
 
34
71
  class Stubber:
@@ -40,11 +77,9 @@ class Stubber:
40
77
  raise NotImplementedError("MicroPython 1.13.0 cannot be stubbed")
41
78
  except AttributeError:
42
79
  pass
43
- self.log = logging.getLogger("stubber")
44
- self._report = [] # type: list[str]
45
80
  self.info = _info()
46
- self.log.info("Port: {}".format(self.info["port"]))
47
- self.log.info("Board: {}".format(self.info["board"]))
81
+ log.info("Port: {}".format(self.info["port"]))
82
+ log.info("Board: {}".format(self.info["board"]))
48
83
  gc.collect()
49
84
  if firmware_id:
50
85
  self._fwid = firmware_id.lower()
@@ -62,11 +97,11 @@ class Stubber:
62
97
  path = get_root()
63
98
 
64
99
  self.path = "{}/stubs/{}".format(path, self.flat_fwid).replace("//", "/")
65
- self.log.debug(self.path)
100
+ # log.debug(self.path)
66
101
  try:
67
102
  ensure_folder(path + "/")
68
103
  except OSError:
69
- self.log.error("error creating stub folder {}".format(path))
104
+ log.error("error creating stub folder {}".format(path))
70
105
  self.problematic = [
71
106
  "upip",
72
107
  "upysh",
@@ -85,21 +120,23 @@ class Stubber:
85
120
  ]
86
121
  # there is no option to discover modules from micropython, list is read from an external file.
87
122
  self.modules = [] # type: list[str]
123
+ self._json_name = None
124
+ self._json_first = False
88
125
 
89
126
  def get_obj_attributes(self, item_instance: object):
90
127
  "extract information of the objects members and attributes"
91
128
  # name_, repr_(value), type as text, item_instance
92
129
  _result = []
93
130
  _errors = []
94
- self.log.debug("get attributes {} {}".format(repr(item_instance), item_instance))
131
+ # log.debug("get attributes {} {}".format(repr(item_instance), item_instance))
95
132
  for name in dir(item_instance):
96
- if name.startswith("_") and not name in self.modules:
133
+ if name.startswith("__") and not name in self.modules:
97
134
  continue
98
- self.log.debug("get attribute {}".format(name))
135
+ # log.debug("get attribute {}".format(name))
99
136
  try:
100
137
  val = getattr(item_instance, name)
101
138
  # name , item_repr(value) , type as text, item_instance, order
102
- self.log.debug("attribute {}:{}".format(name, val))
139
+ # log.debug("attribute {}:{}".format(name, val))
103
140
  try:
104
141
  type_text = repr(type(val)).split("'")[1]
105
142
  except IndexError:
@@ -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,10 +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("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1))
220
+ log.info("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1))
182
221
 
183
222
  except ImportError:
184
- 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."))
185
224
  return False
186
225
 
187
226
  # Start a new file
@@ -193,22 +232,20 @@ class Stubber:
193
232
  module_name, self._fwid, info_, __version__
194
233
  )
195
234
  fp.write(s)
196
- fp.write("from __future__ import annotations\nfrom 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
+ )
197
238
  self.write_object_stub(fp, new_module, module_name, "")
198
239
 
199
- self._report.append('{{"module": "{}", "file": "{}"}}'.format(module_name, file_name.replace("\\", "/")))
240
+ self.report_add(module_name, file_name)
200
241
 
201
242
  if module_name not in {"os", "sys", "logging", "gc"}:
202
243
  # try to unload the module unless we use it
203
244
  try:
204
245
  del new_module
205
246
  except (OSError, KeyError): # lgtm [py/unreachable-statement]
206
- self.log.warning("could not del new_module")
207
- # lets not try - most times it does not work anyway
208
- # try:
209
- # del sys.modules[module_name]
210
- # except KeyError:
211
- # self.log.warning("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
212
249
  gc.collect()
213
250
  return True
214
251
 
@@ -216,14 +253,14 @@ class Stubber:
216
253
  "Write a module/object stub to an open file. Can be called recursive."
217
254
  gc.collect()
218
255
  if object_expr in self.problematic:
219
- self.log.warning("SKIPPING problematic module:{}".format(object_expr))
256
+ log.warning("SKIPPING problematic module:{}".format(object_expr))
220
257
  return
221
258
 
222
- # self.log.debug("DUMP : {}".format(object_expr))
259
+ # # log.debug("DUMP : {}".format(object_expr))
223
260
  items, errors = self.get_obj_attributes(object_expr)
224
261
 
225
262
  if errors:
226
- self.log.error(errors)
263
+ log.error(errors)
227
264
 
228
265
  for item_name, item_repr, item_type_txt, item_instance, _ in items:
229
266
  # name_, repr_(value), type as text, item_instance, order
@@ -231,7 +268,7 @@ class Stubber:
231
268
  # do not create stubs for these primitives
232
269
  continue
233
270
  if item_name[0].isdigit():
234
- self.log.warning("NameError: invalid name {}".format(item_name))
271
+ log.warning("NameError: invalid name {}".format(item_name))
235
272
  continue
236
273
  # Class expansion only on first 3 levels (bit of a hack)
237
274
  if (
@@ -240,7 +277,7 @@ class Stubber:
240
277
  # and not obj_name.endswith(".Pin")
241
278
  # avoid expansion of Pin.cpu / Pin.board to avoid crashes on most platforms
242
279
  ):
243
- self.log.info("{0}class {1}:".format(indent, item_name))
280
+ # log.debug("{0}class {1}:".format(indent, item_name))
244
281
  superclass = ""
245
282
  is_exception = (
246
283
  item_name.endswith("Exception")
@@ -263,7 +300,7 @@ class Stubber:
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,7 +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("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name))
317
+ # log.debug("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name))
281
318
  # module Function or class method
282
319
  # will accept any number of params
283
320
  # return type Any/Incomplete
@@ -295,7 +332,7 @@ class Stubber:
295
332
  s = "{}def {}({}*args, **kwargs) -> {}:\n".format(indent, item_name, first, ret)
296
333
  s += indent + " ...\n\n"
297
334
  fp.write(s)
298
- self.log.debug("\n" + s)
335
+ # log.debug("\n" + s)
299
336
  elif item_type_txt == "<class 'module'>":
300
337
  # Skip imported modules
301
338
  # fp.write("# import {}\n".format(item_name))
@@ -305,28 +342,32 @@ class Stubber:
305
342
  t = item_type_txt[8:-2]
306
343
  s = ""
307
344
 
308
- if t in ["str", "int", "float", "bool", "bytearray", "bytes"]:
345
+ if t in ("str", "int", "float", "bool", "bytearray", "bytes"):
309
346
  # known type: use actual value
310
- s = "{0}{1} = {2} # type: {3}\n".format(indent, item_name, item_repr, t)
311
- 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"):
312
350
  # dict, list , tuple: use empty value
313
351
  ev = {"dict": "{}", "list": "[]", "tuple": "()"}
314
- 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)
315
354
  else:
316
355
  # something else
317
- if t in ["object", "set", "frozenset", "Pin", "FileIO"]:
356
+ if t in ("object", "set", "frozenset", "Pin", "generator"): # "FileIO"
318
357
  # https://docs.python.org/3/tutorial/classes.html#item_instance-objects
319
- # use these types for the attribute
320
- s = "{0}{1} : {2} ## = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
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)
321
362
  else:
322
363
  # Requires Python 3.6 syntax, which is OK for the stubs/pyi
323
364
  t = "Incomplete"
324
- s = "{0}{1} : {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
365
+ s = "{0}{1}: {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr)
325
366
  fp.write(s)
326
- self.log.debug("\n" + s)
367
+ # log.debug("\n" + s)
327
368
  else:
328
369
  # keep only the name
329
- self.log.debug("# all other, type = '{0}'".format(item_type_txt))
370
+ # log.debug("# all other, type = '{0}'".format(item_type_txt))
330
371
  fp.write("# all other, type = '{0}'\n".format(item_type_txt))
331
372
 
332
373
  fp.write(indent + item_name + " # type: Incomplete\n")
@@ -352,7 +393,7 @@ class Stubber:
352
393
  "Remove all files from the stub folder"
353
394
  if path is None:
354
395
  path = self.path
355
- self.log.info("Clean/remove files in folder: {}".format(path))
396
+ log.info("Clean/remove files in folder: {}".format(path))
356
397
  try:
357
398
  os.stat(path) # TEMP workaround mpremote listdir bug -
358
399
  items = os.listdir(path)
@@ -370,43 +411,53 @@ class Stubber:
370
411
  except OSError:
371
412
  pass
372
413
 
373
- def report(self, filename: str = "modules.json"):
374
- "create json with list of exported modules"
375
- self.log.info(
376
- "Created stubs for {} modules on board {}\nPath: {}".format(len(self._report), self._fwid, self.path)
377
- )
378
- f_name = "{}/{}".format(self.path, filename)
379
- 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))
380
421
  gc.collect()
381
422
  try:
382
423
  # write json by node to reduce memory requirements
383
- with open(f_name, "w") as f:
384
- self.write_json_header(f)
385
- first = True
386
- for n in self._report:
387
- self.write_json_node(f, n, first)
388
- first = False
389
- self.write_json_end(f)
390
- used = self._start_free - gc.mem_free() # type: ignore
391
- self.log.info("Memory used: {0} Kb".format(used // 1024))
392
- except OSError:
393
- self.log.error("Failed to create the report.")
394
-
395
- def write_json_header(self, f):
396
- f.write("{")
397
- f.write(dumps({"firmware": self.info})[1:-1])
398
- f.write(",\n")
399
- f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1])
400
- f.write(",\n")
401
- 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)
402
450
 
403
- def write_json_node(self, f, n, first):
404
- if not first:
405
- f.write(",\n")
406
- f.write(n)
451
+ except OSError:
452
+ log.error("Failed to create the report.")
407
453
 
408
- def write_json_end(self, f):
409
- 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))
410
461
 
411
462
 
412
463
  def ensure_folder(path: str):
@@ -452,7 +503,7 @@ def _build(s):
452
503
  def _info(): # type:() -> dict[str, str]
453
504
  info = OrderedDict(
454
505
  {
455
- "family": sys.implementation.name,
506
+ "family": sys.implementation.name, # type: ignore
456
507
  "version": "",
457
508
  "build": "",
458
509
  "ver": "",
@@ -482,9 +533,9 @@ def _info(): # type:() -> dict[str, str]
482
533
  info["board"] = _machine
483
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
  )
@@ -582,9 +633,10 @@ def get_boardname() -> str:
582
633
  "Read the board name from the boardname.py file that may have been created upfront"
583
634
  try:
584
635
  from boardname import BOARDNAME # type: ignore
636
+
585
637
  log.info("Found BOARDNAME: {}".format(BOARDNAME))
586
638
  except ImportError:
587
- log.info("BOARDNAME not found")
639
+ log.warning("BOARDNAME not found")
588
640
  BOARDNAME = ""
589
641
  return BOARDNAME
590
642
 
@@ -639,10 +691,6 @@ def is_micropython() -> bool:
639
691
  # pylint: disable=unused-variable,eval-used
640
692
  try:
641
693
  # either test should fail on micropython
642
- # a) https://docs.micropython.org/en/latest/genrst/syntax.html#spaces
643
- # Micropython : SyntaxError
644
- # a = eval("1and 0") # lgtm [py/unused-local-variable]
645
- # Eval blocks some minification aspects
646
694
 
647
695
  # b) https://docs.micropython.org/en/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented
648
696
  # Micropython: NotImplementedError
@@ -907,16 +955,9 @@ def main():
907
955
  gc.collect()
908
956
 
909
957
  stubber.create_all_stubs()
910
- stubber.report()
911
958
 
912
959
 
913
960
  if __name__ == "__main__" or is_micropython():
914
- try:
915
- log = logging.getLogger("stubber")
916
- logging.basicConfig(level=logging.INFO)
917
- # logging.basicConfig(level=logging.DEBUG)
918
- except NameError:
919
- pass
920
961
  if not file_exists("no_auto_stubber.txt"):
921
962
  try:
922
963
  gc.threshold(4 * 1024) # type: ignore