Skip to content
This repository was archived by the owner on Jan 19, 2025. It is now read-only.

Commit 7bbafd1

Browse files
authored
feat: new additional strict differ (#1190)
Closes #1230. ### Summary of Changes - new additional differ that filters mappings of api elements out whose parents are not mapped together. - `__repr__` method for api elements with only the id of the element. ### Testing Instructions run the `migrate` command before and after applying these improvements
1 parent 2975efb commit 7bbafd1

File tree

13 files changed

+1055
-320
lines changed

13 files changed

+1055
-320
lines changed

package-parser/package_parser/cli/_run_migrate.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
from pathlib import Path
33

44
from package_parser.processing.migration import Migration
5-
from package_parser.processing.migration.model import APIMapping, SimpleDiffer
5+
from package_parser.processing.migration.model import (
6+
APIMapping,
7+
SimpleDiffer,
8+
StrictDiffer,
9+
)
610

711
from ._read_and_write_file import (
812
_read_annotations_file,
@@ -23,8 +27,12 @@ def _run_migrate_command(
2327
differ = SimpleDiffer()
2428
api_mapping = APIMapping(apiv1, apiv2, differ)
2529
mappings = api_mapping.map_api()
26-
migration = Migration(annotationsv1, mappings)
30+
enhanced_api_mapping = APIMapping(apiv1, apiv2, StrictDiffer(mappings, differ))
31+
enhanced_mappings = enhanced_api_mapping.map_api()
32+
33+
migration = Migration(annotationsv1, enhanced_mappings)
2734
migration.migrate_annotations()
35+
migration.print(apiv1, apiv2)
2836
migrated_annotations_file = Path(
2937
os.path.join(out_dir_path, "migrated_annotationsv" + apiv2.version + ".json")
3038
)

package-parser/package_parser/processing/api/model/_api.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def from_json(json: Any) -> Class:
209209
),
210210
json.get("code", ""),
211211
[
212-
Attribute.from_json(instance_attribute)
212+
Attribute.from_json(instance_attribute, json["id"])
213213
for instance_attribute in json.get("instance_attributes", [])
214214
],
215215
)
@@ -267,19 +267,38 @@ def to_json(self) -> Any:
267267
],
268268
}
269269

270+
def __repr__(self) -> str:
271+
return "Class(id=" + self.id + ")"
272+
270273

271274
@dataclass
272275
class Attribute:
273276
name: str
274277
types: Optional[AbstractType]
278+
class_id: Optional[str] = None
275279

276280
def to_json(self) -> dict[str, Any]:
277281
types_json = self.types.to_json() if self.types is not None else None
278282
return {"name": self.name, "types": types_json}
279283

280284
@staticmethod
281-
def from_json(json: Any) -> Attribute:
282-
return Attribute(json["name"], AbstractType.from_json(json.get("types", {})))
285+
def from_json(json: Any, class_id: Optional[str] = None) -> Attribute:
286+
return Attribute(
287+
json["name"], AbstractType.from_json(json.get("types", {})), class_id
288+
)
289+
290+
def __repr__(self) -> str:
291+
type_str = (
292+
" , type=" + str(self.types.to_json()) if self.types is not None else "None"
293+
)
294+
return (
295+
"Attribute(class_id="
296+
+ str(self.class_id)
297+
+ "/"
298+
+ self.name
299+
+ type_str
300+
+ ")"
301+
)
283302

284303

285304
@dataclass
@@ -333,22 +352,32 @@ def to_json(self) -> Any:
333352
"code": self.code,
334353
}
335354

355+
def __repr__(self) -> str:
356+
return "Function(id=" + self.id + ")"
357+
336358

337359
@dataclass
338360
class Result:
339361
name: str
340362
docstring: ResultDocstring
363+
function_id: Optional[str] = None
341364

342365
@staticmethod
343-
def from_json(json: Any) -> Result:
366+
def from_json(json: Any, function_id: Optional[str] = None) -> Result:
344367
return Result(
345368
json["name"],
346369
ResultDocstring.from_json(json.get("docstring", {})),
370+
function_id,
347371
)
348372

349373
def to_json(self) -> Any:
350374
return {"name": self.name, "docstring": self.docstring.to_json()}
351375

