micropython-stubber 1.23.3__py3-none-any.whl → 1.24.1__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 (69) hide show
  1. {micropython_stubber-1.23.3.dist-info → micropython_stubber-1.24.1.dist-info}/METADATA +29 -11
  2. {micropython_stubber-1.23.3.dist-info → micropython_stubber-1.24.1.dist-info}/RECORD +68 -65
  3. {micropython_stubber-1.23.3.dist-info → micropython_stubber-1.24.1.dist-info}/WHEEL +1 -1
  4. mpflash/README.md +2 -2
  5. mpflash/mpflash/basicgit.py +22 -2
  6. mpflash/mpflash/common.py +23 -13
  7. mpflash/mpflash/downloaded.py +10 -2
  8. mpflash/mpflash/flash/esp.py +1 -1
  9. mpflash/mpflash/mpboard_id/__init__.py +9 -4
  10. mpflash/mpflash/mpboard_id/add_boards.py +25 -14
  11. mpflash/mpflash/mpboard_id/board.py +2 -2
  12. mpflash/mpflash/mpboard_id/board_id.py +10 -6
  13. mpflash/mpflash/mpboard_id/board_info.zip +0 -0
  14. mpflash/mpflash/mpremoteboard/__init__.py +13 -8
  15. mpflash/mpflash/vendor/board_database.py +185 -0
  16. mpflash/mpflash/vendor/readme.md +10 -1
  17. mpflash/mpflash/versions.py +28 -40
  18. mpflash/poetry.lock +1147 -231
  19. mpflash/pyproject.toml +4 -3
  20. stubber/__init__.py +1 -1
  21. stubber/board/createstubs.py +76 -34
  22. stubber/board/createstubs_db.py +34 -25
  23. stubber/board/createstubs_db_min.py +90 -83
  24. stubber/board/createstubs_db_mpy.mpy +0 -0
  25. stubber/board/createstubs_mem.py +34 -25
  26. stubber/board/createstubs_mem_min.py +123 -116
  27. stubber/board/createstubs_mem_mpy.mpy +0 -0
  28. stubber/board/createstubs_min.py +154 -145
  29. stubber/board/createstubs_mpy.mpy +0 -0
  30. stubber/board/modulelist.txt +16 -0
  31. stubber/codemod/enrich.py +301 -86
  32. stubber/codemod/merge_docstub.py +251 -66
  33. stubber/codemod/test_enrich.py +87 -0
  34. stubber/codemod/visitors/type_helpers.py +182 -0
  35. stubber/commands/build_cmd.py +16 -3
  36. stubber/commands/clone_cmd.py +3 -3
  37. stubber/commands/config_cmd.py +4 -2
  38. stubber/commands/enrich_folder_cmd.py +33 -21
  39. stubber/commands/get_core_cmd.py +1 -2
  40. stubber/commands/get_docstubs_cmd.py +60 -6
  41. stubber/commands/get_frozen_cmd.py +15 -12
  42. stubber/commands/get_mcu_cmd.py +3 -3
  43. stubber/commands/merge_cmd.py +1 -2
  44. stubber/commands/publish_cmd.py +19 -4
  45. stubber/commands/stub_cmd.py +3 -3
  46. stubber/commands/switch_cmd.py +3 -5
  47. stubber/commands/variants_cmd.py +3 -3
  48. stubber/cst_transformer.py +52 -17
  49. stubber/freeze/common.py +27 -11
  50. stubber/freeze/freeze_manifest_2.py +8 -1
  51. stubber/freeze/get_frozen.py +4 -1
  52. stubber/merge_config.py +111 -0
  53. stubber/minify.py +1 -2
  54. stubber/publish/database.py +51 -10
  55. stubber/publish/merge_docstubs.py +38 -17
  56. stubber/publish/package.py +32 -18
  57. stubber/publish/publish.py +8 -8
  58. stubber/publish/stubpackage.py +117 -50
  59. stubber/rst/lookup.py +205 -41
  60. stubber/rst/reader.py +106 -59
  61. stubber/rst/rst_utils.py +24 -11
  62. stubber/stubber.py +1 -1
  63. stubber/stubs_from_docs.py +31 -13
  64. stubber/update_module_list.py +2 -2
  65. stubber/utils/config.py +33 -13
  66. stubber/utils/post.py +9 -6
  67. stubber/publish/missing_class_methods.py +0 -51
  68. {micropython_stubber-1.23.3.dist-info → micropython_stubber-1.24.1.dist-info}/LICENSE +0 -0
  69. {micropython_stubber-1.23.3.dist-info → micropython_stubber-1.24.1.dist-info}/entry_points.txt +0 -0
