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

Commit 4772cd8

Browse files
authored
feat: support inheritance while migrating (#1234)
Closes #1233. ### Summary of Changes - new additional differ, that compares only api elements whose parent classes are mapped onto each other or with a super- or subclass - improve flexibility for adding and changing and deleting differs in `run_migrate.py` by adding a constructor to AbstractDiffer - new methods for getting all the results or attributes of one api - remove distance_elements and used edit distance from levenshtein-api instead - hash function for subclasses of AbctractType and Parameter - fix possible runtime errors (RecursionError, NoneError) ### Testing Instructions run `migrate` command or `test_inheritance_differ.py`
1 parent 7bbafd1 commit 4772cd8

28 files changed

+1230
-200
lines changed

package-parser/package_parser/cli/_run_migrate.py

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import os
22
from pathlib import Path
3+
from typing import Any
34

4-
from package_parser.processing.migration import Migration
5+
from package_parser.processing.migration import APIMapping, Migration
56
from package_parser.processing.migration.model import (
6-
APIMapping,
7+
AbstractDiffer,
8+
InheritanceDiffer,
9+
Mapping,
710
SimpleDiffer,
811
StrictDiffer,
912
)
@@ -24,26 +27,51 @@ def _run_migrate_command(
2427
apiv1 = _read_api_file(apiv1_file_path)
2528
apiv2 = _read_api_file(apiv2_file_path)
2629
annotationsv1 = _read_annotations_file(annotations_file_path)
27-
differ = SimpleDiffer()
28-
api_mapping = APIMapping(apiv1, apiv2, differ)
29-
mappings = api_mapping.map_api()
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)
34-
migration.migrate_annotations()
35-
migration.print(apiv1, apiv2)
36-
migrated_annotations_file = Path(
37-
os.path.join(out_dir_path, "migrated_annotationsv" + apiv2.version + ".json")
38-
)
39-
unsure_migrated_annotations_file = Path(
40-
os.path.join(
41-
out_dir_path, "unsure_migrated_annotationsv" + apiv2.version + ".json"
30+
31+
differ_init_list: list[tuple[type[AbstractDiffer], dict[str, Any]]] = [
32+
(SimpleDiffer, {}),
33+
(StrictDiffer, {}),
34+
(InheritanceDiffer, {}),
35+
]
36+
previous_base_differ = None
37+
previous_mappings: list[Mapping] = []
38+
39+
for differ_init in differ_init_list:
40+
differ_class, additional_parameters = differ_init
41+
differ = differ_class(
42+
previous_base_differ,
43+
previous_mappings,
44+
apiv1,
45+
apiv2,
46+
**additional_parameters
47+
)
48+
api_mapping = APIMapping(apiv1, apiv2, differ)
49+
mappings = api_mapping.map_api()
50+
51+
previous_mappings = mappings
52+
previous_base_differ = (
53+
differ
54+
if differ.get_related_mappings() is None
55+
else differ.previous_base_differ
56+
)
57+
58+
if previous_mappings is not None:
59+
migration = Migration(annotationsv1, previous_mappings)
60+
migration.migrate_annotations()
61+
migration.print(apiv1, apiv2)
62+
migrated_annotations_file = Path(
63+
os.path.join(
64+
out_dir_path, "migrated_annotationsv" + apiv2.version + ".json"
65+
)
66+
)
67+
unsure_migrated_annotations_file = Path(
68+
os.path.join(
69+
out_dir_path, "unsure_migrated_annotationsv" + apiv2.version + ".json"
70+
)
71+
)
72+
_write_annotations_file(
73+
migration.migrated_annotation_store, migrated_annotations_file
74+
)
75+
_write_annotations_file(
76+
migration.unsure_migrated_annotation_store, unsure_migrated_annotations_file
4277
)
43-
)
44-
_write_annotations_file(
45-
migration.migrated_annotation_store, migrated_annotations_file
46-
)
47-
_write_annotations_file(
48-
migration.unsure_migrated_annotation_store, unsure_migrated_annotations_file
49-
)

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

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
from __future__ import annotations
22

3-
from dataclasses import dataclass
4-
from typing import Any, Optional
3+
from dataclasses import dataclass, field
4+
from typing import Any, Optional, Union
55

6+
from black import FileMode, InvalidInput, format_str
7+
from black.brackets import BracketMatchError
8+
from black.linegen import CannotSplit
9+
from black.trans import CannotTransform
610
from package_parser.utils import parent_id
711

812
from ._documentation import ClassDocumentation, FunctionDocumentation
@@ -79,6 +83,26 @@ def parameters(self) -> dict[str, Parameter]:
7983

8084
return result
8185

86+
def attributes(self) -> dict[str, Attribute]:
87+
result: dict[str, Attribute] = {}
88+
89+
for class_ in self.classes.values():
90+
for attribute in class_.instance_attributes:
91+
attribute_id = f"{class_.id}/{attribute.name}"
92+
result[attribute_id] = attribute
93+
94+
return result
95+
96+
def results(self) -> dict[str, Result]:
97+
result_dict: dict[str, Result] = {}
98+
99+
for function in self.functions.values():
100+
for result in function.results:
101+
result_id = f"{function.id}/{result.name}"
102+
result_dict[result_id] = result
103+
104+
return result_dict
105+
82106
def get_default_value(self, parameter_id: str) -> Optional[str]:
83107
function_id = parent_id(parameter_id)
84108

@@ -241,6 +265,7 @@ def __init__(
241265
self.documentation: ClassDocumentation = documentation
242266
self.code: str = code
243267
self.instance_attributes = instance_attributes
268+
self.formatted_code: Optional[str] = None
244269

245270
@property
246271
def name(self) -> str:
@@ -267,16 +292,36 @@ def to_json(self) -> Any:
267292
],
268293
}
269294

295+
def get_formatted_code(self) -> str:
296+
if self.formatted_code is None:
297+
self.formatted_code = _generate_formatted_code(self)
298+
return self.formatted_code
299+
270300
def __repr__(self) -> str:
271301
return "Class(id=" + self.id + ")"
272302

273303

304+
def _generate_formatted_code(api_element: Union[Class, Function]) -> str:
305+
code = api_element.code
306+
try:
307+
code_tmp = format_str(code, mode=FileMode())
308+
except (CannotSplit, CannotTransform, InvalidInput, BracketMatchError):
309+
# As long as the api black has no documentation, we do not know which exceptions are raised
310+
pass
311+
else:
312+
code = code_tmp
313+
return code
314+
315+
274316
@dataclass
275317
class Attribute:
276318
name: str
277319
types: Optional[AbstractType]
278320
class_id: Optional[str] = None
279321

322+
def __hash__(self) -> int:
323+
return hash((self.name, self.class_id, self.types))
324+
280325
def to_json(self) -> dict[str, Any]:
281326
types_json = self.types.to_json() if self.types is not None else None
282327
return {"name": self.name, "types": types_json}
@@ -312,6 +357,10 @@ class Function:
312357
reexported_by: list[str]
313358
documentation: FunctionDocumentation
314359
code: str
360+
formatted_code: Optional[str] = field(init=False)
361+
362+
def __post_init__(self) -> None:
363+
self.formatted_code = None
315364

316365
@staticmethod
317366
def from_json(json: Any) -> Function:
@@ -352,6 +401,11 @@ def to_json(self) -> Any:
352401
"code": self.code,
353402
}
354403

