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
@@ -7,26 +7,30 @@
7
7
  #
8
8
  import argparse
9
9
  from pathlib import Path
10
- from typing import Dict, List, Optional, Tuple, Union
10
+ from typing import Dict, List, Optional, Tuple, TypeVar, Union, cast
11
11
 
12
12
  import libcst as cst
13
13
  from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand
14
14
  from libcst.codemod.visitors import AddImportsVisitor, GatherImportsVisitor, ImportItem
15
15
  from libcst.helpers.module import insert_header_comments
16
- from mpflash.logger import log
17
16
 
17
+ from mpflash.logger import log
18
18
  from stubber.cst_transformer import (
19
19
  MODULE_KEY,
20
+ AnnoValue,
20
21
  StubTypingCollector,
21
- TypeInfo,
22
22
  update_def_docstr,
23
23
  update_module_docstr,
24
24
  )
25
25
 
26
+ from .visitors.type_helpers import AddTypeHelpers, GatherTypeHelpers
27
+
28
+ Mod_Class_T = TypeVar("Mod_Class_T", cst.Module, cst.ClassDef)
29
+ """TypeVar for Module or ClassDef that both support overloads"""
26
30
  ##########################################################################################
27
31
  # # log = logging.getLogger(__name__)
28
32
  #########################################################################################
29
- empty_module = cst.parse_module("")
33
+ empty_module = cst.parse_module("") # Debugging aid : empty_module.code_for_node(node)
30
34
 
31
35
 
32
36
  class MergeCommand(VisitorBasedCodemodCommand):
@@ -50,7 +54,6 @@ class MergeCommand(VisitorBasedCodemodCommand):
50
54
  """Add command-line args that a user can specify for running this codemod."""
51
55
 
52
56
  arg_parser.add_argument(
53
- # "-sf",
54
57
  "--stubfile",
55
58
  dest="docstub_file",
56
59
  metavar="PATH",
@@ -59,7 +62,18 @@ class MergeCommand(VisitorBasedCodemodCommand):
59
62
  required=True,
60
63
  )
61
64
 
62
- def __init__(self, context: CodemodContext, docstub_file: Union[Path, str]) -> None:
65
+ arg_parser.add_argument(
66
+ "--params-only",
67
+ dest="params_only",
68
+ default=False,
69
+ )
70
+
71
+ def __init__(
72
+ self,
73
+ context: CodemodContext,
74
+ docstub_file: Union[Path, str],
75
+ params_only: bool = False,
76
+ ) -> None:
63
77
  """initialize the base class with context, and save our args."""
64
78
  super().__init__(context)
65
79
  self.replace_functiondef_with_classdef = True
@@ -72,12 +86,16 @@ class MergeCommand(VisitorBasedCodemodCommand):
72
86
  # store the annotations
73
87
  self.annotations: Dict[
74
88
  Tuple[str, ...], # key: tuple of canonical class/function name
75
- Union[TypeInfo, str], # value: TypeInfo
89
+ AnnoValue,
90
+ # Union[TypeInfo, str, List[TypeInfo]], # value: TypeInfo
76
91
  ] = {}
77
92
  self.comments: List[str] = []
78
93
 
94
+ self.params_only = params_only
95
+
79
96
  self.stub_imports: Dict[str, ImportItem] = {}
80
97
  self.all_imports: List[Union[cst.Import, cst.ImportFrom]] = []
98
+ self.type_helpers = []
81
99
  # parse the doc-stub file
82
100
  if self.docstub_source:
83
101
  try:
@@ -89,14 +107,18 @@ class MergeCommand(VisitorBasedCodemodCommand):
89
107
  # create the collectors
90
108
  typing_collector = StubTypingCollector()
91
109
  import_collector = GatherImportsVisitor(context)
92
- # visit the doc-stub file with all collectors
110
+ typevar_collector = GatherTypeHelpers(context)
111
+ # visit the source / doc-stub file with all collectors
93
112
  stub_tree.visit(typing_collector)
94
113
  self.annotations = typing_collector.annotations
95
114
  self.comments = typing_collector.comments
96
- # Store the imports that were added to the stub file
115
+ # Store the imports that were added to the source / doc-stub file
97
116
  stub_tree.visit(import_collector)
