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

Commit 1987206

Browse files
authored
feat: migrate @boundary annotation (#1146)
Closes #1142. ### Summary of Changes - add migration for `@boundary` annotations to another version, with the possibilities: - If the mapped parameter has a number boundary and its type is known, the annotation will be migrated as expected. The interval would be converted if the type of the parameter from apiv1 and the type of the parameter from apiv2 mismatch (int, float). From a floating point boundary to an integer boundary, the boundary would be smaller. E.g., The interval (0.5, 9.5) will be converted to [1, 9]. If a conversion happens, the migrated annotation will be marked as unsure. - If the mapped parameter is a parameter, but its type in unknown, an unsure boundary annotation will be created for this parameter, but without any conversion. - If the element is not a parameter, a `@todo` annotation will be created. ### Testing Instructions run the migrate command or view and run the test_migration.py file Co-authored-by: Aclrian <[email protected]>
1 parent d7951e5 commit 1987206

File tree

7 files changed

+536
-17
lines changed

7 files changed

+536
-17
lines changed

package-parser/package_parser/processing/annotations/model/_AnnotationStore.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -106,32 +106,30 @@ def from_json(json: Any) -> AnnotationStore:
106106
valueAnnotations,
107107
)
108108

109-
def add_annotation(self, annotation: AbstractAnnotation):
109+
def add_annotation(self, annotation: AbstractAnnotation) -> None:
110110
if isinstance(annotation, BoundaryAnnotation):
111111
self.boundaryAnnotations.append(annotation)
112-
if isinstance(annotation, BoundaryAnnotation):
113-
self.boundaryAnnotations.append(annotation)
114-
if isinstance(annotation, CalledAfterAnnotation):
112+
elif isinstance(annotation, CalledAfterAnnotation):
115113
self.calledAfterAnnotations.append(annotation)
116-
if isinstance(annotation, CompleteAnnotation):
114+
elif isinstance(annotation, CompleteAnnotation):
117115
self.completeAnnotations.append(annotation)
118-
if isinstance(annotation, DescriptionAnnotation):
116+
elif isinstance(annotation, DescriptionAnnotation):
119117
self.descriptionAnnotations.append(annotation)
120-
if isinstance(annotation, EnumAnnotation):
118+
elif isinstance(annotation, EnumAnnotation):
121119
self.enumAnnotations.append(annotation)
122-
if isinstance(annotation, GroupAnnotation):
120+
elif isinstance(annotation, GroupAnnotation):
123121
self.groupAnnotations.append(annotation)
124-
if isinstance(annotation, MoveAnnotation):
122+
elif isinstance(annotation, MoveAnnotation):
125123
self.moveAnnotations.append(annotation)
126-
if isinstance(annotation, PureAnnotation):
124+
elif isinstance(annotation, PureAnnotation):
127125
self.pureAnnotations.append(annotation)
128-
if isinstance(annotation, RemoveAnnotation):
126+
elif isinstance(annotation, RemoveAnnotation):
129127
self.removeAnnotations.append(annotation)
130-
if isinstance(annotation, RenameAnnotation):
128+
elif isinstance(annotation, RenameAnnotation):
131129
self.renameAnnotations.append(annotation)
132-
if isinstance(annotation, TodoAnnotation):
130+
elif isinstance(annotation, TodoAnnotation):
133131
self.todoAnnotations.append(annotation)
134-
if isinstance(annotation, ValueAnnotation):
132+
elif isinstance(annotation, ValueAnnotation):
135133
self.valueAnnotations.append(annotation)
136134

