From b36359fd621f97825e49c3378aa1b6f77b62617e Mon Sep 17 00:00:00 2001 From: Ensar Emir EROL Date: Fri, 13 Dec 2024 23:31:32 +0100 Subject: [PATCH] added mapping facades --- server/facades/workspace/mapping/__init__.py | 0 .../create_mapping_in_workspace_facade.py | 98 ++++++++++++++ .../delete_mapping_from_workspace_facade.py | 111 +++++++++++++++ .../get_mappings_in_workspace_facade.py | 70 ++++++++++ .../mapping/update_mapping_facade.py | 38 ++++++ server/models/mapping.py | 12 ++ server/routers/workspaces/models.py | 9 ++ server/routers/workspaces/workspaces.py | 128 ++++++++++++++++++ .../source_service_protocol/__init__.py | 5 +- server/services/__init__.py | 4 + 10 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 server/facades/workspace/mapping/__init__.py create mode 100644 server/facades/workspace/mapping/create_mapping_in_workspace_facade.py create mode 100644 server/facades/workspace/mapping/delete_mapping_from_workspace_facade.py create mode 100644 server/facades/workspace/mapping/get_mappings_in_workspace_facade.py create mode 100644 server/facades/workspace/mapping/update_mapping_facade.py diff --git a/server/facades/workspace/mapping/__init__.py b/server/facades/workspace/mapping/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/facades/workspace/mapping/create_mapping_in_workspace_facade.py b/server/facades/workspace/mapping/create_mapping_in_workspace_facade.py new file mode 100644 index 0000000..795b7cf --- /dev/null +++ b/server/facades/workspace/mapping/create_mapping_in_workspace_facade.py @@ -0,0 +1,98 @@ +from uuid import uuid4 + +from kink import inject + +from server.facades import ( + BaseFacade, + FacadeResponse, +) +from server.models.mapping import MappingGraph +from server.models.source import SourceType +from server.service_protocols.mapping_service_protocol import ( + MappingServiceProtocol, +) +from server.services.core.workspace_metadata_service import ( + WorkspaceMetadataServiceProtocol, +) +from server.services.local.local_source_service import ( + SourceServiceProtocol, +) +from server.services.local.local_workspace_service import ( + WorkspaceServiceProtocol, +) + + +@inject +class CreateMappingInWorkspaceFacade(BaseFacade): + def __init__( + self, + workspace_metadata_service: WorkspaceMetadataServiceProtocol, + workspace_service: WorkspaceServiceProtocol, + mapping_service: MappingServiceProtocol, + source_service: SourceServiceProtocol, + ): + super().__init__() + self.workspace_metadata_service: WorkspaceMetadataServiceProtocol = workspace_metadata_service + self.workspace_service: WorkspaceServiceProtocol = ( + workspace_service + ) + self.mapping_service: MappingServiceProtocol = ( + mapping_service + ) + self.source_service: SourceServiceProtocol = ( + source_service + ) + + @BaseFacade.error_wrapper + def execute( + self, + workspace_id: str, + name: str, + description: str, + source_content: bytes, + source_type: SourceType, + extra: dict, + ) -> FacadeResponse: + self.logger.info( + f"Creating mapping in workspace {workspace_id} with name {name}" + ) + self.logger.info( + f"Retrieving workspace {workspace_id}" + ) + workspace_metadata = self.workspace_metadata_service.get_workspace_metadata( + workspace_id, + ) + workspace = self.workspace_service.get_workspace( + workspace_metadata.location, + ) + self.logger.info("Creating source") + source = self.source_service.create_source( + type=source_type, + content=source_content, + extra=extra, + ) + self.logger.info("Creating mapping") + mapping_graph_uuid = uuid4().hex + mapping_graph = MappingGraph( + uuid=mapping_graph_uuid, + name=name, + description=description, + source_id=source, + nodes=[], + edges=[], + ) + self.mapping_service.create_mapping(mapping_graph) + self.logger.info("Mapping created") + workspace.copy_with( + mappings=workspace.mappings + + [mapping_graph_uuid], + ) + self.workspace_service.update_workspace( + workspace, + ) + self.logger.info("Workspace updated") + return FacadeResponse( + status=202, + message="Mapping created", + data=mapping_graph, + ) diff --git a/server/facades/workspace/mapping/delete_mapping_from_workspace_facade.py b/server/facades/workspace/mapping/delete_mapping_from_workspace_facade.py new file mode 100644 index 0000000..96559e4 --- /dev/null +++ b/server/facades/workspace/mapping/delete_mapping_from_workspace_facade.py @@ -0,0 +1,111 @@ +from kink import inject + +from server.exceptions import ErrCodes +from server.facades import ( + BaseFacade, + FacadeResponse, + ServerException, +) +from server.service_protocols.mapping_service_protocol import ( + MappingServiceProtocol, +) +from server.services.core.workspace_metadata_service import ( + WorkspaceMetadataServiceProtocol, +) +from server.services.local.local_source_service import ( + SourceServiceProtocol, +) +from server.services.local.local_workspace_service import ( + WorkspaceServiceProtocol, +) + + +@inject +class DeleteMappingFromWorkspaceFacade(BaseFacade): + def __init__( + self, + workspace_metadata_service: WorkspaceMetadataServiceProtocol, + workspace_service: WorkspaceServiceProtocol, + mapping_service: MappingServiceProtocol, + source_service: SourceServiceProtocol, + ): + super().__init__() + self.workspace_metadata_service: WorkspaceMetadataServiceProtocol = workspace_metadata_service + self.workspace_service: WorkspaceServiceProtocol = ( + workspace_service + ) + self.mapping_service: MappingServiceProtocol = ( + mapping_service + ) + self.source_service: SourceServiceProtocol = ( + source_service + ) + + @BaseFacade.error_wrapper + def execute( + self, + workspace_id: str, + mapping_id: str, + ) -> FacadeResponse: + self.logger.info( + f"Dropping mapping {mapping_id} from workspace {workspace_id}" + ) + self.logger.info( + f"Retrieving workspace {workspace_id}" + ) + workspace_metadata = self.workspace_metadata_service.get_workspace_metadata( + workspace_id, + ) + workspace = self.workspace_service.get_workspace( + workspace_metadata.location, + ) + + if mapping_id not in workspace.mappings: + self.logger.error( + f"Mapping {mapping_id} not found in workspace {workspace_id}" + ) + raise ServerException( + f"Mapping {mapping_id} not found in workspace {workspace_id}", + code=ErrCodes.MAPPING_NOT_FOUND, + ) + + self.logger.info(f"Retrieving mapping {mapping_id}") + + mapping = self.mapping_service.get_mapping( + mapping_id, + ) + + self.logger.info( + f"Deleting source {mapping.source_id}" + ) + + self.source_service.delete_source( + mapping.source_id, + ) + + self.logger.info(f"Deleting mapping {mapping_id}") + + self.mapping_service.delete_mapping( + mapping_id, + ) + + self.logger.info( + f"Removing mapping {mapping_id} from workspace {workspace_id}" + ) + + new_model = workspace.copy_with( + mappings=[ + m + for m in workspace.mappings + if m != mapping_id + ], + ) + + self.workspace_service.update_workspace( + new_model, + ) + + return FacadeResponse( + status=200, + message=f"Mapping {mapping_id} dropped from workspace {workspace_id}", + ) diff --git a/server/facades/workspace/mapping/get_mappings_in_workspace_facade.py b/server/facades/workspace/mapping/get_mappings_in_workspace_facade.py new file mode 100644 index 0000000..cb8bde5 --- /dev/null +++ b/server/facades/workspace/mapping/get_mappings_in_workspace_facade.py @@ -0,0 +1,70 @@ +from kink import inject + +from server.facades import ( + BaseFacade, + FacadeResponse, +) +from server.service_protocols.mapping_service_protocol import ( + MappingServiceProtocol, +) +from server.services.core.workspace_metadata_service import ( + WorkspaceMetadataServiceProtocol, +) +from server.services.local.local_source_service import ( + SourceServiceProtocol, +) +from server.services.local.local_workspace_service import ( + WorkspaceServiceProtocol, +) + + +@inject +class GetMappingsInWorkspaceFacade(BaseFacade): + def __init__( + self, + workspace_metadata_service: WorkspaceMetadataServiceProtocol, + workspace_service: WorkspaceServiceProtocol, + mapping_service: MappingServiceProtocol, + source_service: SourceServiceProtocol, + ): + super().__init__() + self.workspace_metadata_service: WorkspaceMetadataServiceProtocol = workspace_metadata_service + self.workspace_service: WorkspaceServiceProtocol = ( + workspace_service + ) + self.mapping_service: MappingServiceProtocol = ( + mapping_service + ) + self.source_service: SourceServiceProtocol = ( + source_service + ) + + @BaseFacade.error_wrapper + def execute( + self, + workspace_id: str, + ) -> FacadeResponse: + self.logger.info( + f"Retrieving mappings in workspace {workspace_id}" + ) + self.logger.info( + f"Retrieving workspace {workspace_id}" + ) + workspace_metadata = self.workspace_metadata_service.get_workspace_metadata( + workspace_id, + ) + workspace = self.workspace_service.get_workspace( + workspace_metadata.location, + ) + self.logger.info("Retrieving mappings") + + mappings = [ + self.mapping_service.get_mapping(mapping_id) + for mapping_id in workspace.mappings + ] + + return FacadeResponse( + status=200, + message=f"Retrieved mappings in workspace {workspace_id}", + data=mappings, + ) diff --git a/server/facades/workspace/mapping/update_mapping_facade.py b/server/facades/workspace/mapping/update_mapping_facade.py new file mode 100644 index 0000000..390f74c --- /dev/null +++ b/server/facades/workspace/mapping/update_mapping_facade.py @@ -0,0 +1,38 @@ +from kink import inject + +from server.facades import ( + BaseFacade, + FacadeResponse, +) +from server.models.mapping import MappingGraph +from server.service_protocols.mapping_service_protocol import ( + MappingServiceProtocol, +) + + +@inject +class UpdateMappingFacade(BaseFacade): + def __init__( + self, + mapping_service: MappingServiceProtocol, + ): + super().__init__() + self.mapping_service: MappingServiceProtocol = ( + mapping_service + ) + + @BaseFacade.error_wrapper + def execute( + self, + mapping_id: str, + mapping_graph: MappingGraph, + ) -> FacadeResponse: + self.logger.info(f"Updating mapping {mapping_id}") + self.mapping_service.update_mapping( + mapping_id, + mapping_graph, + ) + return FacadeResponse( + status=200, + message=f"Mapping {mapping_id} updated", + ) diff --git a/server/models/mapping.py b/server/models/mapping.py index 7f458ab..d6bef44 100644 --- a/server/models/mapping.py +++ b/server/models/mapping.py @@ -193,11 +193,15 @@ class MappingGraph: Attributes: uuid (str): The UUID of the graph + name (str): The name of the graph + description (str): The description of the graph nodes (list[MappingNode]): The nodes in the graph edges (list[MappingEdge]): The edges in the graph """ uuid: str + name: str + description: str source_id: str nodes: list[ MappingNode | MappingLiteral | MappingURIRef @@ -207,6 +211,8 @@ class MappingGraph: def to_dict(self): return { "uuid": self.uuid, + "name": self.name, + "description": self.description, "source_id": self.source_id, "nodes": [ node.to_dict() for node in self.nodes @@ -220,6 +226,10 @@ def to_dict(self): def from_dict(cls, data): if "uuid" not in data: raise ValueError("uuid is required") + if "name" not in data: + raise ValueError("name is required") + if "description" not in data: + raise ValueError("description is required") if "source_id" not in data: raise ValueError("source_id is required") if "nodes" not in data: @@ -228,6 +238,8 @@ def from_dict(cls, data): raise ValueError("edges is required") return cls( uuid=data["uuid"], + name=data["name"], + description=data["description"], source_id=data["source_id"], nodes=[ MappingNode.from_dict(node) diff --git a/server/routers/workspaces/models.py b/server/routers/workspaces/models.py index 163efaa..404ea57 100644 --- a/server/routers/workspaces/models.py +++ b/server/routers/workspaces/models.py @@ -4,6 +4,7 @@ HttpUrl, ) +from server.models.source import SourceType from server.services.core.sqlite_db_service.tables.workspace_metadata import ( WorkspaceType, ) @@ -31,3 +32,11 @@ class CreateOntologyInput(BaseModel): description: str base_uri: HttpUrl content: Base64UrlStr + + +class CreateMappingInput(BaseModel): + name: str + description: str + content: Base64UrlStr + source_type: SourceType + extra: dict = {} diff --git a/server/routers/workspaces/workspaces.py b/server/routers/workspaces/workspaces.py index 8772814..33d16ca 100644 --- a/server/routers/workspaces/workspaces.py +++ b/server/routers/workspaces/workspaces.py @@ -15,6 +15,18 @@ from server.facades.workspace.get_workspaces_facade import ( GetWorkspacesFacade, ) +from server.facades.workspace.mapping.create_mapping_in_workspace_facade import ( + CreateMappingInWorkspaceFacade, +) +from server.facades.workspace.mapping.delete_mapping_from_workspace_facade import ( + DeleteMappingFromWorkspaceFacade, +) +from server.facades.workspace.mapping.get_mappings_in_workspace_facade import ( + GetMappingsInWorkspaceFacade, +) +from server.facades.workspace.mapping.update_mapping_facade import ( + UpdateMappingFacade, +) from server.facades.workspace.ontology.create_ontology_in_workspace_facade import ( CreateOntologyInWorkspaceFacade, ) @@ -33,9 +45,11 @@ from server.facades.workspace.prefix.get_prefixes_in_workspace_facade import ( GetPrefixInWorkspaceFacade, ) +from server.models.mapping import MappingGraph from server.models.workspace import WorkspaceModel from server.routers.models import BasicResponse from server.routers.workspaces.models import ( + CreateMappingInput, CreateOntologyInput, CreatePrefixInput, CreateWorkspaceInput, @@ -89,6 +103,26 @@ Depends(lambda: di[DeleteOntologyFromWorkspaceFacade]), ] +CreateMappingInWorkspaceDep = Annotated[ + CreateMappingInWorkspaceFacade, + Depends(lambda: di[CreateMappingInWorkspaceFacade]), +] + +DeleteMappingFromWorkspaceDep = Annotated[ + DeleteMappingFromWorkspaceFacade, + Depends(lambda: di[DeleteMappingFromWorkspaceFacade]), +] + +GetMappingsInWorkspaceDep = Annotated[ + GetMappingsInWorkspaceFacade, + Depends(lambda: di[GetMappingsInWorkspaceFacade]), +] + +UpdateMappingDep = Annotated[ + UpdateMappingFacade, + Depends(lambda: di[UpdateMappingFacade]), +] + @router.get("/") async def get_workspaces( @@ -324,3 +358,97 @@ async def delete_ontology( status_code=facade_response.status, detail=facade_response.to_dict(), ) + + +@router.get("/{workspace_id}/mapping") +async def get_mappings( + workspace_id: str, + get_mappings_in_workspace_facade: GetMappingsInWorkspaceDep, +) -> list[MappingGraph]: + facade_response = ( + get_mappings_in_workspace_facade.execute( + workspace_id=workspace_id, + ) + ) + + if facade_response.status // 100 == 2: + return facade_response.data or [] + + raise HTTPException( + status_code=facade_response.status, + detail=facade_response.to_dict(), + ) + + +@router.post("/{workspace_id}/mapping", status_code=201) +async def create_mapping( + workspace_id: str, + data: CreateMappingInput, + create_mapping_in_workspace_facade: CreateMappingInWorkspaceDep, +) -> BasicResponse: + facade_response = ( + create_mapping_in_workspace_facade.execute( + workspace_id=workspace_id, + name=data.name, + description=data.description, + source_content=data.content.encode(), + source_type=data.source_type, + extra=data.extra, + ) + ) + + if facade_response.status // 100 == 2: + return BasicResponse( + message=facade_response.message, + ) + + raise HTTPException( + status_code=facade_response.status, + detail=facade_response.to_dict(), + ) + + +@router.delete("/{workspace_id}/mapping/{mapping_id}") +async def delete_mapping( + workspace_id: str, + mapping_id: str, + delete_mapping_from_workspace_facade: DeleteMappingFromWorkspaceDep, +) -> BasicResponse: + facade_response = ( + delete_mapping_from_workspace_facade.execute( + workspace_id=workspace_id, + mapping_id=mapping_id, + ) + ) + + if facade_response.status // 100 == 2: + return BasicResponse( + message=facade_response.message, + ) + + raise HTTPException( + status_code=facade_response.status, + detail=facade_response.to_dict(), + ) + + +@router.put("/{workspace_id}/mapping/{mapping_id}") +async def update_mapping( + mapping_id: str, + data: MappingGraph, + update_mapping_facade: UpdateMappingDep, +) -> BasicResponse: + facade_response = update_mapping_facade.execute( + mapping_id=mapping_id, + mapping_graph=data, + ) + + if facade_response.status // 100 == 2: + return BasicResponse( + message=facade_response.message, + ) + + raise HTTPException( + status_code=facade_response.status, + detail=facade_response.to_dict(), + ) diff --git a/server/service_protocols/source_service_protocol/__init__.py b/server/service_protocols/source_service_protocol/__init__.py index 280b756..626052a 100644 --- a/server/service_protocols/source_service_protocol/__init__.py +++ b/server/service_protocols/source_service_protocol/__init__.py @@ -31,7 +31,10 @@ def download_source(self, source_id: str) -> bytes: @abstractmethod def create_source( - self, type: SourceType, content: bytes + self, + type: SourceType, + content: bytes, + extra: dict = {}, ) -> str: """ Create a new source diff --git a/server/services/__init__.py b/server/services/__init__.py index 4496fe1..e3891a8 100644 --- a/server/services/__init__.py +++ b/server/services/__init__.py @@ -8,6 +8,9 @@ from server.services.local.local_fs_service import ( LocalFSService, ) +from server.services.local.local_mapping_service import ( + LocalMappingService, +) from server.services.local.local_ontology_service import ( LocalOntologyService, ) @@ -26,4 +29,5 @@ "LocalFSService", "LocalOntologyService", "LocalSourceService", + "LocalMappingService", ]