98
117
  self.stub_imports = import_collector.symbol_mapping
99
118
  self.all_imports = import_collector.all_imports
119
+ # Get typevars, type aliasses and ParamSpecs
120
+ stub_tree.visit(typevar_collector)
121
+ self.type_helpers = typevar_collector.all_typehelpers
100
122
 
101
123
  # ------------------------------------------------------------------------
102
124
 
@@ -114,29 +136,39 @@ class MergeCommand(VisitorBasedCodemodCommand):
114
136
  :return: The updated module node.
115
137
  """
116
138
  # --------------------------------------------------------------------
117
- # add any needed imports from the doc-stub
139
+ # add any needed imports from the source doc-stub
118
140
  for k in self.stub_imports.keys():
119
- imp = self.stub_imports[k]
120
- log.trace(f"add: import {k} = {imp}")
141
+ import_item = self.stub_imports[k]
142
+ if import_item.module_name == self.context.full_module_name:
143
+ # this is an import from the same module we should NOT add it
144
+ continue
145
+ if import_item.module_name.split(".")[
146
+ 0
147
+ ] == self.context.full_module_name and not self.context.filename.endswith(
148
+ "__init__.pyi"
149
+ ):
150
+ # this is an import from a module child module we should NOT add it
151
+ continue
152
+ log.trace(f"add: import {k} = {import_item}")
121
153
  AddImportsVisitor.add_needed_import(
122
154
  self.context,
123
- module=imp.module_name,
124
- obj=imp.obj_name,
125
- asname=imp.alias,
126
- relative=imp.relative,
155
+ module=import_item.module_name,
156
+ obj=import_item.obj_name,
157
+ asname=import_item.alias,
158
+ relative=import_item.relative,
127
159
  )
128
160
 
129
161
  # add `from module import *` from the doc-stub
130
162
  # FIXME: this cases a problem if there is also a 'from module import foobar' in the firmware stub
131
163
  # also all comments get removed from the import
132
164
  if self.all_imports:
133
- for imp in self.all_imports:
134
- if isinstance(imp, cst.ImportFrom):
165
+ for import_item in self.all_imports:
166
+ if isinstance(import_item, cst.ImportFrom):
135
167
  # perhaps this is an import from *
136
- if isinstance(imp.names, cst.ImportStar):
168
+ if isinstance(import_item.names, cst.ImportStar):
137
169
  # bit of a hack to get the full module name
138
170
  empty_mod = cst.parse_module("")
139
- full_module_name = empty_mod.code_for_node(imp.module) # type: ignore
171
+ full_module_name = empty_mod.code_for_node(import_item.module) # type: ignore
140
172
  log.trace(f"add: from {full_module_name} import *")
141
173
  AddImportsVisitor.add_needed_import(
142
174
  self.context,
@@ -144,37 +176,132 @@ class MergeCommand(VisitorBasedCodemodCommand):
144
176
  obj="*",
145
177
  )
146
178
  # --------------------------------------------------------------------
179
+ # Add any typevars to the module
180
+ if self.type_helpers:
181
+ for tv in self.type_helpers:
182
+ AddTypeHelpers.add_typevar(self.context, tv) # type: ignore
183
+
184
+ atv = AddTypeHelpers(self.context)
185
+ updated_node = atv.transform_module(updated_node)
186
+
187
+ # --------------------------------------------------------------------
147
188
  # update the docstring.
148
- if MODULE_KEY not in self.annotations:
149
- return updated_node
189
+ if MODULE_KEY in self.annotations:
150
190
 
151
- # update/replace module docstrings
152
- # todo: or should we add / merge the docstrings?
153
- docstub_docstr = self.annotations[MODULE_KEY]
154
- assert isinstance(docstub_docstr, str)
155
- src_docstr = original_node.get_docstring() or ""
156
- if src_docstr or docstub_docstr:
157
- if docstub_docstr.strip() != src_docstr.strip():
158
- if src_docstr:
159
- new_docstr = f'"""\n' + docstub_docstr + "\n\n---\n" + src_docstr + '\n"""'
160
- else:
161
- new_docstr = f'"""\n' + docstub_docstr + '\n"""'
191
+ # update/replace module docstrings
192
+ # todo: or should we add / merge the docstrings?
193
+ docstub_docstr = self.annotations[MODULE_KEY].docstring
194
+ assert isinstance(docstub_docstr, str)
195
+ src_docstr = original_node.get_docstring() or ""
196
+ if src_docstr or docstub_docstr:
197
+ if not self.params_only and (docstub_docstr.strip() != src_docstr.strip()):
198
+ if src_docstr:
199
+ log.trace(f"Append module docstrings. (new --- old) ")
200
+ new_docstr = '"""\n' + docstub_docstr + "\n\n---\n" + src_docstr + '\n"""'
201
+ else:
202
+ new_docstr = '"""\n' + docstub_docstr + '\n"""'
162
203
 
163
- docstr_node = cst.SimpleStatementLine(
164
- body=[
165
- cst.Expr(
166
- value=cst.SimpleString(
167
- value=new_docstr,
204
+ docstr_node = cst.SimpleStatementLine(
205
+ body=[
206
+ cst.Expr(
207
+ value=cst.SimpleString(
208
+ value=new_docstr,
209
+ )
168
210
  )
169
- )
170
- ]
171
- )
172
- updated_node = update_module_docstr(updated_node, docstr_node)
211
+ ]
212
+ )
213
+ updated_node = update_module_docstr(updated_node, docstr_node)
173
214
  # --------------------------------------------------------------------
174
215
  # update the comments
175
216
  updated_node = insert_header_comments(updated_node, self.comments)
176
217
 
218
+ # --------------------------------------------------------------------
219
+ # make sure that any @overloads that not yet applied are also added to the firmware stub
220
+ updated_node = self.add_missed_overloads(updated_node, stack_id=())
221
+
177
222
  return updated_node
223
+
224
+ def add_missed_overloads(self, updated_node: Mod_Class_T, stack_id: tuple) -> Mod_Class_T:
225
+ """
226
+ Add any missing overloads to the updated_node
227
+
228
+ """
229
+ missing_overloads = []
230
+ scope_keys = [k for k in self.annotations.keys() if k[: (len(stack_id))] == stack_id]
231
+
232
+ for key in scope_keys:
233
+ for overload in self.annotations[key].overloads:
234
+ missing_overloads.append((overload.def_node, key))
235
+ self.annotations[key].overloads = [] # remove for list, assume works
236
+
237
+ if missing_overloads:
238
+ if isinstance(updated_node, cst.Module):
239
+ module_level = True
240
+ updated_body = list(updated_node.body) # type: ignore
241
+ elif isinstance(updated_node, cst.ClassDef):
242
+ module_level = False
243
+ updated_body = list(updated_node.body.body) # type: ignore
244
+ else:
245
+ raise ValueError(f"Unsupported node type: {updated_node}")
246
+ # insert each overload just after a function with the same name
247
+
248
+ new_classes = []
249
+ for overload, key in missing_overloads:
250
+ matched = False
251
+ matched, i = self.locate_function_by_name(overload, updated_body)
252
+ if matched:
253
+ log.trace(f"Add @overload for {overload.name.value}")
254
+ if self.params_only:
255
+ docstring_node = self.annotations[key].docstring_node or ""
256
+ # Use the new overload - but with the existing docstring
257
+ overload = update_def_docstr(overload, docstring_node)
258
+ updated_body.insert(i + 1, overload)
259
+ else:
260
+ # add to the end of the module or class
261
+ if module_level and len(key) > 1:
262
+ # this is a class level overload, for which no class was found
263
+ class_name = key[0]
264
+ if class_name not in new_classes:
265
+ new_classes.append(class_name)
266
+ # create a class for it, and then add all the overload methods to that class
267
+ log.trace(
268
+ f"Add New class @overload for {overload.name.value} at the end of the module"
269
+ )
270
+ # create a list of all overloads for this class
271
+ class_overloads = [
272
+ overload for overload, k in missing_overloads if k[0] == class_name
273
+ ]
274
+ class_def = cst.ClassDef(
275
+ name=cst.Name(value=class_name),
276
+ body=cst.IndentedBlock(body=class_overloads),
277
+ )
278
+ updated_body.append(class_def)
279
+ else:
280
+ # already processed this class method
281
+ pass
282
+ else:
283
+ log.trace(
284
+ f"Add @overload for {overload.name.value} at the end of the class"
285
+ )
286
+ updated_body.append(overload)
287
+
288
+ if isinstance(updated_node, cst.Module):
289
+ updated_node = updated_node.with_changes(body=tuple(updated_body))
290
+ elif isinstance(updated_node, cst.ClassDef):
291
+ b1 = updated_node.body.with_changes(body=tuple(updated_body))
292
+ updated_node = updated_node.with_changes(body=b1)
293
+
294
+ # cst.IndentedBlock(body=tuple(updated_body))) # type: ignore
295
+ return updated_node
296
+
297
+ def locate_function_by_name(self, overload, updated_body):
298
+ """locate the (last) function by name"""
299
+ matched = False
300
+ for i, node in reversed(list(enumerate(updated_body))):
301
+ if isinstance(node, cst.FunctionDef) and node.name.value == overload.name.value:
302
+ matched = True
303
+ break
304
+ return matched, i
178
305
  # --------------------------------------------------------------------
179
306
 
180
307
  # ------------------------------------------------------------
@@ -192,12 +319,11 @@ class MergeCommand(VisitorBasedCodemodCommand):
192
319
  # no changes to the class
193
320
  return updated_node
194
321
  # update the firmware_stub from the doc_stub information
195
- doc_stub = self.annotations[stack_id]
196
- assert not isinstance(doc_stub, str)
322
+ doc_stub = self.annotations[stack_id].type_info
197
323
  # first update the docstring
198
324
  updated_node = update_def_docstr(updated_node, doc_stub.docstr_node)
199
325
  # Sometimes the MCU stubs and the doc stubs have different types : FunctionDef / ClassDef
200
- # we need to be carefull not to copy over all the annotations if the types are different
326
+ # we need to be careful not to copy over all the annotations if the types are different
201
327
  if doc_stub.def_type == "classdef":
202
328
  # Same type, we can copy over all the annotations
203
329
  # combine the decorators from the doc-stub and the firmware stub
@@ -207,14 +333,16 @@ class MergeCommand(VisitorBasedCodemodCommand):
207
333
  if updated_node.decorators:
208
334
  new_decorators.extend(updated_node.decorators)
209
335
 
210
- return updated_node.with_changes(
336
+ updated_node = updated_node.with_changes(
211
337
  decorators=new_decorators,
212
338
  bases=doc_stub.def_node.bases, # type: ignore
213
339
  )
214
- else:
215
- # Different type: ClassDef != FuncDef ,
216
- # for now just return the updated node
217
- return updated_node
340
+ # else:
341
+ # Different type: ClassDef != FuncDef ,
342
+ # for now just return the updated node
343
+ # Add any missing methods overloads
344
+ updated_node = self.add_missed_overloads(updated_node, stack_id)
345
+ return updated_node
218
346
 
219
347
  # ------------------------------------------------------------------------
220
348
  def visit_FunctionDef(self, node: cst.FunctionDef) -> Optional[bool]:
@@ -228,26 +356,73 @@ class MergeCommand(VisitorBasedCodemodCommand):
228
356
  stack_id = tuple(self.stack)
229
357
  self.stack.pop()
230
358
  if stack_id not in self.annotations:
231
- # no changes to the function
359
+ # no changes to the function in docstub
360
+ return updated_node
361
+ if updated_node.decorators and any(
362
+ dec.decorator.value == "overload" for dec in updated_node.decorators # type: ignore
363
+ ):
364
+ # do not overwrite existing @overload functions
365
+ # ASSUME: they are OK as they are
232
366
  return updated_node
367
+
233
368
  # update the firmware_stub from the doc_stub information
234
- doc_stub = self.annotations[stack_id]
235
- assert not isinstance(doc_stub, str)
369
+ doc_stub = self.annotations[stack_id].type_info
370
+ # Check if it is an @overload decorator
371
+ add_overload = any(dec.decorator.value == "overload" for dec in doc_stub.decorators) and len(self.annotations[stack_id].overloads) > 1 # type: ignore
372
+
373
+ # If there are overloads in the documentation , lets use the first one
374
+ if add_overload:
375
+ log.debug(f"Change to @overload :{updated_node.name.value}")
376
+ # Use the new overload - but with the existing docstring
377
+ doc_stub = self.annotations[stack_id].overloads.pop(0)
378
+ assert doc_stub.def_node
379
+
380
+ if not self.params_only:
381
+ # we have copied over the entire function definition, no further processing should be done on this node
382
+ doc_stub.def_node = cast(cst.FunctionDef, doc_stub.def_node)
383
+ updated_node = doc_stub.def_node
384
+
385
+ else:
386
+ # Save (first) existing docstring if any
387
+ existing_ds = None
388
+ if updated_node.get_docstring():
389
+ # if there is one , then get it including the layout
390
+ existing_ds = original_node.body.body[0]
391
+ assert isinstance(existing_ds, cst.SimpleStatementLine)
392
+
393
+ self.annotations[stack_id].docstring_node = existing_ds
394
+ updated_node = update_def_docstr(doc_stub.def_node, existing_ds)
395
+ return updated_node
396
+
397
+ # assert isinstance(doc_stub, TypeInfo)
398
+ # assert doc_stub
236
399
  # first update the docstring
237
- updated_node = update_def_docstr(updated_node, doc_stub.docstr_node, doc_stub.def_node)
400
+ no_docstring = updated_node.get_docstring() is None
401
+ if (not self.params_only) or no_docstring:
402
+ # DO Not overwrite existing docstring
403
+ updated_node = update_def_docstr(updated_node, doc_stub.docstr_node, doc_stub.def_node)
404
+
238
405
  # Sometimes the MCU stubs and the doc stubs have different types : FunctionDef / ClassDef
239
- # we need to be carefull not to copy over all the annotations if the types are different
406
+ # we need to be careful not to copy over all the annotations if the types are different
240
407
  if doc_stub.def_type == "funcdef":
241
408
  # Same type, we can copy over the annotations
242
409
  # params that should not be overwritten by the doc-stub ?
243
- params_txt = empty_module.code_for_node(original_node.params)
244
- overwrite_params = params_txt in [
245
- "",
246
- "...",
247
- "*args, **kwargs",
248
- "self",
249
- "self, *args, **kwargs",
250
- ]
410
+ if self.params_only:
411
+ # we are copying rich type definitions, just assume they are better than what is currently
412
+ # in the destination stub
413
+ overwrite_params = True
414
+ else:
415
+ params_txt = empty_module.code_for_node(original_node.params)
416
+ overwrite_params = params_txt in [
417
+ "",
418
+ "...",
419
+ "*args, **kwargs",
420
+ "self",
421
+ "self, *args, **kwargs",
422
+ "cls",
423
+ "cls, *args, **kwargs",
424
+ ]
425
+
251
426
  # return that should not be overwritten by the doc-stub ?
252
427
  overwrite_return = True
253
428
  if original_node.returns:
@@ -263,8 +438,18 @@ class MergeCommand(VisitorBasedCodemodCommand):
263
438
  new_decorators = []
264
439
  if doc_stub.decorators:
265
440
  new_decorators.extend(doc_stub.decorators)
266
- if updated_node.decorators:
267
- new_decorators.extend(updated_node.decorators)
441
+
442
+ for decorator in updated_node.decorators:
443
+ if decorator.decorator.value not in [n.decorator.value for n in new_decorators]: # type: ignore
444
+ new_decorators.append(decorator)
445
+
446
+ # if there is both a static and a class method, we remove the class decorator to avoid inconsistencies
447
+ if any(dec.decorator.value == "staticmethod" for dec in doc_stub.decorators) and any( # type: ignore
448
+ dec.decorator.value == "staticmethod" for dec in doc_stub.decorators # type: ignore
449
+ ):
450
+ new_decorators = [
451
+ dec for dec in new_decorators if dec.decorator.value != "classmethod"
452
+ ]
268
453
 
269
454
  return updated_node.with_changes(
270
455
  decorators=new_decorators,
@@ -275,7 +460,7 @@ class MergeCommand(VisitorBasedCodemodCommand):
275
460
  elif doc_stub.def_type == "classdef":
276
461
  # Different type: ClassDef != FuncDef ,
277
462
  if doc_stub.def_node and self.replace_functiondef_with_classdef:
278
- # replace the functiondef with the classdef from the stub file
463
+ # replace the functionDef with the classdef from the stub file
279
464
  return doc_stub.def_node
280
465
  # for now just return the updated node
281
466
  return updated_node
@@ -0,0 +1,87 @@
1
+ from typing import List
2
+ import pytest
3
+ from pathlib import Path
4
+ # from stubber.codemod.enrich import merge_source_candidates
5
+
6
+
7
+ @pytest.fixture
8
+ def docstub_path(tmp_path):
9
+ # Create a temporary directory for docstub files
10
+ docstub_path = tmp_path / "docstubs"
11
+ docstub_path.mkdir(exist_ok=True, parents=True)
12
+
13
+ (docstub_path / "one.pyi").touch()
14
+
15
+ # Create some docstub files
16
+ (docstub_path / "package.py").touch()
17
+ (docstub_path / "package.pyi").touch()
18
+ (docstub_path / "upackage.py").touch()
19
+ (docstub_path / "upackage.pyi").touch()
20
+ # for ufoo to foo
21
+ (docstub_path / "usys.pyi").touch()
22
+ (docstub_path / "sys.pyi").touch()
23
+ # from _foo to foo
24
+ (docstub_path / "rp2.pyi").touch()
25
+ (docstub_path / "_rp2.pyi").touch()
26
+
27
+ # dist package format
28
+ (docstub_path / "foo").mkdir(exist_ok=True, parents=True)
29
+ (docstub_path / "foo" / "__init__.pyi").touch()
30
+
31
+ # dist package format
32
+ (docstub_path / "bar").mkdir(exist_ok=True, parents=True)
33
+ (docstub_path / "bar" / "__init__.pyi").touch()
34
+ (docstub_path / "bar" / "barclass.pyi").touch()
35
+
36
+ # dist package format
37
+ (docstub_path / "machine").mkdir(exist_ok=True, parents=True)
38
+ (docstub_path / "machine" / "__init__.pyi").touch()
39
+ (docstub_path / "machine" / "Pin.pyi").touch()
40
+ (docstub_path / "machine" / "Signal.pyi").touch()
41
+ (docstub_path / "machine" / "ADC.pyi").touch()
42
+ return docstub_path
43
+
44
+
45
+ # @pytest.mark.parametrize(
46
+ # "package_name, expected_candidates",
47
+ # [
48
+ # (
49
+ # "package",
50
+ # [
51
+ # "package.py",
52
+ # "package.pyi",
53
+ # "upackage.py",
54
+ # "upackage.pyi",
55
+ # ],
56
+ # ),
57
+ # ("usys", ["usys.pyi", "sys.pyi"]),
58
+ # ("_rp2", ["rp2.pyi", "_rp2.pyi"]),
59
+ # ("rp2", ["rp2.pyi"]),
60
+ # ("nonexistent", []),
61
+ # ("foo", ["foo/__init__.pyi"]),
62
+ # ("ufoo", ["foo/__init__.pyi"]),
63
+ # ("bar", ["bar/__init__.pyi", "bar/barclass.pyi"]),
64
+ # (
65
+ # "machine",
66
+ # [
67
+ # "machine/__init__.pyi",
68
+ # "machine/Pin.pyi",
69
+ # "machine/Signal.pyi",
70
+ # "machine/ADC.pyi",
71
+ # ],
72
+ # ),
73
+ # ("machine.Pin", ["machine/Pin.pyi"]),
74
+ # ],
75
+ # )
76
+ # def test_merge_source_candidates(
77
+ # package_name: str,
78
+ # expected_candidates: List[str],
79
+ # docstub_path: Path,
80
+ # ):
81
+ # # Test with package name "package"
82
+ # candidates = merge_source_candidates(package_name, docstub_path)
83
+ # assert len(candidates) == len(expected_candidates)
84
+ # for e in expected_candidates:
85
+ # assert docstub_path / e in candidates
86
+
87
+ # # todo : test ordering