diff --git a/package-parser/package_parser/processing/annotations/model/_AnnotationStore.py b/package-parser/package_parser/processing/annotations/model/_AnnotationStore.py index cf9b3639c..8d6a74826 100644 --- a/package-parser/package_parser/processing/annotations/model/_AnnotationStore.py +++ b/package-parser/package_parser/processing/annotations/model/_AnnotationStore.py @@ -106,32 +106,30 @@ def from_json(json: Any) -> AnnotationStore: valueAnnotations, ) - def add_annotation(self, annotation: AbstractAnnotation): + def add_annotation(self, annotation: AbstractAnnotation) -> None: if isinstance(annotation, BoundaryAnnotation): self.boundaryAnnotations.append(annotation) - if isinstance(annotation, BoundaryAnnotation): - self.boundaryAnnotations.append(annotation) - if isinstance(annotation, CalledAfterAnnotation): + elif isinstance(annotation, CalledAfterAnnotation): self.calledAfterAnnotations.append(annotation) - if isinstance(annotation, CompleteAnnotation): + elif isinstance(annotation, CompleteAnnotation): self.completeAnnotations.append(annotation) - if isinstance(annotation, DescriptionAnnotation): + elif isinstance(annotation, DescriptionAnnotation): self.descriptionAnnotations.append(annotation) - if isinstance(annotation, EnumAnnotation): + elif isinstance(annotation, EnumAnnotation): self.enumAnnotations.append(annotation) - if isinstance(annotation, GroupAnnotation): + elif isinstance(annotation, GroupAnnotation): self.groupAnnotations.append(annotation) - if isinstance(annotation, MoveAnnotation): + elif isinstance(annotation, MoveAnnotation): self.moveAnnotations.append(annotation) - if isinstance(annotation, PureAnnotation): + elif isinstance(annotation, PureAnnotation): self.pureAnnotations.append(annotation) - if isinstance(annotation, RemoveAnnotation): + elif isinstance(annotation, RemoveAnnotation): self.removeAnnotations.append(annotation) - if isinstance(annotation, RenameAnnotation): + elif isinstance(annotation, RenameAnnotation): self.renameAnnotations.append(annotation) - if isinstance(annotation, TodoAnnotation): + elif isinstance(annotation, TodoAnnotation): self.todoAnnotations.append(annotation) - if isinstance(annotation, ValueAnnotation): + elif isinstance(annotation, ValueAnnotation): self.valueAnnotations.append(annotation) def to_json(self) -> dict: diff --git a/package-parser/package_parser/processing/migration/_migrate.py b/package-parser/package_parser/processing/migration/_migrate.py index f9273a2c3..fb09a4f70 100644 --- a/package-parser/package_parser/processing/migration/_migrate.py +++ b/package-parser/package_parser/processing/migration/_migrate.py @@ -6,6 +6,7 @@ ) from package_parser.processing.api.model import Attribute, Result from package_parser.processing.migration.annotations import ( + migrate_boundary_annotation, migrate_enum_annotation, migrate_rename_annotation, migrate_todo_annotation, @@ -31,6 +32,12 @@ def migrate_annotations( ) -> AnnotationStore: migrated_annotation_store = AnnotationStore() + for boundary_annotation in annotationsv1.boundaryAnnotations: + mapping = _get_mapping_from_annotation(boundary_annotation, mappings) + if mapping is not None: + for annotation in migrate_boundary_annotation(boundary_annotation, mapping): + migrated_annotation_store.add_annotation(annotation) + for enum_annotation in annotationsv1.enumAnnotations: mapping = _get_mapping_from_annotation(enum_annotation, mappings) if mapping is not None: diff --git a/package-parser/package_parser/processing/migration/annotations/__init__.py b/package-parser/package_parser/processing/migration/annotations/__init__.py index 6344d48ba..e1ba05117 100644 --- a/package-parser/package_parser/processing/migration/annotations/__init__.py +++ b/package-parser/package_parser/processing/migration/annotations/__init__.py @@ -1,4 +1,5 @@ from ._constants import migration_author +from ._migrate_boundary_annotation import migrate_boundary_annotation from ._migrate_enum_annotation import migrate_enum_annotation from ._migrate_rename_annotation import migrate_rename_annotation from ._migrate_todo_annotation import migrate_todo_annotation diff --git a/package-parser/package_parser/processing/migration/annotations/_migrate_boundary_annotation.py b/package-parser/package_parser/processing/migration/annotations/_migrate_boundary_annotation.py new file mode 100644 index 000000000..d21d0484b --- /dev/null +++ b/package-parser/package_parser/processing/migration/annotations/_migrate_boundary_annotation.py @@ -0,0 +1,188 @@ +from copy import deepcopy +from typing import Optional, Tuple + +from package_parser.processing.annotations.model import ( + AbstractAnnotation, + BoundaryAnnotation, + EnumReviewResult, + Interval, + TodoAnnotation, +) +from package_parser.processing.api.model import ( + AbstractType, + Attribute, + NamedType, + Parameter, + Result, + UnionType, +) +from package_parser.processing.migration.model import ( + ManyToManyMapping, + ManyToOneMapping, + Mapping, + OneToManyMapping, + OneToOneMapping, +) + +from ._constants import migration_author + + +def migrate_interval_to_fit_parameter_type( + intervalv1: Interval, is_discrete: bool +) -> Interval: + intervalv2 = deepcopy(intervalv1) + if intervalv2.isDiscrete == is_discrete: + return intervalv2 + if is_discrete: + intervalv2.isDiscrete = True + if intervalv1.upperLimitType in (0, 1): + intervalv2.upperIntervalLimit = int(intervalv1.upperIntervalLimit) + intervalv2.upperLimitType = 1 + if intervalv2.upperIntervalLimit == intervalv1.upperIntervalLimit: + intervalv2.upperLimitType = intervalv1.upperLimitType + if intervalv1.lowerLimitType in (0, 1): + intervalv2.lowerIntervalLimit = int(intervalv1.lowerIntervalLimit) + intervalv2.lowerLimitType = 1 + if intervalv2.lowerIntervalLimit == intervalv1.lowerIntervalLimit: + intervalv2.lowerLimitType = intervalv1.lowerLimitType + else: + intervalv2.lowerIntervalLimit += 1 + else: + intervalv2.isDiscrete = False + if intervalv1.upperLimitType in (0, 1): + intervalv2.upperIntervalLimit = float(intervalv1.upperIntervalLimit) + if intervalv1.lowerLimitType in (0, 1): + intervalv2.lowerIntervalLimit = float(intervalv1.lowerIntervalLimit) + return intervalv2 + + +def _contains_number_and_is_discrete( + type_: Optional[AbstractType], +) -> Tuple[bool, bool]: + if type_ is None: + return False, False + if isinstance(type_, NamedType): + return type_.name in ("int", "float"), type_.name == "int" + if isinstance(type_, UnionType): + for element in type_.types: + is_number, is_discrete = _contains_number_and_is_discrete(element) + if is_number: + return is_number, is_discrete + return False, False + + +# pylint: disable=duplicate-code +def migrate_boundary_annotation( + boundary_annotation: BoundaryAnnotation, mapping: Mapping +) -> list[AbstractAnnotation]: + boundary_annotation = deepcopy(boundary_annotation) + authors = boundary_annotation.authors + authors.append(migration_author) + boundary_annotation.authors = authors + + migrate_text = ( + "The @Boundary Annotation with the interval '" + + str(boundary_annotation.interval.to_json()) + + "' from the previous version was at '" + + boundary_annotation.target + + "' and the possible alternatives in the new version of the api are: " + + ", ".join( + map(lambda api_element: api_element.name, mapping.get_apiv2_elements()) + ) + ) + + if isinstance(mapping, (OneToOneMapping, ManyToOneMapping)): + parameter = mapping.get_apiv2_elements()[0] + if isinstance(parameter, (Attribute, Result)): + return [] + if isinstance(parameter, Parameter): + boundary_annotation.target = parameter.id + ( + parameter_expects_number, + parameter_type_is_discrete, + ) = _contains_number_and_is_discrete(parameter.type) + if parameter.type is None: + boundary_annotation.reviewResult = EnumReviewResult.UNSURE + return [boundary_annotation] + if parameter_expects_number: + if ( + parameter_type_is_discrete + is not boundary_annotation.interval.isDiscrete + ): + boundary_annotation.reviewResult = EnumReviewResult.UNSURE + boundary_annotation.interval = ( + migrate_interval_to_fit_parameter_type( + boundary_annotation.interval, parameter_type_is_discrete + ) + ) + return [boundary_annotation] + return [ + TodoAnnotation( + parameter.id, + authors, + boundary_annotation.reviewers, + boundary_annotation.comment, + EnumReviewResult.NONE, + migrate_text, + ) + ] + migrated_annotations: list[AbstractAnnotation] = [] + if isinstance(mapping, (OneToManyMapping, ManyToManyMapping)): + for parameter in mapping.get_apiv2_elements(): + if isinstance(parameter, Parameter): + is_number, is_discrete = _contains_number_and_is_discrete( + parameter.type + ) + if ( + parameter.type is not None + and is_number + and is_discrete is boundary_annotation.interval.isDiscrete + ): + migrated_annotations.append( + BoundaryAnnotation( + parameter.id, + authors, + boundary_annotation.reviewers, + boundary_annotation.comment, + EnumReviewResult.NONE, + boundary_annotation.interval, + ) + ) + elif parameter.type is not None and is_number: + migrated_annotations.append( + BoundaryAnnotation( + parameter.id, + authors, + boundary_annotation.reviewers, + boundary_annotation.comment, + EnumReviewResult.UNSURE, + migrate_interval_to_fit_parameter_type( + boundary_annotation.interval, + is_discrete, + ), + ) + ) + elif parameter.type is None: + migrated_annotations.append( + BoundaryAnnotation( + parameter.id, + authors, + boundary_annotation.reviewers, + boundary_annotation.comment, + EnumReviewResult.UNSURE, + boundary_annotation.interval, + ) + ) + continue + if not isinstance(parameter, (Attribute, Result)): + migrated_annotations.append( + TodoAnnotation( + parameter.id, + authors, + boundary_annotation.reviewers, + boundary_annotation.comment, + EnumReviewResult.UNSURE, + migrate_text, + ) + ) + return migrated_annotations diff --git a/package-parser/package_parser/processing/migration/annotations/_migrate_enum_annotation.py b/package-parser/package_parser/processing/migration/annotations/_migrate_enum_annotation.py index 19c8c687b..4d8a3ab49 100644 --- a/package-parser/package_parser/processing/migration/annotations/_migrate_enum_annotation.py +++ b/package-parser/package_parser/processing/migration/annotations/_migrate_enum_annotation.py @@ -47,6 +47,7 @@ def _default_value_is_in_instance_values_or_is_empty( ) +# pylint: disable=duplicate-code def migrate_enum_annotation( enum_annotation: EnumAnnotation, mapping: Mapping ) -> list[AbstractAnnotation]: @@ -92,6 +93,7 @@ def migrate_enum_annotation( return [] else: enum_annotation.reviewResult = EnumReviewResult.UNSURE + enum_annotation.target = parameter.id return [enum_annotation] return [ TodoAnnotation( diff --git a/package-parser/tests/processing/migration/annotations/test_boundary_migration.py b/package-parser/tests/processing/migration/annotations/test_boundary_migration.py new file mode 100644 index 000000000..320cc2fed --- /dev/null +++ b/package-parser/tests/processing/migration/annotations/test_boundary_migration.py @@ -0,0 +1,309 @@ +from typing import Tuple + +from package_parser.processing.annotations.model import ( + AbstractAnnotation, + BoundaryAnnotation, + EnumReviewResult, + Interval, +) +from package_parser.processing.api.model import ( + Parameter, + ParameterAssignment, + ParameterDocumentation, +) +from package_parser.processing.migration import ( + Mapping, + OneToManyMapping, + OneToOneMapping, +) +from package_parser.processing.migration.annotations import migration_author + + +def migrate_boundary_annotation_data_one_to_one_mapping() -> Tuple[ + Mapping, + AbstractAnnotation, + list[AbstractAnnotation], +]: + parameterv1 = Parameter( + id_="test/test.boundary.test1.testA", + name="testA", + qname="test.enum.test1.testA", + default_value="1", + assigned_by=ParameterAssignment.POSITION_OR_NAME, + is_public=True, + documentation=ParameterDocumentation("int", "1", ""), + ) + parameterv2 = Parameter( + id_="test/test.boundary.test1.testB", + name="testB", + qname="test.enum.test1.testB", + default_value="1", + assigned_by=ParameterAssignment.POSITION_OR_NAME, + is_public=True, + documentation=ParameterDocumentation("int", "1", ""), + ) + boundary_annotation = BoundaryAnnotation( + target="test/test.boundary.test1.testA", + authors=["testauthor"], + reviewers=[], + comment="", + reviewResult=EnumReviewResult.NONE, + interval=Interval( + isDiscrete=True, + lowerIntervalLimit=0, + lowerLimitType=1, + upperIntervalLimit=10, + upperLimitType=1, + ), + ) + migrated_boundary_annotation = BoundaryAnnotation( + target="test/test.boundary.test1.testB", + authors=["testauthor", migration_author], + reviewers=[], + comment="", + reviewResult=EnumReviewResult.NONE, + interval=Interval( + isDiscrete=True, + lowerIntervalLimit=0, + lowerLimitType=1, + upperIntervalLimit=10, + upperLimitType=1, + ), + ) + return ( + OneToOneMapping(1.0, parameterv1, parameterv2), + boundary_annotation, + [migrated_boundary_annotation], + ) + + +def migrate_boundary_annotation_data_one_to_one_mapping_int_to_float() -> Tuple[ + Mapping, + AbstractAnnotation, + list[AbstractAnnotation], +]: + parameterv1 = Parameter( + id_="test/test.boundary.test2.testA", + name="testA", + qname="test.enum.test2.testA", + default_value="1", + assigned_by=ParameterAssignment.POSITION_OR_NAME, + is_public=True, + documentation=ParameterDocumentation("int", "1", "int in the range of (0, 10)"), + ) + parameterv2 = Parameter( + id_="test/test.boundary.test2.testB", + name="testB", + qname="test.enum.test2.testB", + default_value="1.0", + assigned_by=ParameterAssignment.POSITION_OR_NAME, + is_public=True, + documentation=ParameterDocumentation( + "float", "1.0", "float in the range of [1.0, 9.0]" + ), + ) + boundary_annotation = BoundaryAnnotation( + target="test/test.boundary.test2.testA", + authors=["testauthor"], + reviewers=[], + comment="", + reviewResult=EnumReviewResult.NONE, + interval=Interval( + isDiscrete=True, + lowerIntervalLimit=0, + lowerLimitType=0, + upperIntervalLimit=10, + upperLimitType=0, + ), + ) + migrated_boundary_annotation = BoundaryAnnotation( + target="test/test.boundary.test2.testB", + authors=["testauthor", migration_author], + reviewers=[], + comment="", + reviewResult=EnumReviewResult.UNSURE, + interval=Interval( + isDiscrete=False, + lowerIntervalLimit=0.0, + lowerLimitType=0, + upperIntervalLimit=10.0, + upperLimitType=0, + ), + ) + return ( + OneToOneMapping(1.0, parameterv1, parameterv2), + boundary_annotation, + [migrated_boundary_annotation], + ) + + +def migrate_boundary_annotation_data_one_to_one_mapping_float_to_int() -> Tuple[ + Mapping, + AbstractAnnotation, + list[AbstractAnnotation], +]: + parameterv1 = Parameter( + id_="test/test.boundary.test3.testA", + name="testA", + qname="test.enum.test3.testA", + default_value="1.0", + assigned_by=ParameterAssignment.POSITION_OR_NAME, + is_public=True, + documentation=ParameterDocumentation( + "float", "1.0", "float in the range of [0.5, 9.5]" + ), + ) + parameterv2 = Parameter( + id_="test/test.boundary.test3.testB", + name="testB", + qname="test.enum.test3.testB", + default_value="1", + assigned_by=ParameterAssignment.POSITION_OR_NAME, + is_public=True, + documentation=ParameterDocumentation("int", "1", "int in the range of (0, 10)"), + ) + boundary_annotation = BoundaryAnnotation( + target="test/test.boundary.test3.testA", + authors=["testauthor"], + reviewers=[], + comment="", + reviewResult=EnumReviewResult.NONE, + interval=Interval( + isDiscrete=False, + lowerIntervalLimit=0.5, + lowerLimitType=0, + upperIntervalLimit=9.5, + upperLimitType=0, + ), + ) + migrated_boundary_annotation = BoundaryAnnotation( + target="test/test.boundary.test3.testB", + authors=["testauthor", migration_author], + reviewers=[], + comment="", + reviewResult=EnumReviewResult.UNSURE, + interval=Interval( + isDiscrete=True, + lowerIntervalLimit=1, + lowerLimitType=1, + upperIntervalLimit=9, + upperLimitType=1, + ), + ) + return ( + OneToOneMapping(1.0, parameterv1, parameterv2), + boundary_annotation, + [migrated_boundary_annotation], + ) + + +def migrate_boundary_annotation_data_one_to_many_mapping() -> Tuple[ + Mapping, + AbstractAnnotation, + list[AbstractAnnotation], +]: + parameterv1 = Parameter( + id_="test/test.boundary.test4.testv1", + name="testA", + qname="test.enum.test2.testA", + default_value="1", + assigned_by=ParameterAssignment.POSITION_OR_NAME, + is_public=True, + documentation=ParameterDocumentation("int", "1", "int in the range of (0, 10)"), + ) + parameterv2_a = Parameter( + id_="test/test.boundary.test4.testA", + name="testA", + qname="test.enum.test3.testA", + default_value="1", + assigned_by=ParameterAssignment.POSITION_OR_NAME, + is_public=True, + documentation=ParameterDocumentation("int", "1", "int in the range of (0, 10)"), + ) + parameterv2_b = Parameter( + id_="test/test.boundary.test4.testB", + name="testB", + qname="test.enum.test3.testB", + default_value="1.0", + assigned_by=ParameterAssignment.POSITION_OR_NAME, + is_public=True, + documentation=ParameterDocumentation( + "float", "1.0", "float in the range of (0.0, 10.0)" + ), + ) + parameterv2_c = Parameter( + id_="test/test.boundary.test4.testC", + name="testC", + qname="test.enum.test3.testC", + default_value="", + assigned_by=ParameterAssignment.POSITION_OR_NAME, + is_public=True, + documentation=ParameterDocumentation("", "", ""), + ) + boundary_annotation = BoundaryAnnotation( + target="test/test.boundary.test4.testv1", + authors=["testauthor"], + reviewers=[], + comment="", + reviewResult=EnumReviewResult.NONE, + interval=Interval( + isDiscrete=True, + lowerIntervalLimit=0, + lowerLimitType=1, + upperIntervalLimit=10, + upperLimitType=1, + ), + ) + migrated_boundary_annotation_a = BoundaryAnnotation( + target="test/test.boundary.test4.testA", + authors=["testauthor", migration_author], + reviewers=[], + comment="", + reviewResult=EnumReviewResult.NONE, + interval=Interval( + isDiscrete=True, + lowerIntervalLimit=0.0, + lowerLimitType=1, + upperIntervalLimit=10.0, + upperLimitType=1, + ), + ) + migrated_boundary_annotation_b = BoundaryAnnotation( + target="test/test.boundary.test4.testB", + authors=["testauthor", migration_author], + reviewers=[], + comment="", + reviewResult=EnumReviewResult.UNSURE, + interval=Interval( + isDiscrete=False, + lowerIntervalLimit=0, + lowerLimitType=1, + upperIntervalLimit=10, + upperLimitType=1, + ), + ) + migrated_boundary_annotation_c = BoundaryAnnotation( + target="test/test.boundary.test4.testC", + authors=["testauthor", migration_author], + reviewers=[], + comment="", + reviewResult=EnumReviewResult.UNSURE, + interval=Interval( + isDiscrete=True, + lowerIntervalLimit=0, + lowerLimitType=1, + upperIntervalLimit=10, + upperLimitType=1, + ), + ) + return ( + OneToManyMapping( + 1.0, parameterv1, [parameterv2_a, parameterv2_b, parameterv2_c] + ), + boundary_annotation, + [ + migrated_boundary_annotation_a, + migrated_boundary_annotation_b, + migrated_boundary_annotation_c, + ], + ) diff --git a/package-parser/tests/processing/migration/test_migration.py b/package-parser/tests/processing/migration/test_migration.py index 360021a62..a187bb252 100644 --- a/package-parser/tests/processing/migration/test_migration.py +++ b/package-parser/tests/processing/migration/test_migration.py @@ -4,6 +4,12 @@ ) from package_parser.processing.migration import migrate_annotations from package_parser.processing.migration.model import Mapping +from tests.processing.migration.annotations.test_boundary_migration import ( + migrate_boundary_annotation_data_one_to_many_mapping, + migrate_boundary_annotation_data_one_to_one_mapping, + migrate_boundary_annotation_data_one_to_one_mapping_float_to_int, + migrate_boundary_annotation_data_one_to_one_mapping_int_to_float, +) from tests.processing.migration.annotations.test_enum_migration import ( migrate_enum_annotation_data_one_to_many_mapping, migrate_enum_annotation_data_one_to_many_mapping__only_one_relevant_mapping, @@ -21,15 +27,23 @@ ) test_data = [ + # enum annotation + migrate_enum_annotation_data_one_to_one_mapping(), + migrate_enum_annotation_data_one_to_many_mapping(), + migrate_enum_annotation_data_one_to_many_mapping__only_one_relevant_mapping(), + # boundary annotation + migrate_boundary_annotation_data_one_to_one_mapping(), + migrate_boundary_annotation_data_one_to_one_mapping_int_to_float(), + migrate_boundary_annotation_data_one_to_one_mapping_float_to_int(), + migrate_boundary_annotation_data_one_to_many_mapping(), + # rename annotation migrate_rename_annotation_data_one_to_many_mapping__with_changed_new_name(), migrate_rename_annotation_data_one_to_one_mapping(), migrate_rename_annotation_data_one_to_many_mapping(), + # to-do annotation migrate_todo_annotation_data_one_to_one_mapping(), migrate_todo_annotation_data_one_to_many_mapping(), migrate_todo_annotation_data_many_to_many_mapping(), - migrate_enum_annotation_data_one_to_one_mapping(), - migrate_enum_annotation_data_one_to_many_mapping(), - migrate_enum_annotation_data_one_to_many_mapping__only_one_relevant_mapping(), ]