From 6574e3ddb2df32aad0af41093c776dfe751d8bb2 Mon Sep 17 00:00:00 2001 From: Hendre Janse van Rensburg Date: Fri, 8 Aug 2025 15:29:56 +0200 Subject: [PATCH 1/8] add endpoint to create new mappings --- .../src/structure_comparer/handler/mapping.py | 9 +++ service/src/structure_comparer/serve.py | 57 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/service/src/structure_comparer/handler/mapping.py b/service/src/structure_comparer/handler/mapping.py index 9b9ca65..21e75a5 100644 --- a/service/src/structure_comparer/handler/mapping.py +++ b/service/src/structure_comparer/handler/mapping.py @@ -125,6 +125,15 @@ def set_field( proj.manual_entries.write() return new_entry + + def create_new(self, project_key, mapping: MappingBaseModel) -> MappingDetailsModel: + proj = self.project_handler.get(project_key) # Ensure project exists + if proj is not None: + new_mapping = proj.mappings.create(mapping) + new_mapping.fill_action_remark(proj.manual_entries) + proj.config.mappings.append(new_mapping.to_base_model()) + proj.config.write() + return new_mapping.to_details_model() def __get(self, project_key, mapping_id, proj: Project | None = None): if proj is None: diff --git a/service/src/structure_comparer/serve.py b/service/src/structure_comparer/serve.py index 05eb328..8164c86 100644 --- a/service/src/structure_comparer/serve.py +++ b/service/src/structure_comparer/serve.py @@ -993,6 +993,63 @@ async def get_mapping_field( return ErrorModel.from_except(e) +@app.post( + "/project/{project_key}/mapping/", + tags=["Mappings"], + response_model_exclude_unset=True, + response_model_exclude_none=True, + responses={400: {}, 404: {}}, +) +async def post_mapping( + project_key: str, + mapping: MappingBaseModel, + response: Response, +) -> MappingFieldModel | ErrorModel: + """ + Post a new mapping for a project + Creates a new mapping in the project with the given key. + The mapping needs to be a valid MappingBaseModel. + + --- + consumes: + - application/json + parameters: + - in: path + name: project_key + type: string + required: true + description: The key of the project + responses: + 200: + description: The mapping was created + 400: + description: There was something wrong with the request + schema: + properties: + error: + type: string + description: An error message + 404: + description: Project not found + """ + global mapping_handler + try: + return mapping_handler.create_new(project_key, mapping) + + except ProjectNotFound as e: + response.status_code = 404 + return ErrorModel.from_except(e) + + except ( + MappingActionNotAllowed, + MappingTargetMissing, + MappingTargetNotFound, + MappingValueMissing, + ) as e: + response.status_code = 400 + return ErrorModel.from_except(e) + + @app.post( "/mapping/{mapping_id}/field/{field_id}/classification", tags=["Fields"], From b7e629c9fd024af2ae5c54a959138f23002ba60b Mon Sep 17 00:00:00 2001 From: Hendre Janse van Rensburg Date: Mon, 11 Aug 2025 14:14:58 +0200 Subject: [PATCH 2/8] add new mapping models --- service/src/structure_comparer/handler/mapping.py | 4 +++- service/src/structure_comparer/model/mapping.py | 5 +++++ service/src/structure_comparer/serve.py | 9 +++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/service/src/structure_comparer/handler/mapping.py b/service/src/structure_comparer/handler/mapping.py index 21e75a5..f13a011 100644 --- a/service/src/structure_comparer/handler/mapping.py +++ b/service/src/structure_comparer/handler/mapping.py @@ -14,6 +14,7 @@ from ..helpers import get_field_by_name from ..model.manual_entries import ManualEntriesMapping from ..model.mapping import MappingBase as MappingBaseModel +from ..model.mapping import MappingCreate as MappingCreateModel from ..model.mapping import MappingDetails as MappingDetailsModel from ..model.mapping import MappingField as MappingFieldModel from ..model.mapping import MappingFieldBase as MappingFieldBaseModel @@ -126,9 +127,10 @@ def set_field( return new_entry - def create_new(self, project_key, mapping: MappingBaseModel) -> MappingDetailsModel: + def create_new(self, project_key, mapping: MappingCreateModel) -> MappingDetailsModel: proj = self.project_handler.get(project_key) # Ensure project exists if proj is not None: + print(mapping) new_mapping = proj.mappings.create(mapping) new_mapping.fill_action_remark(proj.manual_entries) proj.config.mappings.append(new_mapping.to_base_model()) diff --git a/service/src/structure_comparer/model/mapping.py b/service/src/structure_comparer/model/mapping.py index f01c7b2..657ab1e 100644 --- a/service/src/structure_comparer/model/mapping.py +++ b/service/src/structure_comparer/model/mapping.py @@ -37,6 +37,11 @@ class MappingBase(BaseModel): target: Profile +class MappingCreate(BaseModel): + source_ids: list[str] + target_id: str + + class MappingDetails(MappingBase): fields: list[MappingField] diff --git a/service/src/structure_comparer/serve.py b/service/src/structure_comparer/serve.py index 8164c86..eaeffe3 100644 --- a/service/src/structure_comparer/serve.py +++ b/service/src/structure_comparer/serve.py @@ -36,6 +36,7 @@ from .model.get_mappings_output import GetMappingsOutput from .model.init_project_input import InitProjectInput from .model.mapping import MappingBase as MappingBaseModel +from .model.mapping import MappingCreate as MappingCreateModel from .model.mapping import MappingDetails as MappingDetailsModel from .model.mapping import MappingField as MappingFieldModel from .model.mapping import MappingFieldMinimal as MappingFieldMinimalModel @@ -994,7 +995,7 @@ async def get_mapping_field( @app.post( - "/project/{project_key}/mapping/", + "/project/{project_key}/mapping", tags=["Mappings"], response_model_exclude_unset=True, response_model_exclude_none=True, @@ -1002,7 +1003,7 @@ async def get_mapping_field( ) async def post_mapping( project_key: str, - mapping: MappingBaseModel, + mappingData: MappingCreateModel, response: Response, ) -> MappingFieldModel | ErrorModel: """ @@ -1018,7 +1019,7 @@ async def post_mapping( name: project_key type: string required: true - description: The key of the project + description: The key of the project responses: 200: description: The mapping was created @@ -1034,7 +1035,7 @@ async def post_mapping( """ global mapping_handler try: - return mapping_handler.create_new(project_key, mapping) + return mapping_handler.create_new(project_key, mappingData) except ProjectNotFound as e: response.status_code = 404 From ee9c2ac9afa083fa349b55a9748437dc4958cc71 Mon Sep 17 00:00:00 2001 From: Hendre Janse van Rensburg Date: Tue, 12 Aug 2025 13:14:23 +0200 Subject: [PATCH 3/8] fix errors when creating new mappings --- .../src/structure_comparer/data/profile.py | 19 ++++---- .../src/structure_comparer/data/project.py | 4 +- .../src/structure_comparer/handler/mapping.py | 43 +++++++++++++++---- service/src/structure_comparer/serve.py | 2 +- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/service/src/structure_comparer/data/profile.py b/service/src/structure_comparer/data/profile.py index b954c02..6b6b9c3 100644 --- a/service/src/structure_comparer/data/profile.py +++ b/service/src/structure_comparer/data/profile.py @@ -161,16 +161,15 @@ def must_support(self) -> bool: @property def ref_types(self) -> list[str]: - return ( - [ - p - for t in self.__data.type - if t.code == "Reference" - for p in t.targetProfile - ] - if self.__data.type is not None - else [] - ) + if self.__data.type is None: + return [] + result = [] + for t in self.__data.type: + if t.code == "Reference": + if t.targetProfile is not None: + for p in t.targetProfile: + result.append(p) + return result @property def is_default(self) -> bool: diff --git a/service/src/structure_comparer/data/project.py b/service/src/structure_comparer/data/project.py index f840e42..a10775e 100644 --- a/service/src/structure_comparer/data/project.py +++ b/service/src/structure_comparer/data/project.py @@ -23,7 +23,7 @@ def __init__(self, path: Path): self.__load_packages() self.load_comparisons() - self.__load_mappings() + self.load_mappings() self.__read_manual_entries() def __load_packages(self) -> None: @@ -53,7 +53,7 @@ def load_comparisons(self): c.id: Comparison(c, self).init_ext() for c in self.config.comparisons } - def __load_mappings(self): + def load_mappings(self): self.mappings = { m.id: Mapping(m, self).init_ext() for m in self.config.mappings } diff --git a/service/src/structure_comparer/handler/mapping.py b/service/src/structure_comparer/handler/mapping.py index f13a011..c652356 100644 --- a/service/src/structure_comparer/handler/mapping.py +++ b/service/src/structure_comparer/handler/mapping.py @@ -1,4 +1,6 @@ +from datetime import datetime from typing import List +from uuid import uuid4 from ..action import Action from ..data.mapping import MappingField @@ -20,6 +22,10 @@ from ..model.mapping import MappingFieldBase as MappingFieldBaseModel from ..model.mapping import MappingFieldMinimal as MappingFieldMinimalModel from ..model.mapping import MappingFieldsOutput as MappingFieldsOutputModel +from ..data.config import MappingConfig as MappingConfigModel +from ..data.config import ComparisonProfilesConfig as ComparisonProfilesConfigModel +from ..data.config import ComparisonProfileConfig as ComparisonProfileConfigModel +from ..data.mapping import Mapping as MappingModel from .project import ProjectsHandler @@ -126,17 +132,38 @@ def set_field( proj.manual_entries.write() return new_entry - - def create_new(self, project_key, mapping: MappingCreateModel) -> MappingDetailsModel: - proj = self.project_handler.get(project_key) # Ensure project exists + + def create_new( + self, project_key, mapping: MappingCreateModel + ) -> MappingDetailsModel: + proj = self.project_handler._get(project_key) # Ensure project exists if proj is not None: - print(mapping) - new_mapping = proj.mappings.create(mapping) - new_mapping.fill_action_remark(proj.manual_entries) - proj.config.mappings.append(new_mapping.to_base_model()) - proj.config.write() + # new_mapping = proj.mappings.append(mapping) + new_mappingConfig = MappingConfigModel( + id=str(uuid4()), + version="1.0", + last_updated=datetime.now().isoformat(), + status="active", + mappings=ComparisonProfilesConfigModel( + sourceprofiles=[ + self._to_profiles_config(id) for id in mapping.source_ids + ], + targetprofile=self._to_profiles_config(mapping.target_id), + ), + ) + new_mapping = MappingModel(new_mappingConfig, proj) + new_mapping.init_ext() + # new_mapping.fill_action_remark(proj.manual_entries) + proj.config.mappings.append(new_mappingConfig) + # proj.config.write() + proj.write_config() + proj.load_mappings() return new_mapping.to_details_model() + def _to_profiles_config(self, url: str) -> ComparisonProfileConfigModel: + url, version = url.split("|") + return ComparisonProfileConfigModel(url=url, version=version) + def __get(self, project_key, mapping_id, proj: Project | None = None): if proj is None: proj = self.project_handler._get(project_key) diff --git a/service/src/structure_comparer/serve.py b/service/src/structure_comparer/serve.py index eaeffe3..5f3568c 100644 --- a/service/src/structure_comparer/serve.py +++ b/service/src/structure_comparer/serve.py @@ -1005,7 +1005,7 @@ async def post_mapping( project_key: str, mappingData: MappingCreateModel, response: Response, -) -> MappingFieldModel | ErrorModel: +) -> MappingDetailsModel | ErrorModel: """ Post a new mapping for a project Creates a new mapping in the project with the given key. From 4a0e1b0f882896153aeba7bc93773c8e87b6e5df Mon Sep 17 00:00:00 2001 From: Hendre Janse van Rensburg Date: Tue, 12 Aug 2025 15:45:03 +0200 Subject: [PATCH 4/8] fix a bug when updating a mapping --- service/src/structure_comparer/data/mapping.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/service/src/structure_comparer/data/mapping.py b/service/src/structure_comparer/data/mapping.py index c1fc229..9d6ab4c 100644 --- a/service/src/structure_comparer/data/mapping.py +++ b/service/src/structure_comparer/data/mapping.py @@ -113,7 +113,10 @@ def classify_remark_field( if self.action in EXTRA_ACTIONS: # Cut away the common part with the parent and add the remainder to the parent's extra - self.other = parent_update.other + self.name[len(self.name_parent) :] + if parent_update.other is not None: + self.other = parent_update.other + self.name[len(self.name_parent):] + else: + self.other = self.name[len(self.name_parent):] self.remark = REMARKS[self.action].format(self.other) # Else use the parent's remark From 35b9122d61daa73764fe9d151a5e9b51cc0f205d Mon Sep 17 00:00:00 2001 From: Hendre Janse van Rensburg Date: Tue, 12 Aug 2025 15:47:15 +0200 Subject: [PATCH 5/8] fix formatting --- service/src/structure_comparer/data/mapping.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/service/src/structure_comparer/data/mapping.py b/service/src/structure_comparer/data/mapping.py index 9d6ab4c..6f04797 100644 --- a/service/src/structure_comparer/data/mapping.py +++ b/service/src/structure_comparer/data/mapping.py @@ -114,9 +114,11 @@ def classify_remark_field( # Cut away the common part with the parent and add the remainder to the parent's extra if parent_update.other is not None: - self.other = parent_update.other + self.name[len(self.name_parent):] + self.other = ( + parent_update.other + self.name[len(self.name_parent) :] + ) else: - self.other = self.name[len(self.name_parent):] + self.other = self.name[len(self.name_parent) :] self.remark = REMARKS[self.action].format(self.other) # Else use the parent's remark From 2cd8ca48435fc1bde70b3a1d5defa96af762924b Mon Sep 17 00:00:00 2001 From: Hendre Janse van Rensburg Date: Fri, 5 Sep 2025 14:07:33 +0200 Subject: [PATCH 6/8] make suggested changes --- .../src/structure_comparer/data/profile.py | 19 ++++---- .../src/structure_comparer/handler/mapping.py | 46 ++++++++++--------- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/service/src/structure_comparer/data/profile.py b/service/src/structure_comparer/data/profile.py index 6b6b9c3..a4c578a 100644 --- a/service/src/structure_comparer/data/profile.py +++ b/service/src/structure_comparer/data/profile.py @@ -161,15 +161,16 @@ def must_support(self) -> bool: @property def ref_types(self) -> list[str]: - if self.__data.type is None: - return [] - result = [] - for t in self.__data.type: - if t.code == "Reference": - if t.targetProfile is not None: - for p in t.targetProfile: - result.append(p) - return result + return ( + [ + p + for t in self.__data.type + if t.code == "Reference" and t.targetProfile is not None + for p in t.targetProfile + ] + if self.__data.type is not None + else [] + ) @property def is_default(self) -> bool: diff --git a/service/src/structure_comparer/handler/mapping.py b/service/src/structure_comparer/handler/mapping.py index c652356..0228131 100644 --- a/service/src/structure_comparer/handler/mapping.py +++ b/service/src/structure_comparer/handler/mapping.py @@ -137,28 +137,30 @@ def create_new( self, project_key, mapping: MappingCreateModel ) -> MappingDetailsModel: proj = self.project_handler._get(project_key) # Ensure project exists - if proj is not None: - # new_mapping = proj.mappings.append(mapping) - new_mappingConfig = MappingConfigModel( - id=str(uuid4()), - version="1.0", - last_updated=datetime.now().isoformat(), - status="active", - mappings=ComparisonProfilesConfigModel( - sourceprofiles=[ - self._to_profiles_config(id) for id in mapping.source_ids - ], - targetprofile=self._to_profiles_config(mapping.target_id), - ), - ) - new_mapping = MappingModel(new_mappingConfig, proj) - new_mapping.init_ext() - # new_mapping.fill_action_remark(proj.manual_entries) - proj.config.mappings.append(new_mappingConfig) - # proj.config.write() - proj.write_config() - proj.load_mappings() - return new_mapping.to_details_model() + if proj is None: + raise ProjectNotFound() + + # new_mapping = proj.mappings.append(mapping) + new_mappingConfig = MappingConfigModel( + id=str(uuid4()), + version="1.0", + last_updated=datetime.now().isoformat(), + status="active", + mappings=ComparisonProfilesConfigModel( + sourceprofiles=[ + self._to_profiles_config(id) for id in mapping.source_ids + ], + targetprofile=self._to_profiles_config(mapping.target_id), + ), + ) + new_mapping = MappingModel(new_mappingConfig, proj) + new_mapping.init_ext() + # new_mapping.fill_action_remark(proj.manual_entries) + proj.config.mappings.append(new_mappingConfig) + # proj.config.write() + proj.write_config() + proj.load_mappings() + return new_mapping.to_details_model() def _to_profiles_config(self, url: str) -> ComparisonProfileConfigModel: url, version = url.split("|") From b449a498bdcbe2fc2bfec523ea6380a9a06049ed Mon Sep 17 00:00:00 2001 From: Hendre Janse van Rensburg Date: Fri, 5 Sep 2025 14:10:36 +0200 Subject: [PATCH 7/8] fix formatting --- service/src/structure_comparer/handler/mapping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/src/structure_comparer/handler/mapping.py b/service/src/structure_comparer/handler/mapping.py index 0228131..a1a95bc 100644 --- a/service/src/structure_comparer/handler/mapping.py +++ b/service/src/structure_comparer/handler/mapping.py @@ -139,7 +139,7 @@ def create_new( proj = self.project_handler._get(project_key) # Ensure project exists if proj is None: raise ProjectNotFound() - + # new_mapping = proj.mappings.append(mapping) new_mappingConfig = MappingConfigModel( id=str(uuid4()), From 3b74d6eb78b33bb26edd76910685472e29fcdcc3 Mon Sep 17 00:00:00 2001 From: Hendre Janse van Rensburg Date: Fri, 17 Oct 2025 15:20:09 +0200 Subject: [PATCH 8/8] clean up code and fix up mapping_handler.create_new method --- service/src/structure_comparer/data/mapping.py | 2 +- service/src/structure_comparer/handler/mapping.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/service/src/structure_comparer/data/mapping.py b/service/src/structure_comparer/data/mapping.py index 6f04797..f29d5e0 100644 --- a/service/src/structure_comparer/data/mapping.py +++ b/service/src/structure_comparer/data/mapping.py @@ -118,7 +118,7 @@ def classify_remark_field( parent_update.other + self.name[len(self.name_parent) :] ) else: - self.other = self.name[len(self.name_parent) :] + raise ValueError("Error with the data: parent_update.other is None") self.remark = REMARKS[self.action].format(self.other) # Else use the parent's remark diff --git a/service/src/structure_comparer/handler/mapping.py b/service/src/structure_comparer/handler/mapping.py index a1a95bc..b27522a 100644 --- a/service/src/structure_comparer/handler/mapping.py +++ b/service/src/structure_comparer/handler/mapping.py @@ -136,11 +136,10 @@ def set_field( def create_new( self, project_key, mapping: MappingCreateModel ) -> MappingDetailsModel: - proj = self.project_handler._get(project_key) # Ensure project exists + proj = self.project_handler._get(project_key) if proj is None: raise ProjectNotFound() - # new_mapping = proj.mappings.append(mapping) new_mappingConfig = MappingConfigModel( id=str(uuid4()), version="1.0", @@ -154,13 +153,14 @@ def create_new( ), ) new_mapping = MappingModel(new_mappingConfig, proj) - new_mapping.init_ext() - # new_mapping.fill_action_remark(proj.manual_entries) + new_mapping.fill_action_remark(proj.manual_entries) + proj.config.mappings.append(new_mappingConfig) - # proj.config.write() proj.write_config() proj.load_mappings() - return new_mapping.to_details_model() + + mapping = proj.mappings.get(new_mapping.id) + return mapping.to_details_model() def _to_profiles_config(self, url: str) -> ComparisonProfileConfigModel: url, version = url.split("|")