404+
def get_formatted_code(self) -> str:
405+
if self.formatted_code is None:
406+
self.formatted_code = _generate_formatted_code(self)
407+
return self.formatted_code
408+
355409
def __repr__(self) -> str:
356410
return "Function(id=" + self.id + ")"
357411

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@ def from_dict(d: dict) -> ParameterDocumentation:
4242

4343
def to_dict(self) -> dict:
4444
return dataclasses.asdict(self)
45+
46+
def __hash__(self) -> int:
47+
return hash((self.type, self.default_value, self.description))

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ def from_json(json: Any) -> Parameter:
2020
ParameterDocumentation.from_dict(json.get("docstring", {})),
2121
)
2222

23+
def __hash__(self) -> int:
24+
return hash(
25+
(
26+
self.id,
27+
self.name,
28+
self.qname,
29+
self.default_value,
30+
self.assigned_by,
31+
self.is_public,
32+
self.documentation,
33+
)
34+
)
35+
2336
def __init__(
2437
self,
2538
id_: str,

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ def __eq__(self, other: object) -> bool:
5252
return self.name == other.name
5353
return False
5454

55+
def __hash__(self) -> int:
56+
return hash(self.name)
57+
5558

5659
@dataclass
5760
class EnumType(AbstractType):
@@ -105,6 +108,9 @@ def update(self, enum: EnumType) -> None:
105108
def to_json(self) -> dict[str, Any]:
106109
return {"kind": self.__class__.__name__, "values": self.values}
107110

111+
def __hash__(self) -> int:
112+
return hash(frozenset(self.values))
113+
108114

109115
@dataclass
110116
class BoundaryType(AbstractType):
@@ -201,6 +207,11 @@ def __eq__(self, __o: object) -> bool:
201207
return self.max_inclusive == __o.max_inclusive
202208
return False
203209

210+
def __hash__(self) -> int:
211+
return hash(
212+
(self.base_type, self.min, self.min_inclusive, self.max, self.max_inclusive)
213+
)
214+
204215
def to_json(self) -> dict[str, Any]:
205216
return {
206217
"kind": self.__class__.__name__,
@@ -234,6 +245,9 @@ def to_json(self) -> dict[str, Any]:
234245

235246
return {"kind": self.__class__.__name__, "types": type_list}
236247

248+
def __hash__(self) -> int:
249+
return hash((frozenset(self.types)))
250+
237251

238252
def create_type(
239253
parameter_documentation: ParameterDocumentation,
Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,2 @@
1-
from package_parser.processing.migration.model import (
2-
AbstractDiffer,
3-
APIMapping,
4-
ManyToManyMapping,
5-
ManyToOneMapping,
6-
Mapping,
7-
OneToManyMapping,
8-
OneToOneMapping,
9-
SimpleDiffer,
10-
)
11-
1+
from ._api_mapping import APIMapping
122
from ._migrate import Migration

0 commit comments

Comments
 (0)