stubber/rst/reader.py CHANGED
@@ -69,10 +69,12 @@ from typing import List, Optional, Tuple
69
69
 
70
70
  from mpflash.logger import log
71
71
  from mpflash.versions import V_PREVIEW
72
+
72
73
  from stubber.rst import (
73
74
  CHILD_PARENT_CLASS,
74
75
  MODULE_GLUE,
75
76
  PARAM_FIXES,
77
+ PARAM_RE_FIXES,
76
78
  RST_DOC_FIXES,
77
79
  TYPING_IMPORT,
78
80
  ClassSourceDict,
@@ -83,14 +85,17 @@ from stubber.rst import (
83
85
  from stubber.rst.lookup import Fix
84
86
  from stubber.utils.config import CONFIG
85
87
 
86
- SEPERATOR = "::"
88
+ SEPARATOR = "::"
89
+ USE_SUBMODULES = True
87
90
 
88
91
 
89
92
  class FileReadWriter:
90
93
  """base class for reading rst files"""
91
94
 
92
95
  def __init__(self):
93
- self.filename = ""
96
+ self.filename: str = ""
97
+ # self.modulename: str = ""
98
+ # self.out_file: str = "__init__.pyi"
94
99
  # input buffer
95
100
  self.rst_text: List[str] = []
96
101
  self.max_line = 0
@@ -111,6 +116,8 @@ class FileReadWriter:
111
116
  # some lines now may have \n sin them , so re-join and re-split the lines
112
117
  self.rst_text = "".join(self.rst_text).splitlines(keepends=True)
113
118
 
119
+ self.modulename = filename.stem
120
+ self.out_file = "__init__.pyi"
114
121
  self.filename = filename.as_posix() # use fwd slashes in origin
115
122
  self.max_line = len(self.rst_text) - 1
116
123
 
@@ -148,7 +155,11 @@ class FileReadWriter:
148
155
  """
149
156
  append = 0
150
157
  newline = self.rst_text[self.line_no]
151
- while not self.is_balanced(newline) and self.line_no >= 0 and (self.line_no + append + 1) <= self.max_line:
158
+ while (
159
+ not self.is_balanced(newline)
160
+ and self.line_no >= 0
161
+ and (self.line_no + append + 1) <= self.max_line
162
+ ):
152
163
  append += 1
153
164
  # concat the lines
154
165
  newline += self.rst_text[self.line_no + append]
@@ -177,6 +188,7 @@ class RSTReader(FileReadWriter):
177
188
  self.current_module = ""
178
189
  self.current_class = ""
179
190
  self.current_function = "" # function & method
191
+ self.clean_rst = True
180
192
  super().__init__()
181
193
 
182
194
  def read_file(self, filename: Path):
@@ -215,11 +227,13 @@ class RSTReader(FileReadWriter):
215
227
  # return _l.startswith("..") and not any(_l.startswith(a) for a in self.docstring_anchors)
216
228
 
217
229
  # @property
218
- def at_heading(self, large=False) -> bool:
230
+ def at_heading(self, large: bool = False) -> bool:
219
231
  "stop at heading"
220
232
  u_line = self.rst_text[min(self.line_no + 1, self.max_line - 1)].rstrip()
221
233
  # Heading ---, ==, ~~~
222
- underlined = u_line.startswith("---") or u_line.startswith("===") or u_line.startswith("~~~")
234
+ underlined = (
235
+ u_line.startswith("---") or u_line.startswith("===") or u_line.startswith("~~~")
236
+ )
223
237
  if underlined and self.line_no > 0:
224
238
  # check if previous line is a heading
225
239
  line = self.rst_text[self.line_no].strip()
@@ -278,22 +292,23 @@ class RSTReader(FileReadWriter):
278
292
  # remove empty lines at beginning/end of block
279
293
  block = self.clean_docstr(block)
280
294
  # add clickable hyperlinks to CPython docpages
281
- block = self.add_link_to_docsstr(block)
295
+ block = self.add_link_to_docstr(block)
282
296
  # make sure the first char of the first line is a capital
283
297
  if len(block) > 0 and len(block[0]) > 0:
284
298
  block[0] = block[0][0].upper() + block[0][1:]
285
299
  return block
286
300
 
287
- @staticmethod
288
- def clean_docstr(block: List[str]):
301
+ # @staticmethod
302
+ def clean_docstr(self, block: List[str]):
289
303
  """Clean up a docstring"""
290
- # if a Quoted Literal Block , then remove the first character of each line
291
- # https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#quoted-literal-blocks
292
- if block and len(block[0]) > 0 and block[0][0] != " ":
293
- q_char = block[0][0]
294
- if all(l.startswith(q_char) for l in block):
295
- # all lines start with the same character, so skip that character
296
- block = [l[1:] for l in block]
304
+ if self.clean_rst:
305
+ # if a Quoted Literal Block , then remove the first character of each line
306
+ # https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#quoted-literal-blocks
307
+ if block and len(block[0]) > 0 and block[0][0] != " ":
308
+ q_char = block[0][0]
309
+ if all(l.startswith(q_char) for l in block):
310
+ # all lines start with the same character, so skip that character
311
+ block = [l[1:] for l in block]
297
312
  # rstrip all lines
298
313
  block = [l.rstrip() for l in block]
299
314
  # remove empty lines at beginning/end of block
@@ -301,7 +316,6 @@ class RSTReader(FileReadWriter):
301
316
  block = block[1:]
302
317
  while len(block) and len(block[-1]) == 0:
303
318
  block = block[:-1]
304
-
305
319
  # Clean up Synopsis
306
320
  if len(block) and ":synopsis:" in block[0]:
307
321
  block[0] = re.sub(
@@ -311,15 +325,18 @@ class RSTReader(FileReadWriter):
311
325
  )
312
326
  return block
313
327
 
314
- @staticmethod
315
- def add_link_to_docsstr(block: List[str]):
316
- """Add clickable hyperlinks to CPython docpages"""
328
+ def add_link_to_docstr(self, block: List[str]):
329
+ """Add clickable hyperlinks to CPython docpages,
330
+ and clean the docstring from rst constructs"""
331
+ if not self.clean_rst:
332
+ return block
333
+
317
334
  for i in range(len(block)):
318
335
  # hyperlink to Cpython doc pages
319
336
  # https://regex101.com/r/5RN8rj/1
320
337
  # Link to python 3 documentation
321
338
  _l = re.sub(
322
- r"(\s*\|see_cpython_module\|\s+:mod:`python:(?P<mod>[\w|\s]*)`)[.]?",
339
+ r"(\s*\|see_cpython_module\|\s+:mod:`python:(?P<mod>[\w|]*)\s?`)[.]?",
323
340
  r"\g<1> https://docs.python.org/3/library/\g<mod>.html .",
324
341
  block[i],
325
342
  )
@@ -333,7 +350,10 @@ class RSTReader(FileReadWriter):
333
350
  # Clean up note and other docstring anchors
334
351
  _l = _l.replace(".. note:: ", "``Note:`` ")
335
352
  _l = _l.replace(".. data:: ", "")
336
- _l = _l.replace(".. admonition:: ", "")
353
+ _l = _l.replace(".. admonition:: ", "Admonition:")
354
+ # remove rst highlights from docstrings
355
+ _l = _l.replace(":class: attention\n", "")
356
+ # Sphinx directive to link to CPython documentation
337
357
  _l = _l.replace("|see_cpython_module|", "CPython module:")
338
358
  # clean up unsupported escape sequences in rst
339
359
  _l = _l.replace(r"\ ", " ")
@@ -347,7 +367,7 @@ class RSTReader(FileReadWriter):
347
367
  return m[1] if m else ""
348
368
 
349
369
  def strip_prefixes(self, name: str, strip_mod: bool = True, strip_class: bool = False):
350
- "Remove the modulename. and or the classname. from the begining of a name"
370
+ "Remove the modulename. and or the classname. from the beginning of a name"
351
371
  prefixes = self.module_names if strip_mod else []
352
372
  if strip_class and self.current_class != "":
353
373
  prefixes += [self.current_class]
@@ -364,12 +384,6 @@ class RSTParser(RSTReader):
364
384
  """
365
385
 
366
386
  target = ".py" # py/pyi
367
- # TODO: Move to lookup.py
368
- PARAM_RE_FIXES = [
369
- Fix(r"\[angle, time=0\]", "[angle], time=0", is_re=True), # fix: method:: Servo.angle([angle, time=0])
370
- Fix(r"\[speed, time=0\]", "[speed], time=0", is_re=True), # fix: .. method:: Servo.speed([speed, time=0])
371
- Fix(r"\[service_id, key=None, \*, \.\.\.\]", "[service_id], [key], *, ...", is_re=True), # fix: network - AbstractNIC.connect
372
- ]
373
387
 
374
388
  def __init__(self, v_tag: str) -> None:
375
389
  super().__init__()
@@ -398,7 +412,7 @@ class RSTParser(RSTReader):
398
412
 
399
413
  ## Deal with SQUARE brackets first ( Documentation meaning := [optional])
400
414
 
401
- for fix in self.PARAM_RE_FIXES:
415
+ for fix in PARAM_RE_FIXES:
402
416
  params = self.apply_fix(fix, params, name)
403
417
 
404
418
  # ###########################################################################################################
@@ -432,7 +446,9 @@ class RSTParser(RSTReader):
432
446
  def apply_fix(fix: Fix, params: str, name: str = ""):
433
447
  if fix.name and fix.name != name:
434
448
  return params
435
- return re.sub(fix.from_, fix.to, params) if fix.is_re else params.replace(fix.from_, fix.to)
449
+ return (
450
+ re.sub(fix.from_, fix.to, params) if fix.is_re else params.replace(fix.from_, fix.to)
451
+ )
436
452
 
437
453
  def create_update_class(self, name: str, params: str, docstr: List[str]):
438
454
  # a bit of a hack: assume no classes in classes or functions in function
@@ -442,7 +458,9 @@ class RSTParser(RSTReader):
442
458
  class_def = self.output_dict[full_name]
443
459
  else:
444
460
  parent = CHILD_PARENT_CLASS[name] if name in CHILD_PARENT_CLASS.keys() else ""
445
- if parent == "" and (name.endswith("Error") or name.endswith("Exception")):
461
+ if parent == "" and (
462
+ name.endswith("Error") or name.endswith("Exception") or name in {"GeneratorExit"}
463
+ ):
446
464
  parent = "Exception"
447
465
  class_def = ClassSourceDict(
448
466
  f"class {name}({parent}):",
@@ -467,12 +485,25 @@ class RSTParser(RSTReader):
467
485
  toctree = self.read_docstring()
468
486
  # cleanup toctree
469
487
  toctree = [x.strip() for x in toctree if f"{self.current_module}." in x]
470
- # Now parse all files mentioned in the toc
471
- for file in toctree:
472
- #
473
- file_path = CONFIG.mpy_path / "docs" / "library" / file.strip()
474
- self.read_file(file_path)
475
- self.parse()
488
+
489
+ if USE_SUBMODULES:
490
+ # add sub modules imports
491
+ for file in toctree:
492
+ submod_name = file.replace(".rst", "")
493
+ rel_name = submod_name.replace(f"{self.modulename}.", ".")
494
+ assumed_class = rel_name.lstrip(".") # .capitalize()
495
+
496
+ # absolute class import with class name based on the module name
497
+ self.output_dict.add_import(f"from {submod_name} import {assumed_class}")
498
+
499
+ else:
500
+ # Now parse all files mentioned in the toc
501
+ # sub modules are now processed as individual files
502
+ for file in toctree:
503
+ #
504
+ file_path = CONFIG.mpy_path / "docs" / "library" / file.strip()
505
+ self.read_file(file_path)
506
+ self.parse()
476
507
  # reset this file to done
477
508
  self.rst_text = []
478
509
  self.line_no = 1
@@ -480,7 +511,7 @@ class RSTParser(RSTReader):
480
511
  def parse_module(self):
481
512
  "parse a module tag and set the module's docstring"
482
513
  log.trace(f"# {self.line.rstrip()}")
483
- module_name = self.line.split(SEPERATOR)[-1].strip()
514
+ module_name = self.line.split(SEPARATOR)[-1].strip()
484
515
 
485
516
  self.current_module = module_name
486
517
  self.current_function = self.current_class = ""
@@ -493,31 +524,40 @@ class RSTParser(RSTReader):
493
524
  if "nightly" in self.source_tag:
494
525
  version = V_PREVIEW
495
526
  else:
496
- version = self.source_tag.replace("_", ".") # TODO Use clean_version(self.source_tag)
497
- docstr[0] = f"{docstr[0]}.\n\nMicroPython module: https://docs.micropython.org/en/{version}/library/{module_name}.html"
527
+ version = self.source_tag.replace("_", ".")
528
+ # documentation is not available for patch versions, so use only the major.minor.0
529
+ version = ".".join(version.split(".")[:2]) + ".0"
530
+ # TODO Use clean_version(self.source_tag)
531
+ docstr[0] = (
532
+ f"{docstr[0]}.\n\nMicroPython module: https://docs.micropython.org/en/{version}/library/{module_name}.html"
533
+ )
498
534
 
499
535
  self.output_dict.name = module_name
500
536
  self.output_dict.add_comment(f"# source version: {self.source_tag}")
501
537
  self.output_dict.add_comment(f"# origin module:: {self.filename}")
502
538
  self.output_dict.add_docstr(docstr)
503
- # Add additional imports to allow one module te refer to another
539
+ # Add additional imports to allow one module to refer to another
504
540
  if module_name in MODULE_GLUE.keys():
505
541
  self.output_dict.add_import(MODULE_GLUE[module_name])
506
542
 
507
543
  def parse_current_module(self):
508
544
  log.trace(f"# {self.line.rstrip()}")
509
- module_name = self.line.split(SEPERATOR)[-1].strip()
510
- mod_comment = f"# + module: {self.current_module}.rst"
511
- self.current_module = module_name
545
+ # module_name = self.line.split(SEPARATOR)[-1].strip()
546
+ # mod_comment = f"# + module: {self.current_module}.rst"
547
+ # now that each .rst is a (sub) module we can process them as such
548
+ # log.debug(mod_comment)
549
+ # self.current_module = module_name
512
550
  self.current_function = self.current_class = ""
513
- log.debug(mod_comment)
514
- self.output_dict.name = module_name
515
- self.output_dict.add_comment(mod_comment)
551
+ self.output_dict.name = self.current_module
552
+ # self.output_dict.add_comment(mod_comment)
516
553
  self.line_no += 1 # advance as we did not read any docstring
554
+ # Add additional imports to allow one module to refer to another
555
+ if self.current_module in MODULE_GLUE.keys():
556
+ self.output_dict.add_import(MODULE_GLUE[self.current_module])
517
557
 
518
558
  def parse_function(self):
519
559
  log.trace(f"# {self.line.rstrip()}")
520
- # this_function = self.line.split(SEPERATOR)[-1].strip()
560
+ # this_function = self.line.split(SEPARATOR)[-1].strip()
521
561
  # name = this_function
522
562
 
523
563
  # Get one or more names
@@ -526,7 +566,9 @@ class RSTParser(RSTReader):
526
566
 
527
567
  for this_function in function_names:
528
568
  # Parse return type from docstring
529
- ret_type = return_type_from_context(docstring=docstr, signature=this_function, module=self.current_module)
569
+ ret_type = return_type_from_context(
570
+ docstring=docstr, signature=this_function, module=self.current_module
571
+ )
530
572
 
531
573
  # defaults
532
574
  name = params = ""
@@ -548,7 +590,7 @@ class RSTParser(RSTReader):
548
590
  # fixup parameters
549
591
  params = self.fix_parameters(params, name)
550
592
  # ASSUME no functions in classes,
551
- # so with ther cursor at a function this probably means that we are no longer in a class
593
+ # so with the cursor at a function this probably means that we are no longer in a class
552
594
  self.leave_class()
553
595
 
554
596
  fn_def = FunctionSourceDict(
@@ -560,7 +602,7 @@ class RSTParser(RSTReader):
560
602
 
561
603
  def parse_class(self):
562
604
  log.trace(f"# {self.line.rstrip()}")
563
- this_class = self.line.split(SEPERATOR)[-1].strip() # raw
605
+ this_class = self.line.split(SEPARATOR)[-1].strip() # raw
564
606
  if "(" in this_class:
565
607
  name, params = this_class.split("(", 2)
566
608
  else:
@@ -632,7 +674,9 @@ class RSTParser(RSTReader):
632
674
  params = self.fix_parameters(params, f"{class_name}.{name}")
633
675
 
634
676
  # parse return type from docstring
635
- ret_type = return_type_from_context(docstring=docstr, signature=f"{class_name}.{name}", module=self.current_module)
677
+ ret_type = return_type_from_context(
678
+ docstring=docstr, signature=f"{class_name}.{name}", module=self.current_module
679
+ )
636
680
  # methods have 4 flavours
637
681
  # - __init__ (self, <params>) -> None:
638
682
  # - classmethod (cls, <params>) -> <ret_type>:
@@ -692,7 +736,7 @@ class RSTParser(RSTReader):
692
736
 
693
737
  def parse_exception(self):
694
738
  log.trace(f"# {self.line.rstrip()}")
695
- name = self.line.split(SEPERATOR)[1].strip()
739
+ name = self.line.split(SEPARATOR)[1].strip()
696
740
  if name == "Exception":
697
741
  # no need to redefine Exception
698
742
  self.line_no += 1
@@ -709,9 +753,9 @@ class RSTParser(RSTReader):
709
753
  "get the constant/function/class name from a line with an identifier"
710
754
  # '.. data:: espnow.MAX_DATA_LEN(=250)\n'
711
755
  if line:
712
- return line.split(SEPERATOR)[-1].strip()
756
+ return line.split(SEPARATOR)[-1].strip()
713
757
  else:
714
- return self.line.split(SEPERATOR)[-1].strip()
758
+ return self.line.split(SEPARATOR)[-1].strip()
715
759
 
716
760
  def parse_names(self, oneliner: bool = True):
717
761
  """get a list of constant/function/class names from and following a line with an identifier
@@ -734,7 +778,7 @@ class RSTParser(RSTReader):
734
778
  log.trace("Sequence detected")
735
779
  names.append(self.parse_name(self.rst_text[self.line_no + counter]))
736
780
  counter += 1
737
- # now advance the linecounter
781
+ # now advance the line counter
738
782
  self.line_no += counter - 1
739
783
  # clean up before returning
740
784
  names = [n.strip() for n in names if n.strip() != "etc."] # remove etc.
@@ -753,7 +797,9 @@ class RSTParser(RSTReader):
753
797
 
754
798
  # deal with documentation wildcards
755
799
  for name in names:
756
- r_type = return_type_from_context(docstring=docstr, signature=name, module=self.current_module, literal=True)
800
+ r_type = return_type_from_context(
801
+ docstring=docstr, signature=name, module=self.current_module, literal=True
802
+ )
757
803
  if r_type in ["None"]: # None does not make sense
758
804
  r_type = "Incomplete" # Default to Incomplete/ Unknown / int
759
805
  name = self.strip_prefixes(name)
@@ -800,9 +846,10 @@ class RSTWriter(RSTParser):
800
846
  def __init__(self, v_tag="v1.xx"):
801
847
  super().__init__(v_tag=v_tag)
802
848
 
803
- def write_file(self, filename: Path) -> bool:
849
+ def write_file(self, target: Path) -> bool:
804
850
  self.prepare_output()
805
- return super().write_file(filename)
851
+ target.parent.mkdir(parents=True, exist_ok=True)
852
+ return super().write_file(target)
806
853
 
807
854
  def prepare_output(self):
808
855
  "Remove trailing spaces and commas from the output."
stubber/rst/rst_utils.py CHANGED
@@ -318,6 +318,7 @@ def distill_return(return_text: str) -> List[Dict]:
318
318
  match_string,
319
319
  [
320
320
  "tuple",
321
+ "a tuple",
321
322
  "a pair",
322
323
  "1-tuple",
323
324
  "2-tuple",
@@ -459,7 +460,7 @@ def _type_from_context(
459
460
  - then parses the docstring to find references to known types and give then a rating though a hand coded model ()
460
461
  - builds a list return type candidates
461
462
  - selects the highest ranking candidate
462
- - the default Type is 'Any'
463
+ - the default Type is 'Incomplete'
463
464
  """
464
465
 
465
466
  if isinstance(docstring, list):
@@ -494,19 +495,31 @@ def _type_from_context(
494
495
 
495
496
  # ------------------------------------------------------
496
497
  # lookup returns that cannot be found based on the docstring from the lookup list
497
- try:
498
- function_name = function_re.findall(signature)[0]
499
- except IndexError:
500
- function_name = signature.strip().strip(":()")
501
-
502
- function_name = ".".join((module, function_name))
503
-
504
- if function_name in LOOKUP_LIST.keys():
505
- sig_type = LOOKUP_LIST[function_name][0]
498
+ if "(" in signature:
499
+ # method / function / class
500
+ try:
501
+ item_name = function_re.findall(signature)[0]
502
+ except IndexError:
503
+ item_name = signature.strip().strip(":()")
504
+ else:
505
+ # module or class attribute
506
+ item_name = signature.replace(".. data::", "").strip()
507
+
508
+ mod_last = module.split(".")[-1]
509
+ item_first = item_name.split(".")[0]
510
+ name_repeat = mod_last == item_first
511
+ if name_repeat:
512
+ # avoid machine.UART.UART.irq or pyb.pyb.hid_mouse
513
+ item_name = f"{module}.{item_name}".replace(f"{mod_last}.{item_first}", mod_last)
514
+ else:
515
+ item_name = f"{module}.{item_name}"
516
+
517
+ if item_name in LOOKUP_LIST.keys():
518
+ sig_type = LOOKUP_LIST[item_name][0]
506
519
  return {
507
520
  "type": sig_type,
508
521
  "confidence": C_LOOKUP * WEIGHT_LOOPUPS,
509
- "match": function_name,
522
+ "match": item_name,
510
523
  }
511
524
  # ------------------------------------------------------
512
525
  # parse the docstring for simple start verbs,
stubber/stubber.py CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
- """Create, Process, and Maintain stubs ✏️ for MicroPython"""
4
+ """Create, Process, Merge and Maintain stubs ✏️ for MicroPython"""
5
5
 
6
6
 
7
7
  from stubber.commands.build_cmd import cli_build
@@ -20,8 +20,10 @@ def generate_from_rst(
20
20
  v_tag: str,
21
21
  release: Optional[str] = None,
22
22
  pattern: str = "*.rst",
23
- suffix: str = ".py",
23
+ suffix: str = ".pyi",
24
24
  black: bool = True,
25
+ autoflake: bool = True,
26
+ clean_rst: bool = True,
25
27
  ) -> int:
26
28
  dst_path.mkdir(parents=True, exist_ok=True)
27
29
  if not release:
@@ -32,15 +34,15 @@ def generate_from_rst(
32
34
 
33
35
  files = get_rst_sources(rst_path, pattern)
34
36
 
35
- # simplify debugging
36
- # files = [f for f in files if f.name == "collections.rst"]
37
+ # reduce files for test/debugging
38
+ # files = [f for f in files if "machine" in f.name]
37
39
 
38
40
  clean_destination(dst_path)
39
- make_docstubs(dst_path, v_tag, release, suffix, files)
41
+ make_docstubs(dst_path, v_tag, release, suffix, files, clean_rst=clean_rst)
40
42
 
41
43
  log.info("::group:: start post processing of retrieved stubs")
42
44
  # do not run stubgen
43
- utils.do_post_processing([dst_path], stubgen=False, black=black, autoflake=True)
45
+ utils.do_post_processing([dst_path], stubgen=False, black=black, autoflake=autoflake)
44
46
 
45
47
  # Generate a module manifest for the docstubs
46
48
  utils.make_manifest(
@@ -65,26 +67,42 @@ def clean_destination(dst_path: Path):
65
67
 
66
68
  def get_rst_sources(rst_path: Path, pattern: str) -> List[Path]:
67
69
  """Get the list of rst files to process"""
68
- files = [f for f in rst_path.glob(pattern) if f.stem != "index" and "." not in f.stem]
70
+ files = [f for f in rst_path.glob(pattern) if f.stem != "index"] # and "." not in f.stem]
69
71
 
70
72
  # - excluded modules, ones that offer little advantage or cause much problems
71
73
  files = [f for f in files if f.name not in DOCSTUB_SKIP]
72
74
  return files
73
75
 
74
76
 
75
- def make_docstubs(dst_path: Path, v_tag: str, release: str, suffix: str, files: List[Path]):
77
+ def make_docstubs(
78
+ dst_path: Path, v_tag: str, release: str, suffix: str, files: List[Path], clean_rst: bool
79
+ ):
76
80
  """Create the docstubs"""
77
81
 
78
82
  for file in files:
79
83
  reader = RSTWriter(v_tag)
84
+ reader.clean_rst = clean_rst
80
85
  reader.source_release = release
81
86
  log.debug(f"Reading: {file}")
82
87
  reader.read_file(file)
83
88
  reader.parse()
84
- fname = (dst_path / file.name).with_suffix(suffix)
85
- reader.write_file(fname)
86
- if file.stem in U_MODULES:
87
- # create umod.py file and mod.py file
88
- fname = (dst_path / ("u" + file.name)).with_suffix(suffix)
89
- reader.write_file(fname)
89
+ # Destination = "module.__init__.pyi"
90
+ if "." in file.stem:
91
+ target = dst_path / f"{(file.stem).replace('.', '/')}{suffix}"
92
+ else:
93
+ target = dst_path / file.stem / f"__init__{suffix}"
94
+ # fname = (dst_path / file.name).with_suffix(suffix)
95
+ reader.write_file(target)
90
96
  del reader
97
+ for name in U_MODULES:
98
+ # create a file "umodule.pyi" for each module
99
+ # and add a line : from module import *
100
+ # this is to allow the use of the u modules in the code
101
+
102
+ # create the file
103
+ target = dst_path / f"u{name}.pyi"
104
+ with open(target, "w") as f:
105
+ f.write(f"# {name} module\n")
106
+ f.write("# Allow the use of micro-module notation \n\n")
107
+ f.write(f"from {name} import * # type: ignore\n")
108
+ f.flush()
@@ -23,7 +23,7 @@ def read_modules(path: Optional[Path] = None) -> Set[str]:
23
23
  read text files with modules per firmware.
24
24
  each contains the output of help("modules")
25
25
  - lines starting with # are comments.
26
- - split the other lines at whitespace seperator,
26
+ - split the other lines at whitespace separator,
27
27
  - and add each module to a set
28
28
  """
29
29
  path = Path(path or "./data")
@@ -73,7 +73,7 @@ def update_module_list():
73
73
  "upip_utarfile",
74
74
  "upysh",
75
75
  "uasyncio",
76
- "builtins",
76
+ # "builtins", # may be used for type hints
77
77
  "re",
78
78
  }
79
79
  log.info("Update the module list in createstubs.py")
stubber/utils/config.py CHANGED
@@ -1,20 +1,20 @@
1
1
  """stubber configuration"""
2
2
 
3
3
  from pathlib import Path
4
- from typing import List
4
+ from typing import List, Optional, Union
5
5
 
6
6
  from mpflash.logger import log
7
+ from mpflash.versions import get_preview_mp_version, get_stable_mp_version, micropython_versions
7
8
  from typedconfig.config import Config, key, section
8
9
  from typedconfig.source import EnvironmentConfigSource
9
10
 
10
- import mpflash.basicgit as git
11
- from mpflash.versions import V_PREVIEW
12
-
13
11
  from .typed_config_toml import TomlConfigSource
14
12
 
15
13
 
16
14
  @section("micropython-stubber")
17
15
  class StubberConfig(Config):
16
+ _config_path = None
17
+
18
18
  "stubber configuration class"
19
19
  # relative to stubs folder
20
20
  fallback_path: Path = key(
@@ -45,9 +45,13 @@ class StubberConfig(Config):
45
45
  stable_version: str = key(
46
46
  key_name="stable-version", cast=str, required=False, default="1.20.0"
47
47
  )
48
-
49
48
  "last published stable"
50
49
 
50
+ preview_version: str = key(
51
+ key_name="preview-version", cast=str, required=False, default="preview"
52
+ )
53
+ "current preview version"
54
+
51
55
  all_versions = key(
52
56
  key_name="all-versions",
53
57
  cast=list,
@@ -79,6 +83,15 @@ class StubberConfig(Config):
79
83
  "return the stubs path in the microypthon-stubs repo"
80
84
  return self.mpy_stubs_path / "publish" / "template"
81
85
 
86
+ @property
87
+ def config_path(self) -> Path:
88
+ "return the config path"
89
+ return self._config_path
90
+
91
+ @config_path.setter
92
+ def config_path(self, value: Path):
93
+ self._config_path = value
94
+
82
95
  def post_read_hook(self) -> dict:
83
96
  config_updates = {}
84
97
  # relative to stubs
@@ -94,32 +107,39 @@ class StubberConfig(Config):
94
107
  # read the versions from the git tags
95
108
  all_versions = []
96
109
  try:
97
- all_versions = git.get_tags("micropython/micropython", minver="v1.17")
110
+ all_versions = micropython_versions(minver="v1.17")
98
111
  except Exception as e:
99
112
  log.warning(f"Could not read micropython versions from git: {e}")
100
113
  all_versions = ["1.19", "1.19.1", "1.20.0", "1.21.0"]
101
114
  config_updates.update(all_versions=all_versions)
102
115
  config_updates.update(
103
- stable_version=[v for v in all_versions if not v.endswith(V_PREVIEW)][-1]
116
+ stable_version=get_stable_mp_version(),
117
+ preview_version=get_preview_mp_version(),
104
118
  ) # second last version - last version is the preview version
105
119
  return config_updates
106
120
 
107
121
 
108
- def readconfig(filename: str = "pyproject.toml", prefix: str = "tool.", must_exist: bool = True):
122
+ def readconfig(
123
+ location: Optional[Path] = None,
124
+ filename: str = "pyproject.toml",
125
+ prefix: str = "tool.",
126
+ must_exist: bool = True,
127
+ ):
109
128
  "read the configuration from the pyproject.toml file"
110
129
  # locate the pyproject.toml file
111
- path = Path.cwd()
130
+ config_path = location or Path.cwd()
112
131
  use_toml = True
113
- while not (path / filename).exists():
114
- path = path.parent
115
- if path == path.parent:
132
+ while not (config_path / filename).exists():
133
+ config_path = config_path.parent
134
+ if config_path == config_path.parent:
116
135
  log.trace(f"Could not find config file: {filename}")
117
136
  use_toml = False
118
137
  break
119
138
 
120
- filename = str(path / filename)
139
+ filename = str(config_path / filename)
121
140
 
122
141
  config = StubberConfig()
142
+ config.config_path = config_path.absolute()
123
143
  # add provider sources to the config
124
144
  config.add_source(EnvironmentConfigSource())
125
145
  if use_toml: