micropython-stubber 1.23.2__py3-none-any.whl → 1.24.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.
- {micropython_stubber-1.23.2.dist-info → micropython_stubber-1.24.0.dist-info}/METADATA +30 -12
- {micropython_stubber-1.23.2.dist-info → micropython_stubber-1.24.0.dist-info}/RECORD +69 -66
- {micropython_stubber-1.23.2.dist-info → micropython_stubber-1.24.0.dist-info}/WHEEL +1 -1
- mpflash/README.md +2 -2
- mpflash/mpflash/basicgit.py +49 -9
- mpflash/mpflash/common.py +23 -16
- mpflash/mpflash/downloaded.py +10 -2
- mpflash/mpflash/mpboard_id/__init__.py +9 -4
- mpflash/mpflash/mpboard_id/add_boards.py +25 -14
- mpflash/mpflash/mpboard_id/board.py +2 -2
- mpflash/mpflash/mpboard_id/board_id.py +10 -6
- mpflash/mpflash/mpboard_id/board_info.zip +0 -0
- mpflash/mpflash/mpboard_id/store.py +8 -3
- mpflash/mpflash/mpremoteboard/__init__.py +13 -8
- mpflash/mpflash/mpremoteboard/mpy_fw_info.py +27 -16
- mpflash/mpflash/vendor/board_database.py +185 -0
- mpflash/mpflash/vendor/readme.md +10 -1
- mpflash/mpflash/versions.py +28 -40
- mpflash/poetry.lock +1605 -601
- mpflash/pyproject.toml +4 -3
- stubber/__init__.py +1 -1
- stubber/board/createstubs.py +51 -27
- stubber/board/createstubs_db.py +36 -28
- stubber/board/createstubs_db_min.py +171 -165
- stubber/board/createstubs_db_mpy.mpy +0 -0
- stubber/board/createstubs_mem.py +36 -28
- stubber/board/createstubs_mem_min.py +184 -178
- stubber/board/createstubs_mem_mpy.mpy +0 -0
- stubber/board/createstubs_min.py +102 -94
- stubber/board/createstubs_mpy.mpy +0 -0
- stubber/board/modulelist.txt +16 -0
- stubber/codemod/enrich.py +297 -88
- stubber/codemod/merge_docstub.py +250 -65
- stubber/codemod/test_enrich.py +87 -0
- stubber/codemod/visitors/typevars.py +200 -0
- stubber/commands/build_cmd.py +16 -3
- stubber/commands/clone_cmd.py +3 -3
- stubber/commands/config_cmd.py +4 -2
- stubber/commands/enrich_folder_cmd.py +33 -21
- stubber/commands/get_core_cmd.py +1 -2
- stubber/commands/get_docstubs_cmd.py +60 -6
- stubber/commands/get_frozen_cmd.py +15 -12
- stubber/commands/get_mcu_cmd.py +3 -3
- stubber/commands/merge_cmd.py +1 -2
- stubber/commands/publish_cmd.py +19 -4
- stubber/commands/stub_cmd.py +3 -3
- stubber/commands/switch_cmd.py +3 -5
- stubber/commands/variants_cmd.py +3 -3
- stubber/cst_transformer.py +52 -17
- stubber/freeze/common.py +27 -11
- stubber/freeze/freeze_manifest_2.py +8 -1
- stubber/freeze/get_frozen.py +4 -1
- stubber/merge_config.py +111 -0
- stubber/minify.py +1 -2
- stubber/publish/database.py +51 -10
- stubber/publish/merge_docstubs.py +33 -16
- stubber/publish/package.py +32 -18
- stubber/publish/publish.py +8 -8
- stubber/publish/stubpackage.py +110 -47
- stubber/rst/lookup.py +205 -43
- stubber/rst/reader.py +106 -59
- stubber/rst/rst_utils.py +24 -11
- stubber/stubber.py +1 -1
- stubber/stubs_from_docs.py +31 -13
- stubber/update_module_list.py +2 -2
- stubber/utils/config.py +33 -13
- stubber/utils/post.py +9 -6
- stubber/publish/missing_class_methods.py +0 -51
- {micropython_stubber-1.23.2.dist-info → micropython_stubber-1.24.0.dist-info}/LICENSE +0 -0
- {micropython_stubber-1.23.2.dist-info → micropython_stubber-1.24.0.dist-info}/entry_points.txt +0 -0
stubber/codemod/merge_docstub.py
CHANGED
@@ -7,7 +7,7 @@
|
|
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
|
@@ -17,16 +17,20 @@ from mpflash.logger import log
|
|
17
17
|
|
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.typevars import AddTypeVarsVisitor, GatherTypeVarsVisitor
|
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
|
-
|
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
|
-
|
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.typevars = []
|
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
|
-
|
110
|
+
typevar_collector = GatherTypeVarsVisitor(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
|
120
|
+
stub_tree.visit(typevar_collector)
|
121
|
+
self.typevars = typevar_collector.all_typealias_or_vars
|
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
|
-
|
120
|
-
|
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=
|
124
|
-
obj=
|
125
|
-
asname=
|
126
|
-
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
|
134
|
-
if isinstance(
|
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(
|
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(
|
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.typevars:
|
181
|
+
for tv in self.typevars:
|
182
|
+
AddTypeVarsVisitor.add_typevar(self.context, tv) # type: ignore
|
183
|
+
|
184
|
+
atv = AddTypeVarsVisitor(self.context)
|
185
|
+
updated_node = atv.transform_module(updated_node)
|
186
|
+
|
187
|
+
# --------------------------------------------------------------------
|
147
188
|
# update the docstring.
|
148
|
-
if MODULE_KEY
|
149
|
-
return updated_node
|
189
|
+
if MODULE_KEY in self.annotations:
|
150
190
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
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
|
-
|
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
|
-
|
216
|
-
|
217
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|
-
|
267
|
-
|
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
|
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
|