diff --git a/app/db/models/__init__.py b/app/db/models/__init__.py index 7b68cfcde95f352654a63c30236dec3320b304a7..47e5579d16d0dedc795ba2c61361fe78cdd069f5 100644 --- a/app/db/models/__init__.py +++ b/app/db/models/__init__.py @@ -1,3 +1,4 @@ -from .tracked_mitm_dataset import BaseTrackedMitMDataset, AddTrackedMitMDataset, TrackedMitMDataset from .common import FromPydanticModelsMixin, APPLICATION_DB_SCHEMA -from .presentation import ListTrackedMitMDataset \ No newline at end of file +from .tracked_mitm_dataset import BaseTrackedMitMDataset, AddTrackedMitMDataset, TrackedMitMDataset, ExternalTrackedMitMDataset +from .mapped_sources import MappedDB, MappedDBSource, MappedDBPull +from .presentation import ListTrackedMitMDataset diff --git a/app/db/models/common.py b/app/db/models/common.py index f01fa7b609c77dcf25f9598ba2a09c04fd85ff54..34ff6efa1da1de52f12c85d92bfc8d3e3b556a91 100644 --- a/app/db/models/common.py +++ b/app/db/models/common.py @@ -13,6 +13,6 @@ class FromPydanticModelsMixin: def from_models(cls, *base_objs: pydantic.BaseModel, **kwargs) -> Self: const_kwargs = {} for base_obj in base_objs: - const_kwargs |= base_obj.model_dump(round_trip=True) + const_kwargs |= base_obj.__dict__ const_kwargs |= kwargs return cls(**const_kwargs) diff --git a/app/db/models/mapped_sources.py b/app/db/models/mapped_sources.py index b39ce6970ebfc8f916673745f4b78eb1018795ee..40d6107b64df3e9d6d1e0cd9596b085296d9940f 100644 --- a/app/db/models/mapped_sources.py +++ b/app/db/models/mapped_sources.py @@ -1,16 +1,23 @@ +from __future__ import annotations + import uuid +from datetime import datetime from uuid import UUID import pydantic +import sqlmodel from mitm_tooling.definition import MITM from mitm_tooling.extraction.sql.data_models import CompiledVirtualView +from mitm_tooling.extraction.sql.data_models.db_meta import DBMetaInfoBase +from mitm_tooling.extraction.sql.data_models.db_probe import DBProbeBase from mitm_tooling.extraction.sql.mapping import ConceptMapping -from pydantic import BaseModel +from mitm_tooling.representation import Header +from pydantic import BaseModel, AnyUrl from sqlalchemy.orm import relationship from sqlmodel import Field, SQLModel from .common import APPLICATION_DB_SCHEMA -from ..adapters import PydanticType +from ..adapters import PydanticType, StrType class MappedDB(BaseModel): @@ -23,12 +30,56 @@ class MappedDB(BaseModel): return [cm for cm in self.mappings if cm.mitm == self.mitm] -class MappedMitMDatasetSources(SQLModel, table=True): +class DBInfo(BaseModel): + db_meta: DBMetaInfoBase + db_probe: DBProbeBase + + +class MappedDBPull(SQLModel, table=True): model_config = pydantic.ConfigDict(arbitrary_types_allowed=True) - __tablename__ = 'mapped_mitmdataset_sources' + __tablename__ = 'mapped_db_pulls' + __table_args__ = {'schema': APPLICATION_DB_SCHEMA} + + id: int = Field(primary_key=True, sa_column_kwargs={'autoincrement': True}) + tracked_mitm_dataset_id: int = Field(nullable=False, + foreign_key=f'{APPLICATION_DB_SCHEMA}.tracked_mitm_datasets.id') + mapped_db_source_id: int = Field(nullable=False, foreign_key=f'{APPLICATION_DB_SCHEMA}.mapped_db_sources.id') + + time: datetime = Field(sa_type=sqlmodel.DateTime, default_factory=datetime.now) + instances_imported: int = Field(default=0) + rows_created: int = Field(default=0) + + @property + def tracked_mitm_dataset(self) -> 'ExternalTrackedMitMDataset': + return relationship('ExternalTrackedMitMDataset', foreign_keys='tracked_mitm_dataset_id', lazy='joined') + + @property + def mapped_db_source(self) -> MappedDBSource: + return relationship('MappedDBSource', foreign_keys='mapped_db_source_id', lazy='joined') + + +class MappedDBSource(SQLModel, table=True): + model_config = pydantic.ConfigDict(arbitrary_types_allowed=True) + __tablename__ = 'mapped_db_sources' __table_args__ = {'schema': APPLICATION_DB_SCHEMA} id: int = Field(primary_key=True, sa_column_kwargs={'autoincrement': True}) uuid: UUID = Field(default_factory=uuid.uuid4, index=True, unique=True, nullable=False) + sql_alchemy_uri: AnyUrl = Field(sa_type=StrType.wrap(AnyUrl)) mitm_mapping: MappedDB = Field(sa_type=PydanticType.wrap(MappedDB), repr=False) + mitm_header: Header = Field(sa_type=PydanticType.wrap(Header), repr=False) + @property + def pulls(self) -> list[MappedDBPull]: + return relationship('MappedDBPull') + + # @hybridproperty + @property + def last_pulled(self) -> datetime | None: + from sqlalchemy import select, func + stmt = select(func.max(MappedDBPull.time)).where(MappedDBPull.mapped_db_source_id == self.id) + return stmt.scalar_subquery() + + @property + def tracked_mitm_datasets(self) -> list['ExternalTrackedMitMDataset']: + return relationship('ExternalTrackedMitMDataset', back_populates='mapped_db_source') diff --git a/app/db/models/tracked_mitm_dataset.py b/app/db/models/tracked_mitm_dataset.py index c79c59da80e1870a8680e6f80bff1225eb7ed54d..dba89717856e6feb48cd5531d06e5e7cd7bd95bc 100644 --- a/app/db/models/tracked_mitm_dataset.py +++ b/app/db/models/tracked_mitm_dataset.py @@ -1,22 +1,27 @@ +from __future__ import annotations + import uuid from abc import ABC from datetime import datetime +from typing import TYPE_CHECKING from uuid import UUID import pydantic import sqlmodel from mitm_tooling.definition import MITM - from mitm_tooling.representation import Header, SQLRepresentationSchema, mk_sql_rep_schema from mitm_tooling.transformation.superset.asset_bundles import MitMDatasetIdentifierBundle, DatasourceIdentifierBundle from mitm_tooling.transformation.superset.common import DBConnectionInfo from mitm_tooling.transformation.superset.definitions import MitMDatasetIdentifier from pydantic import BaseModel, AnyUrl +from sqlalchemy.orm import relationship from sqlmodel import SQLModel, Field from app.db.adapters import StrType, PydanticType from .common import FromPydanticModelsMixin, APPLICATION_DB_SCHEMA +if TYPE_CHECKING: + from .mapped_sources import MappedDBSource, MappedDBPull class BaseTrackedMitMDataset(BaseModel, ABC): @@ -31,21 +36,26 @@ class AddTrackedMitMDataset(BaseModel): sql_alchemy_uri: AnyUrl mitm_header: Header + class TrackedMitMDataset(FromPydanticModelsMixin, AddTrackedMitMDataset, BaseTrackedMitMDataset, SQLModel, table=True): model_config = pydantic.ConfigDict(arbitrary_types_allowed=True) __tablename__ = 'tracked_mitm_datasets' __table_args__ = {'schema': APPLICATION_DB_SCHEMA} + __mapper_args__ = {'polymorphic_on': 'type', 'polymorphic_identity': 'local'} id: int = Field(primary_key=True, sa_column_kwargs={'autoincrement': True}) uuid: UUID = Field(default_factory=uuid.uuid4, index=True, unique=True, nullable=False) + type: str = Field(default='local', nullable=False) + dataset_name: str = Field() schema_name: str = Field() sql_alchemy_uri: AnyUrl = Field(sa_type=StrType.wrap(AnyUrl)) + is_managed_locally: bool = Field(default=True) last_edited: datetime = Field(sa_type=sqlmodel.DateTime, default_factory=datetime.now) - mitm_header: Header = Field(sa_type=PydanticType.wrap(Header), repr=False) - identifier_bundle: MitMDatasetIdentifierBundle = Field(sa_type=PydanticType.wrap(MitMDatasetIdentifierBundle), repr=False) + identifier_bundle: MitMDatasetIdentifierBundle = Field(sa_type=PydanticType.wrap(MitMDatasetIdentifierBundle), + repr=False) @property def identifier(self) -> MitMDatasetIdentifier: @@ -54,7 +64,8 @@ class TrackedMitMDataset(FromPydanticModelsMixin, AddTrackedMitMDataset, BaseTra @property def datasource_identifiers(self) -> DatasourceIdentifierBundle: # we explicitly do not want to use the identifier_bundle itself directly, as that includes the visualization identifier map - return DatasourceIdentifierBundle(database=self.identifier_bundle.database, ds_id_map=self.identifier_bundle.ds_id_map) + return DatasourceIdentifierBundle(database=self.identifier_bundle.database, + ds_id_map=self.identifier_bundle.ds_id_map) @property def db_conn_info(self) -> DBConnectionInfo: @@ -64,7 +75,39 @@ class TrackedMitMDataset(FromPydanticModelsMixin, AddTrackedMitMDataset, BaseTra def sql_rep_schema(self) -> SQLRepresentationSchema: return mk_sql_rep_schema(self.mitm_header, override_schema=self.schema_name) - #@pydantic.computed_field() + # @pydantic.computed_field() @property def mitm(self) -> MITM: return self.mitm_header.mitm + + +class ExternalTrackedMitMDataset(TrackedMitMDataset): + model_config = pydantic.ConfigDict(arbitrary_types_allowed=True) + __mapper_args__ = {'polymorphic_identity': 'mapped'} + + type: str = Field(default='mapped', nullable=False) + sql_alchemy_uri: AnyUrl = Field(sa_type=StrType.wrap(AnyUrl)) + is_managed_locally: bool = Field(default=False) + last_edited: datetime = Field(sa_type=sqlmodel.DateTime, default_factory=datetime.now) + mitm_header: Header = Field(sa_type=PydanticType.wrap(Header), repr=False) + identifier_bundle: MitMDatasetIdentifierBundle = Field(sa_type=PydanticType.wrap(MitMDatasetIdentifierBundle), + repr=False) + + mapped_db_source_id: int = Field(foreign_key=f'{APPLICATION_DB_SCHEMA}.mapped_db_sources.id', nullable=False) + + @property + def mapped_db_source(self) -> MappedDBSource: + return relationship('MappedDBSource', + back_populates='tracked_mitm_datasets', + foreign_keys='mapped_db_source_id') + + @property + def pulls(self) -> list[MappedDBPull]: + return relationship('MappedDBPull', foreign_keys='tracked_mitm_dataset_id') + + # @hybridproperty + @property + def last_pulled(self) -> datetime | None: + from sqlalchemy import select, func + stmt = select(func.max('MappedDBPull.time')).where('MappedDBPull.tracked_mitm_dataset_id' == self.id) + return stmt.scalar_subquery() diff --git a/app/db/setup.py b/app/db/setup.py index d077a782b5df778fcddadb49c1cb267a64b5ff94..a4d2bc2c60f95bf88c09f4b83a69cc6c535503cd 100644 --- a/app/db/setup.py +++ b/app/db/setup.py @@ -4,7 +4,6 @@ import sqlalchemy as sa from mitm_tooling.utilities.python_utils import pick_from_mapping from sqlalchemy import create_engine, inspect, Engine from sqlalchemy.orm import Session -from sqlmodel import SQLModel from ..config import app_cfg @@ -20,7 +19,9 @@ if MITM_DATABASE_URL.get_dialect().name == 'sqlite': engine = create_engine(MITM_DATABASE_URL, execution_options=execution_options) def init_db(): + from .models import TrackedMitMDataset, ExternalTrackedMitMDataset, MappedDBSource, MappedDBPull from .models import APPLICATION_DB_SCHEMA + from sqlmodel import SQLModel from .utils import create_schema logger.info(f'Setting up MITM DB @ {MITM_DATABASE_URL}') with engine.connect() as conn: diff --git a/app/db/utils.py b/app/db/utils.py index a11965b665b16cb8a7e7131c96d4fcd415726199..f0c089cd8297135ac7af97c0a83ef3711f438626 100644 --- a/app/db/utils.py +++ b/app/db/utils.py @@ -33,7 +33,7 @@ def mk_orm_session(engine: Engine) -> Generator[ORMSession, None, None]: def infer_tracked_mitm_dataset_schema(engine: sa.Engine, uuid: UUID) -> DBMetaInfo | None: with mk_session(engine) as session: - model = session.get(TrackedMitMDataset, (uuid,)) + model = session.query(TrackedMitMDataset).filter(TrackedMitMDataset.uuid == uuid).one_or_none() if model is not None: meta, _ = connect_and_reflect(engine, allowed_schemas={model.schema_name}) return DBMetaInfo.from_sa_meta(meta, default_schema=model.schema_name) diff --git a/app/dependencies/orm.py b/app/dependencies/orm.py index c62d716f2de2d66e7ffc91a0600416000406e575..66281981e65f8c3de04e6451b94f2b1cb0797a3a 100644 --- a/app/dependencies/orm.py +++ b/app/dependencies/orm.py @@ -9,7 +9,7 @@ from ..db.models import TrackedMitMDataset def get_uploaded_dataset(session: ORMSessionDependency, uuid: UUID = fastapi.Path()) -> TrackedMitMDataset: - o = session.get(TrackedMitMDataset, (uuid,)) + o = session.query(TrackedMitMDataset).filter(TrackedMitMDataset.uuid == uuid).one_or_none() if o is None: raise HTTPException(status_code=404, detail='Referenced MitM Dataset does not exist.') return o diff --git a/app/routes/definitions/generate.py b/app/routes/definitions/generate.py index 1533ddd322b6842ef6d4027bf442c7aed20d545e..1f2992eabb6fe8a2753d55b6bbc877dc9d187b95 100644 --- a/app/routes/definitions/generate.py +++ b/app/routes/definitions/generate.py @@ -1,5 +1,5 @@ -from datetime import datetime from collections.abc import Sequence +from datetime import datetime from mitm_tooling.definition import MITM from mitm_tooling.representation import Header @@ -8,7 +8,7 @@ from mitm_tooling.transformation.superset import mk_superset_mitm_dataset_bundle from mitm_tooling.transformation.superset.asset_bundles import MitMDatasetIdentifierBundle, SupersetMitMDatasetBundle, \ SupersetDatasourceBundle, SupersetVisualizationBundle from mitm_tooling.transformation.superset.common import DBConnectionInfo -from mitm_tooling.transformation.superset.definitions import SupersetAssetsImport, SupersetMitMDatasetImport, \ +from mitm_tooling.transformation.superset.definitions import SupersetMitMDatasetImport, \ MetadataType from app.db.models import TrackedMitMDataset @@ -120,7 +120,8 @@ def track_visualizations(orm_session: ORMSession, def exec_viz_import_request(orm_session: ORMSession, tracked_dataset: TrackedMitMDataset, - request: GenerateVisualizationsRequest, as_assets: bool = False) -> SupersetMitMDatasetImport: + request: GenerateVisualizationsRequest) -> SupersetMitMDatasetImport: mitm_dataset_bundle = exec_viz_request(orm_session, tracked_dataset, request) - importable = mitm_dataset_bundle.to_import(metadata_type=(MetadataType.Asset if as_assets else MetadataType.MitMDataset)) + importable = mitm_dataset_bundle.to_import(metadata_type=( + request.override_metadata_type if request.override_metadata_type else MetadataType.MitMDataset)) return importable diff --git a/app/routes/definitions/requests.py b/app/routes/definitions/requests.py index 060c12e73c9f43578a2e0bd5aae336f42a8e1352..f673d5d0e7b2f2fd7fef47d37f909fdacdcbc41b 100644 --- a/app/routes/definitions/requests.py +++ b/app/routes/definitions/requests.py @@ -3,7 +3,7 @@ from mitm_tooling.representation import Header from mitm_tooling.transformation.superset import VisualizationType, MAEDVisualizationType from mitm_tooling.transformation.superset.asset_bundles import MitMDatasetIdentifierBundle from mitm_tooling.transformation.superset.common import DBConnectionInfo, MitMDatasetInfo -from mitm_tooling.transformation.superset.definitions import StrUUID +from mitm_tooling.transformation.superset.definitions import StrUUID, MetadataType from mitm_tooling.transformation.superset.definitions.mitm_dataset import MitMDatasetIdentifier @@ -18,3 +18,4 @@ class GenerateVisualizationsRequest(pydantic.BaseModel): visualization_types: list[VisualizationType] = [MAEDVisualizationType.Baseline] reuse_existing_identifiers: bool = True track_identifiers: bool = False + override_metadata_type: MetadataType | None = None diff --git a/app/routes/definitions/router.py b/app/routes/definitions/router.py index 02938ea32750e8540353a9d154a2dee6caf74822..193c098e0357cc50cfdf0e42a1b1b43b6b25f3fb 100644 --- a/app/routes/definitions/router.py +++ b/app/routes/definitions/router.py @@ -61,7 +61,7 @@ def generate_tracked_mitm_dataset_import(tracked_dataset: TrackedMitMDatasetDepe importable = exec_tracked_asset_import_request(tracked_dataset, include_visualizations, override_metadata_type=override_metadata_type) - return MitMDatasetImportResponse.from_models(importable.model_dump()) + return MitMDatasetImportResponse.from_models(importable) @router.get('/mitm_dataset/{uuid}/import/zip', response_class=StreamingResponse, @@ -84,25 +84,23 @@ def generate_visualizations_for_tracked_dataset(orm_session: ORMSessionDependenc tracked_dataset: TrackedMitMDatasetDependency, request: GenerateVisualizationsRequest) -> MitMDatasetBundleResponse: mitm_dataset_bundle = exec_viz_request(orm_session, tracked_dataset, request) - return MitMDatasetBundleResponse.from_models(mitm_dataset_bundle.model_dump()) + return MitMDatasetBundleResponse.from_models(mitm_dataset_bundle) @router.post('/mitm_dataset/viz/{uuid}/import') def generate_visualizations_import_for_tracked_dataset(orm_session: ORMSessionDependency, tracked_dataset: TrackedMitMDatasetDependency, - request: GenerateVisualizationsRequest, - as_assets: bool = False) -> VisualizationImportResponse: - importable = exec_viz_import_request(orm_session, tracked_dataset, request, as_assets=as_assets) - return VisualizationImportResponse.from_models(importable.model_dump()) + request: GenerateVisualizationsRequest) -> VisualizationImportResponse: + importable = exec_viz_import_request(orm_session, tracked_dataset, request) + return VisualizationImportResponse.from_models(importable) @router.post('/mitm_dataset/viz/{uuid}/import/zip', response_class=StreamingResponse, responses={200: {'content': {'application/zip': {}}}}) def generate_visualizations_import_zip_for_tracked_dataset(orm_session: ORMSessionDependency, tracked_dataset: TrackedMitMDatasetDependency, - request: GenerateVisualizationsRequest, - as_assets: bool = False) -> StreamingResponse: - importable = exec_viz_import_request(orm_session, tracked_dataset, request, as_assets=as_assets) + request: GenerateVisualizationsRequest) -> StreamingResponse: + importable = exec_viz_import_request(orm_session, tracked_dataset, request) bio = io.BytesIO() write_superset_import_as_zip(bio, importable) bio.seek(0) diff --git a/app/routes/mitm_dataset/export.py b/app/routes/mitm_dataset/export.py index c68711762353bf55528cc3bc3659ebf4e1b3a520..83185bd4dd29e07c7f2a58d2b6e8a2ea079efe2b 100644 --- a/app/routes/mitm_dataset/export.py +++ b/app/routes/mitm_dataset/export.py @@ -9,6 +9,7 @@ from mitm_tooling.transformation.sql.into_mappings import sql_rep_into_mappings from mitm_tooling.utilities.sql_utils import create_sa_engine from app.db.models import TrackedMitMDataset +from app.routes.mitm_dataset.mapped_db import mk_exportable logger = logging.getLogger(__name__) diff --git a/app/routes/mitm_dataset/register_external.py b/app/routes/mitm_dataset/register_external.py index a50e08ac367c4cb01dbb8a72f4569b757e6fc262..8ea786f965631addbe054ab5b7e1b06da4c89817 100644 --- a/app/routes/mitm_dataset/register_external.py +++ b/app/routes/mitm_dataset/register_external.py @@ -1,3 +1,5 @@ +from datetime import datetime + import sqlalchemy as sa from mitm_tooling.definition import MITM from mitm_tooling.extraction.sql.data_models import VirtualDB, DBMetaInfo, SourceDBType, VirtualView, \ @@ -12,19 +14,51 @@ from mitm_tooling.utilities.sql_utils import create_sa_engine from pydantic import AnyUrl, BaseModel from sqlalchemy.orm import Session +from app.db.models.mapped_sources import MappedDBSource, MappedDBPull +from app.db.models.tracked_mitm_dataset import ExternalTrackedMitMDataset from app.dependencies.db import ORMSessionDependency +from app.routes.mitm_dataset.append import append_exportable +from app.routes.mitm_dataset.mapped_db import mk_exportable from app.routes.mitm_dataset.requests import RegisterExternalMitMDatasetRequest -from app.routes.mitm_dataset.responses import RegisterMitMResponse - +from app.routes.mitm_dataset.responses import RegisterExternalMitMResponse +from app.routes.mitm_dataset.upload import upload_exportable def register_external_mitm_dataset( session: ORMSessionDependency, request: RegisterExternalMitMDatasetRequest, -) -> RegisterMitMResponse: +) -> RegisterExternalMitMResponse: + + + remote_engine = create_sa_engine(request.sql_alchemy_uri) + exportable = mk_exportable(remote_engine, request.mapped_db) + # header = exportable.generate_header(remote_engine) + + add_tracked_mitm_dataset = upload_exportable(request.sql_alchemy_uri, exportable, request.dataset_name, skip_instances=True) + + mapped_db_source = MappedDBSource(sql_alchemy_uri=request.sql_alchemy_uri, mitm_mapping=request.mapped_db, mitm_header=add_tracked_mitm_dataset.mitm_header) + session.add(mapped_db_source) + + external_tracked_mitm_dataset = ExternalTrackedMitMDataset.from_models(add_tracked_mitm_dataset, mapped_db_source=mapped_db_source) + + session.add(external_tracked_mitm_dataset) + + session.commit() + session.refresh(external_tracked_mitm_dataset) + session.refresh(mapped_db_source) + return RegisterExternalMitMResponse(tracked_mitm_dataset=external_tracked_mitm_dataset, mapped_db_source=mapped_db_source) + +def pull(session: ORMSessionDependency, external_tracked_mitm_dataset: ExternalTrackedMitMDataset): + db_source = external_tracked_mitm_dataset.mapped_db_source + + remote_engine = create_sa_engine(db_source.sql_alchemy_uri) + + exportable = mk_exportable(remote_engine, db_source.mitm_mapping) - sql_rep_schema = mk_sql_rep_schema(header, view_generators=None) + ii, ir = append_exportable(db_source.sql_alchemy_uri, exportable, external_tracked_mitm_dataset) + external_tracked_mitm_dataset.last_edited = datetime.now() - # TODO connect mapped_vvs to sql_rep_schema + pull = MappedDBPull(tracked_mitm_dataset_id=external_tracked_mitm_dataset.id, mapped_db_source_id=db_source.id, instances_imported=ii, rows_created=ir) - raise NotImplementedError() + session.add(pull) + session.flush() diff --git a/app/routes/mitm_dataset/requests.py b/app/routes/mitm_dataset/requests.py index 877dfa73fae66727398c3014c198a7245114e2a8..5ad67111e48f5f7484aba4f799ade1c8d9f5a6df 100644 --- a/app/routes/mitm_dataset/requests.py +++ b/app/routes/mitm_dataset/requests.py @@ -1,16 +1,17 @@ +from uuid import UUID + import pydantic -from mitm_tooling.definition import MITM -from mitm_tooling.extraction.sql.data_models import CompiledVirtualView -from mitm_tooling.extraction.sql.mapping import ConceptMapping from mitm_tooling.representation import Header from pydantic import AnyUrl from app.db.models import AddTrackedMitMDataset +from app.db.models.mapped_sources import MappedDB class AddTrackedMitMDatasetRequest(AddTrackedMitMDataset): pass + class EditTrackedMitMDatasetRequest(pydantic.BaseModel): dataset_name: str schema_name: str @@ -18,9 +19,9 @@ class EditTrackedMitMDatasetRequest(pydantic.BaseModel): mitm_header: Header is_managed_locally: bool + class RegisterExternalMitMDatasetRequest(pydantic.BaseModel): + uuid: UUID | None = None dataset_name: str sql_alchemy_uri: AnyUrl - mitm: MITM - cvvs: list[CompiledVirtualView] - mappings: list[ConceptMapping] + mapped_db: MappedDB diff --git a/app/routes/mitm_dataset/responses.py b/app/routes/mitm_dataset/responses.py index 09daed4a0974d0f8a1152da5e12414ade02e439e..3813a1c62dc2239dc22dc71944002e4d13cb0cb2 100644 --- a/app/routes/mitm_dataset/responses.py +++ b/app/routes/mitm_dataset/responses.py @@ -7,6 +7,7 @@ from mitm_tooling.definition import MITM from pydantic import Field from app.db.models import TrackedMitMDataset, BaseTrackedMitMDataset, ListTrackedMitMDataset +from app.db.models.mapped_sources import MappedDBSource class TrackMitMResponse(pydantic.BaseModel): @@ -19,8 +20,8 @@ class UploadMitMResponse(TrackMitMResponse): pass -class RegisterMitMResponse(TrackMitMResponse): - pass +class RegisterExternalMitMResponse(TrackMitMResponse): + mapped_db_source: MappedDBSource | None = None MitMsListResponse = pydantic.TypeAdapter(list[ListTrackedMitMDataset]) diff --git a/app/routes/mitm_dataset/router.py b/app/routes/mitm_dataset/router.py index 6b17a20c2ca258c7d4c8c2014acccdbfa52b5708..fdbb2794f0fee30ebf2f2c2643ae2ee1c12f1374 100644 --- a/app/routes/mitm_dataset/router.py +++ b/app/routes/mitm_dataset/router.py @@ -15,7 +15,7 @@ from app.dependencies.db import DBEngineDependency, ORMSessionDependency from app.dependencies.orm import TrackedMitMDatasetDependency from .export import export_via_mapping from .requests import AddTrackedMitMDatasetRequest, RegisterExternalMitMDatasetRequest, EditTrackedMitMDatasetRequest -from .responses import UploadMitMResponse, RegisterMitMResponse +from .responses import UploadMitMResponse, RegisterExternalMitMResponse from .upload import upload_mitm_file from ...db.utils import mk_session @@ -44,7 +44,7 @@ def upload_mitm_dataset( @router.post('/register') def register_external_mitm_dataset(session: ORMSessionDependency, - request: RegisterExternalMitMDatasetRequest) -> RegisterMitMResponse: + request: RegisterExternalMitMDatasetRequest) -> RegisterExternalMitMResponse: from .register_external import register_external_mitm_dataset return register_external_mitm_dataset(session, request) @@ -53,7 +53,7 @@ def register_external_mitm_dataset(session: ORMSessionDependency, def post_mitm_dataset(session: ORMSessionDependency, new_mitm_dataset: AddTrackedMitMDatasetRequest) -> TrackedMitMDataset: try: - new = TrackedMitMDataset.model_validate(**new_mitm_dataset.model_dump(mode='python', round_trip=True)) + new = TrackedMitMDataset.from_models(new_mitm_dataset) session.add(new) session.commit() session.refresh(new) @@ -103,11 +103,11 @@ def export_mitm_dataset(engine: DBEngineDependency, tracked_dataset: TrackedMitMDatasetDependency, use_streaming: bool = False) -> StreamingResponse: remote_engine, exportable = export_via_mapping(tracked_dataset) - with mk_session(remote_engine) as session: + with remote_engine.begin() as conn: if use_streaming: - ze = exportable.export_as_stream(session) + ze = exportable.export_as_stream(conn) data = ze.iter_bytes() else: - ze = exportable.export_to_memory(session) + ze = exportable.export_to_memory(conn) data = ze.to_buffer() return StreamingResponse(data, media_type='application/zip', headers={'Content-Disposition': 'attachment; filename=export.zip'}) diff --git a/app/routes/mitm_dataset/upload.py b/app/routes/mitm_dataset/upload.py index 5ae32c70c1bc5777fff1fc4253a9fccfa0135ffa..b394d93dfe2573af48e01b200fc3ae3411cc5443 100644 --- a/app/routes/mitm_dataset/upload.py +++ b/app/routes/mitm_dataset/upload.py @@ -6,10 +6,10 @@ import sqlalchemy as sa from mitm_tooling.definition import MITM from mitm_tooling.extraction.sql.mapping import Exportable from mitm_tooling.io import read_zip -from mitm_tooling.representation import mk_sql_rep_schema, MITMData, insert_mitm_data, Header, SQLRepresentationSchema +from mitm_tooling.representation import mk_sql_rep_schema, MITMData, Header, SQLRepresentationSchema from mitm_tooling.representation.sql_representation import insert_db_schema, insert_mitm_data_instances -from mitm_tooling.transformation.sql.from_exportable import insert_exportable, insert_exportable_instances -from mitm_tooling.utilities.identifiers import name_plus_uuid, mk_uuid +from mitm_tooling.transformation.sql.from_exportable import insert_exportable_instances +from mitm_tooling.utilities.identifiers import name_plus_uuid, mk_uuid, mk_short_uuid_str from mitm_tooling.utilities.io_utils import DataSource from mitm_tooling.utilities.sql_utils import sa_url_into_any_url, create_sa_engine from pydantic import AnyUrl @@ -22,6 +22,16 @@ from app.dependencies.db import get_engine logger = logging.getLogger(__name__) +def _skip_instances( + conn: sa.Connection, + sql_rep_schema: SQLRepresentationSchema, +) -> tuple[int, int]: + """ + Skip the instances insertion. + """ + return 0, 0 + + def upload_mitm_file(mitm: MITM, mitm_zip: DataSource, dataset_name: str, @@ -34,9 +44,13 @@ def upload_mitm_file(mitm: MITM, def upload_mitm_data(mitm_data: MITMData, dataset_name: str, uuid: UUID | None = None, - engine: Engine = None) -> AddTrackedMitMDataset: - header_creator = lambda : mitm_data.header - instances_inserter = lambda conn, sql_rep_schema: insert_mitm_data_instances(conn, sql_rep_schema, mitm_data) + engine: Engine = None, + skip_instances: bool = False) -> AddTrackedMitMDataset: + header_creator = lambda: mitm_data.header + if skip_instances: + instances_inserter = _skip_instances + else: + instances_inserter = lambda conn, sql_rep_schema: insert_mitm_data_instances(conn, sql_rep_schema, mitm_data) return upload_data(header_creator, instances_inserter, dataset_name, uuid, engine) @@ -45,13 +59,17 @@ def upload_exportable(source: AnyUrl, exportable: Exportable, dataset_name: str, uuid: UUID | None = None, - engine: Engine = None) -> AddTrackedMitMDataset: + engine: Engine = None, + skip_instances: bool = False) -> AddTrackedMitMDataset: source_engine = create_sa_engine(source) header_creator = lambda: exportable.generate_header(source_engine) - def instances_inserter(conn, sql_rep_schema): - return insert_exportable_instances(source_engine, exportable, conn, sql_rep_schema, stream_data=False) + if skip_instances: + instances_inserter = _skip_instances + else: + def instances_inserter(conn, sql_rep_schema): + return insert_exportable_instances(source_engine, exportable, conn, sql_rep_schema, stream_data=False) return upload_data(header_creator, instances_inserter, dataset_name, uuid, engine) @@ -64,7 +82,7 @@ def upload_data(header_creator: Callable[[], Header], engine = engine if engine is not None else get_engine() sql_alchemy_uri = sa_url_into_any_url(engine.url) uuid = uuid or mk_uuid() - unique_schema_name = name_plus_uuid(dataset_name, uuid, sep='_') + unique_schema_name = mk_short_uuid_str(uuid) # name_plus_uuid(dataset_name, uuid, sep='_') logger.info(f'Uploading MitM Data with uuid {uuid} into target schema {unique_schema_name} on connected DB {engine.url}.') diff --git a/docker-compose.yaml b/docker-compose.yaml index 2e6b30fdbe08b1f0e5f7f31fff697169f5e75216..4a9570449ab47bdbe108980c56f7b95071255299 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -22,7 +22,7 @@ services: container_name: test_mitm_db restart: unless-stopped ports: - - "5432:5432" + - "25432:5432" env_file: docker/.env volumes: - mitm_db_home:/var/lib/postgresql/data diff --git a/test/http-client.env.json b/test/http-client.env.json index 5f385fd7c7327f7b2bc979fed5ad5bfcdeeee70a..a70cd6df712c2411f974707758f9591cf194f8d4 100644 --- a/test/http-client.env.json +++ b/test/http-client.env.json @@ -9,7 +9,7 @@ }, "superset": { "port": "8180", - "uuid": "5b9bc5c0-7faf-4807-a098-573558a0e2a6" + "uuid": "6ea60ebf-b3ec-4b64-988d-8dc4e2f48eca" }, "kubernetes": { "port": "8080", diff --git a/test/upload.http b/test/upload.http index 706b92cb3cf2a5daeb1dcb9927eae5c2b5ce2864..e62824c1f60e47004ecd0b3aca51934754105875 100644 --- a/test/upload.http +++ b/test/upload.http @@ -3,7 +3,7 @@ # @name Upload MAED dataset # @timeout 180 # @connection-timeout 180 -POST http://localhost:{{port}}/mitm_dataset/upload?dataset_name=myname_0&mitm=MAED +POST http://localhost:{{port}}/mitm_dataset/upload?dataset_name=myname_x&mitm=MAED Accept: application/json Content-Type: multipart/form-data; boundary=WebAppBoundary