137135
def to_json(self) -> dict:

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
)
77
from package_parser.processing.api.model import Attribute, Result
88
from package_parser.processing.migration.annotations import (
9+
migrate_boundary_annotation,
910
migrate_enum_annotation,
1011
migrate_rename_annotation,
1112
migrate_todo_annotation,
@@ -31,6 +32,12 @@ def migrate_annotations(
3132
) -> AnnotationStore:
3233
migrated_annotation_store = AnnotationStore()
3334

35+
for boundary_annotation in annotationsv1.boundaryAnnotations:
36+
mapping = _get_mapping_from_annotation(boundary_annotation, mappings)
37+
if mapping is not None:
38+
for annotation in migrate_boundary_annotation(boundary_annotation, mapping):
39+
migrated_annotation_store.add_annotation(annotation)
40+
3441
for enum_annotation in annotationsv1.enumAnnotations:
3542
mapping = _get_mapping_from_annotation(enum_annotation, mappings)
3643
if mapping is not None:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from ._constants import migration_author
2+
from ._migrate_boundary_annotation import migrate_boundary_annotation
23
from ._migrate_enum_annotation import migrate_enum_annotation
34
from ._migrate_rename_annotation import migrate_rename_annotation
45
from ._migrate_todo_annotation import migrate_todo_annotation
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
from copy import deepcopy
2+
from typing import Optional, Tuple
3+
4+
from package_parser.processing.annotations.model import (
5+
AbstractAnnotation,
6+
BoundaryAnnotation,
7+
EnumReviewResult,
8+
Interval,
9+
TodoAnnotation,
10+
)
11+
from package_parser.processing.api.model import (
12+
AbstractType,
13+
Attribute,
14+
NamedType,
15+
Parameter,
16+
Result,
17+
UnionType,
18+
)
19+
from package_parser.processing.migration.model import (
20+
ManyToManyMapping,
21+
ManyToOneMapping,
22+
Mapping,
23+
OneToManyMapping,
24+
OneToOneMapping,
25+
)
26+
27+
from ._constants import migration_author
28+
29+
30+
def migrate_interval_to_fit_parameter_type(
31+
intervalv1: Interval, is_discrete: bool
32+
) -> Interval:
33+
intervalv2 = deepcopy(intervalv1)
34+
if intervalv2.isDiscrete == is_discrete:
35+
return intervalv2
36+
if is_discrete:
37+
intervalv2.isDiscrete = True
38+
if intervalv1.upperLimitType in (0, 1):
39+
intervalv2.upperIntervalLimit = int(intervalv1.upperIntervalLimit)
40+
intervalv2.upperLimitType = 1
41+
if intervalv2.upperIntervalLimit == intervalv1.upperIntervalLimit:
42+
intervalv2.upperLimitType = intervalv1.upperLimitType
43+
if intervalv1.lowerLimitType in (0, 1):
44+
intervalv2.lowerIntervalLimit = int(intervalv1.lowerIntervalLimit)
45+
intervalv2.lowerLimitType = 1
46+
if intervalv2.lowerIntervalLimit == intervalv1.lowerIntervalLimit:
47+
intervalv2.lowerLimitType = intervalv1.lowerLimitType
48+
else:
49+
intervalv2.lowerIntervalLimit += 1
50+
else:
51+
intervalv2.isDiscrete = False
52+
if intervalv1.upperLimitType in (0, 1):
53+
intervalv2.upperIntervalLimit = float(intervalv1.upperIntervalLimit)
54+
if intervalv1.lowerLimitType in (0, 1):
55+
intervalv2.lowerIntervalLimit = float(intervalv1.lowerIntervalLimit)
56+
return intervalv2
57+
58+
59+
def _contains_number_and_is_discrete(
60+
type_: Optional[AbstractType],
61+
) -> Tuple[bool, bool]:
62+
if type_ is None:
63+
return False, False
64+
if isinstance(type_, NamedType):
65+
return type_.name in ("int", "float"), type_.name == "int"
66+
if isinstance(type_, UnionType):
67+
for element in type_.types:
68+
is_number, is_discrete = _contains_number_and_is_discrete(element)
69+
if is_number:
70+
return is_number, is_discrete
71+
return False, False
72+
73+
74+
# pylint: disable=duplicate-code
75+
def migrate_boundary_annotation(
76+
boundary_annotation: BoundaryAnnotation, mapping: Mapping
77+
) -> list[AbstractAnnotation]:
78+
boundary_annotation = deepcopy(boundary_annotation)
79+
authors = boundary_annotation.authors
80+
authors.append(migration_author)
81+
boundary_annotation.authors = authors
82+
83+
migrate_text = (
84+
"The @Boundary Annotation with the interval '"
85+
+ str(boundary_annotation.interval.to_json())
86+
+ "' from the previous version was at '"
87+
+ boundary_annotation.target
88+
+ "' and the possible alternatives in the new version of the api are: "
89+
+ ", ".join(
90+
map(lambda api_element: api_element.name, mapping.get_apiv2_elements())
91+
)
92+
)
93+
94+
if isinstance(mapping, (OneToOneMapping, ManyToOneMapping)):
95+
parameter = mapping.get_apiv2_elements()[0]
96+
if isinstance(parameter, (Attribute, Result)):
97+
return []
98+
if isinstance(parameter, Parameter):
99+
boundary_annotation.target = parameter.id
100+
(
101+
parameter_expects_number,
102+
parameter_type_is_discrete,
103+
) = _contains_number_and_is_discrete(parameter.type)
104+
if parameter.type is None:
105+
boundary_annotation.reviewResult = EnumReviewResult.UNSURE
106+
return [boundary_annotation]
107+
if parameter_expects_number:
108+
if (
109+
parameter_type_is_discrete
110+
is not boundary_annotation.interval.isDiscrete
111+
):
112+
boundary_annotation.reviewResult = EnumReviewResult.UNSURE
113+
boundary_annotation.interval = (
114+
migrate_interval_to_fit_parameter_type(
115+
boundary_annotation.interval, parameter_type_is_discrete
116+
)
117+
)
118+
return [boundary_annotation]
119+
return [
120+
TodoAnnotation(
121+
parameter.id,
122+
authors,
123+
boundary_annotation.reviewers,
124+
boundary_annotation.comment,
125+
EnumReviewResult.NONE,
126+
migrate_text,
127+
)
128+
]
129+
migrated_annotations: list[AbstractAnnotation] = []
130+
if isinstance(mapping, (OneToManyMapping, ManyToManyMapping)):
131+
for parameter in mapping.get_apiv2_elements():
132+
if isinstance(parameter, Parameter):
133+
is_number, is_discrete = _contains_number_and_is_discrete(
134+
parameter.type
135+
)
136+
if (
137+
parameter.type is not None
138+
and is_number
139+
and is_discrete is boundary_annotation.interval.isDiscrete
140+
):
141+
migrated_annotations.append(
142+
BoundaryAnnotation(
143+
parameter.id,
144+
authors,
145+
boundary_annotation.reviewers,
146+
boundary_annotation.comment,
147+
EnumReviewResult.NONE,
148+
boundary_annotation.interval,
149+
)
150+
)
151+
elif parameter.type is not None and is_number:
152+
migrated_annotations.append(
153+
BoundaryAnnotation(
154+
parameter.id,
155+
authors,
156+
boundary_annotation.reviewers,
157+
boundary_annotation.comment,
158+
EnumReviewResult.UNSURE,
159+
migrate_interval_to_fit_parameter_type(
160+
boundary_annotation.interval,
161+
is_discrete,
162+
),
163+
)
164+
)
165+
elif parameter.type is None:
166+
migrated_annotations.append(
167+
BoundaryAnnotation(
168+
parameter.id,
169+
authors,
170+
boundary_annotation.reviewers,
171+
boundary_annotation.comment,
172+
EnumReviewResult.UNSURE,
173+
boundary_annotation.interval,
174+
)
175+
)
176+
continue
177+
if not isinstance(parameter, (Attribute, Result)):
178+
migrated_annotations.append(
179+
TodoAnnotation(
180+
parameter.id,
181+
authors,
182+
boundary_annotation.reviewers,
183+
boundary_annotation.comment,
184+
EnumReviewResult.UNSURE,
185+
migrate_text,
186+
)
187+
)
188+
return migrated_annotations

package-parser/package_parser/processing/migration/annotations/_migrate_enum_annotation.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def _default_value_is_in_instance_values_or_is_empty(
4747
)
4848

4949

50+
# pylint: disable=duplicate-code
5051
def migrate_enum_annotation(
5152
enum_annotation: EnumAnnotation, mapping: Mapping
5253
) -> list[AbstractAnnotation]:
@@ -92,6 +93,7 @@ def migrate_enum_annotation(
9293
return []
9394
else:
9495
enum_annotation.reviewResult = EnumReviewResult.UNSURE
96+
enum_annotation.target = parameter.id
9597
return [enum_annotation]
9698
return [
9799
TodoAnnotation(

0 commit comments

Comments
 (0)