376+
def __repr__(self) -> str:
377+
return (
378+
"Result(function_id=" + str(self.function_id) + ", name=" + self.name + ")"
379+
)
380+
352381

353382
@dataclass
354383
class ResultDocstring:

package-parser/package_parser/processing/api/model/_documentation.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class ClassDocumentation:
1313
def from_dict(d: dict) -> ClassDocumentation:
1414
return ClassDocumentation(**d)
1515

16-
def to_dict(self):
16+
def to_dict(self) -> dict:
1717
return dataclasses.asdict(self)
1818

1919

@@ -26,7 +26,7 @@ class FunctionDocumentation:
2626
def from_dict(d: dict) -> FunctionDocumentation:
2727
return FunctionDocumentation(**d)
2828

29-
def to_dict(self):
29+
def to_dict(self) -> dict:
3030
return dataclasses.asdict(self)
3131

3232

@@ -40,5 +40,5 @@ class ParameterDocumentation:
4040
def from_dict(d: dict) -> ParameterDocumentation:
4141
return ParameterDocumentation(**d)
4242

43-
def to_dict(self):
43+
def to_dict(self) -> dict:
4444
return dataclasses.asdict(self)

package-parser/package_parser/processing/api/model/_parameters.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
class Parameter:
1111
@staticmethod
12-
def from_json(json: Any):
12+
def from_json(json: Any) -> Parameter:
1313
return Parameter(
1414
json["id"],
1515
json["name"],
@@ -57,6 +57,9 @@ def to_json(self) -> Any:
5757
"type": self.type.to_json() if self.type is not None else {},
5858
}
5959

60+
def __repr__(self) -> str:
61+
return "Parameter(id=" + self.id + ")"
62+
6063

6164
class ParameterAssignment(Enum):
6265
"""

package-parser/package_parser/processing/migration/_migrate.py

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
from dataclasses import dataclass, field
2-
from typing import Optional, Tuple
2+
from typing import Optional, Tuple, Union
33

44
from package_parser.processing.annotations import are_semantic_equal
55
from package_parser.processing.annotations.model import (
66
AbstractAnnotation,
77
AnnotationStore,
88
EnumReviewResult,
99
)
10-
from package_parser.processing.api.model import Attribute, Result
10+
from package_parser.processing.api.model import (
11+
API,
12+
Attribute,
13+
Class,
14+
Function,
15+
Parameter,
16+
Result,
17+
)
1118
from package_parser.processing.migration.annotations import (
1219
migrate_boundary_annotation,
1320
migrate_called_after_annotation,
@@ -158,6 +165,142 @@ def add_annotations_based_on_similarity(
158165
else:
159166
self.unsure_migrated_annotation_store.add_annotation(annotation)
160167

168+
def _get_mappings_for_table(self) -> list[str]:
169+
table_rows: list[str] = []
170+
for mapping in self.mappings:
171+
172+
def print_api_element(
173+
api_element: Union[Attribute, Class, Function, Parameter, Result]
174+
) -> str:
175+
if isinstance(api_element, Result):
176+
return api_element.name
177+
if isinstance(api_element, Attribute):
178+
return str(api_element.class_id) + "/" + api_element.name
179+
return "/".join(api_element.id.split("/")[1:])
180+
181+
apiv1_elements = ", ".join(
182+
[
183+
print_api_element(api_element)
184+
for api_element in mapping.get_apiv1_elements()
185+
]
186+
)
187+
apiv2_elements = ", ".join(
188+
[
189+
print_api_element(api_element)
190+
for api_element in mapping.get_apiv2_elements()
191+
]
192+
)
193+
apiv1_elements = "`" + apiv1_elements + "`"
194+
apiv2_elements = "`" + apiv2_elements + "`"
195+
table_rows.append(
196+
f"{mapping.similarity:.4}|{apiv1_elements}|{apiv2_elements}|"
197+
)
198+
return table_rows
199+
200+
def _get_not_mapped_api_elements_for_table(
201+
self, apiv1: API, apiv2: API
202+
) -> list[str]:
203+
not_mapped_api_elements: list[str] = []
204+
not_mapped_apiv1_elements = self._get_not_mapped_api_elements_as_string(apiv1)
205+
for element_id in not_mapped_apiv1_elements:
206+
not_mapped_api_elements.append(f"\u200B|{element_id}||")
207+
not_mapped_apiv2_elements = self._get_not_mapped_api_elements_as_string(
208+
apiv2, print_for_apiv2=True
209+
)
210+
for element_id in not_mapped_apiv2_elements:
211+
not_mapped_api_elements.append(f"\u200B||`{element_id}`|")
212+
return not_mapped_api_elements
213+
214+
def _get_not_mapped_api_elements_as_string(
215+
self, api: API, print_for_apiv2: bool = False
216+
) -> list[str]:
217+
not_mapped_api_elements: list[str] = []
218+
219+
def is_included(
220+
api_element: Union[Attribute, Class, Function, Parameter, Result]
221+
) -> bool:
222+
if not print_for_apiv2:
223+
for mapping in self.mappings:
224+
for element in mapping.get_apiv1_elements():
225+
if isinstance(api_element, Attribute) and isinstance(
226+
element, Attribute
227+
):
228+
if element.name == api_element.name and isinstance(
229+
element.types, type(api_element.types)
230+
):
231+
return True
232+
if isinstance(api_element, Result) and isinstance(
233+
element, Result
234+
):
235+
if (
236+
element.name == api_element.name
237+
and element.docstring == api_element.docstring
238+
):
239+
return True
240+
if not isinstance(
241+
api_element, (Attribute, Result)
242+
) and not isinstance(element, (Attribute, Result)):
243+
if element.id == api_element.id:
244+
return True
245+
return False
246+
for mapping in self.mappings:
247+
for element in mapping.get_apiv2_elements():
248+
if isinstance(api_element, Attribute) and isinstance(
249+
element, Attribute
250+
):
251+
if element.name == api_element.name and isinstance(
252+
element.types, type(api_element.types)
253+
):
254+
return True
255+
if isinstance(api_element, Result) and isinstance(element, Result):
256+
if (
257+
element.name == api_element.name
258+
and element.docstring == api_element.docstring
259+
):
260+
return True
261+
if not isinstance(
262+
api_element, (Attribute, Result)
263+
) and not isinstance(element, (Attribute, Result)):
264+
if element.id == api_element.id:
265+
return True
266+
return False
267+
268+
for class_ in api.classes.values():
269+
if not is_included(class_):
270+
not_mapped_api_elements.append(class_.id)
271+
for function in api.functions.values():
272+
if not is_included(function):
273+
not_mapped_api_elements.append(function.id)
274+
for parameter in api.parameters().values():
275+
if not is_included(parameter):
276+
not_mapped_api_elements.append(parameter.id)
277+
for attribute, class_ in [
278+
(attribute, class_)
279+
for class_ in api.classes.values()
280+
for attribute in class_.instance_attributes
281+
]:
282+
if not is_included(attribute):
283+
not_mapped_api_elements.append(class_.id + "/" + attribute.name)
284+
for result, function in [
285+
(result, function)
286+
for function in api.functions.values()
287+
for result in function.results
288+
]:
289+
if not is_included(result):
290+
not_mapped_api_elements.append(function.id + "/" + result.name)
291+
return not_mapped_api_elements
292+
293+
def print(self, apiv1: API, apiv2: API) -> None:
294+
print("**Similarity**|**APIV1**|**APIV2**|**comment**")
295+
print(":-----:|:-----:|:-----:|:----:|")
296+
table_body = self._get_mappings_for_table()
297+
table_body.extend(self._get_not_mapped_api_elements_for_table(apiv1, apiv2))
298+
table_body.sort(
299+
key=lambda row: max(len(cell.split("/")) for cell in row.split("|")[:-1])
300+
)
301+
for row in table_body:
302+
print(row)
303+
161304
def _remove_duplicates(self) -> None:
162305
for annotation_type in [
163306
"boundaryAnnotations",
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
from ._api_mapping import APIMapping
12
from ._differ import AbstractDiffer, SimpleDiffer
23
from ._mapping import (
3-
APIMapping,
44
ManyToManyMapping,
55
ManyToOneMapping,
66
Mapping,
77
OneToManyMapping,
88
OneToOneMapping,
99
)
10+
from ._strict_differ import StrictDiffer

0 commit comments

Comments
 (0)