diff --git a/justfile b/justfile index ccd579bb00b2384d152ed3a59573e8c321b1fe0b..d240c6a92be0249547b7f7d2e7e32352475d5979 100644 --- a/justfile +++ b/justfile @@ -1,18 +1,22 @@ set windows-shell := ["pwsh", "-c"] + +default: + @just --list + lock: - @poetry lock + uv lock -update: - @poetry update --with dev +sync: + uv sync build: - @poetry build + uv build -publish: build - @poetry publish +publish + uv publish requirements: - @poetry export --without-hashes -f requirements.txt > requirements.txt --without-hashes + uv export --no-hashes > requirements.txt preflight: lock requirements build diff --git a/mitm_tooling/transformation/superset/asset_bundles/asset_bundles.py b/mitm_tooling/transformation/superset/asset_bundles/asset_bundles.py index 5a9ef91f5e64dc95605f50c765124f2ec942f511..b7a40f9eacbabd60c68099a66e8e3051446d8dfa 100644 --- a/mitm_tooling/transformation/superset/asset_bundles/asset_bundles.py +++ b/mitm_tooling/transformation/superset/asset_bundles/asset_bundles.py @@ -6,9 +6,13 @@ from uuid import UUID import pydantic from mitm_tooling.representation import TableName +from mitm_tooling.utilities.python_utils import deep_merge_dicts +from .identifier import VizCollectionIdentifierMap, DatasourceIdentifierBundle, \ + MitMDatasetIdentifierBundle from ..definitions import SupersetDatabaseDef, SupersetMitMDatasetDef, \ SupersetChartDef, SupersetDashboardDef, SupersetAssetsImport, SupersetDatasetDef, \ - SupersetMitMDatasetImport, SupersetDefFolder, DatasourceIdentifier + SupersetMitMDatasetImport, SupersetDefFolder, DatasetIdentifier, MetadataType, DatasetIdentifierMap, \ + DatabaseIdentifier, ChartIdentifierMap, DashboardIdentifierMap from ..factories.importable import mk_assets_import, mk_mitm_dataset_import @@ -35,16 +39,31 @@ class SupersetDatasourceBundle(SupersetAssetBundle): return [ds.uuid for ds in self.datasets] @property - def placeholder_dataset_identifiers(self) -> dict[TableName, DatasourceIdentifier]: - return {ds.table_name: DatasourceIdentifier(uuid=ds.uuid) for ds in self.datasets} + def database_identifier(self) -> DatabaseIdentifier: + return self.database.identifier - def to_import(self) -> SupersetAssetsImport: - return mk_assets_import(databases=[self.database], datasets=self.datasets) + @property + def dataset_identifier_map(self) -> DatasetIdentifierMap: + return {ds.table_name: ds.identifier for ds in self.datasets} + + @property + def identifiers(self) -> DatasourceIdentifierBundle: + return DatasourceIdentifierBundle(database=self.database_identifier, ds_id_map=self.dataset_identifier_map) + + @property + def placeholder_dataset_identifiers(self) -> dict[TableName, DatasetIdentifier]: + return {ds.table_name: DatasetIdentifier(uuid=ds.uuid, id=-1) for ds in self.datasets} + + def to_import(self, metadata_type: MetadataType = MetadataType.Asset) -> SupersetAssetsImport: + return mk_assets_import(databases=[self.database], datasets=self.datasets, metadata_type=metadata_type) class SupersetVisualizationBundle(SupersetAssetBundle): charts: list[SupersetChartDef] = pydantic.Field(default_factory=list) dashboards: list[SupersetDashboardDef] = pydantic.Field(default_factory=list) + viz_collections: VizCollectionIdentifierMap | None = None + + # viz_map: VizDashboardIdentifierMap | None = None @property def chart_uuids(self) -> list[UUID]: @@ -54,6 +73,21 @@ class SupersetVisualizationBundle(SupersetAssetBundle): def dashboard_uuids(self) -> list[UUID]: return [da.uuid for da in self.dashboards] + @property + def chart_identifier_map(self) -> ChartIdentifierMap: + return {ch.slice_name: ch.identifier for ch in self.charts} + + @property + def dashboard_identifier_map(self) -> DashboardIdentifierMap: + return {da.dashboard_title: da.identifier for da in self.dashboards} + + @property + def viz_identifier_map(self) -> VizCollectionIdentifierMap: + if self.viz_collections is None: + return {'default': self.dashboard_identifier_map} + else: + return self.viz_collections + @classmethod def combine(cls, *bundles: Self) -> Self: if not bundles or len(bundles) == 0: @@ -61,10 +95,14 @@ class SupersetVisualizationBundle(SupersetAssetBundle): charts, dashboards = itertools.chain(*(b.charts for b in bundles)), itertools.chain(*(b.dashboards for b in bundles)) - return cls(charts=list(charts), dashboards=list(dashboards)) + viz_collections_map = deep_merge_dicts(*(b.viz_identifier_map for b in bundles)) + + return cls(charts=list(charts), + dashboards=list(dashboards), + viz_collections=viz_collections_map) - def to_import(self) -> SupersetAssetsImport: - return mk_assets_import(charts=self.charts, dashboards=self.dashboards) + def to_import(self, metadata_type: MetadataType = MetadataType.Asset) -> SupersetAssetsImport: + return mk_assets_import(charts=self.charts, dashboards=self.dashboards, metadata_type=metadata_type) class SupersetMitMDatasetBundle(SupersetAssetBundle): @@ -72,7 +110,14 @@ class SupersetMitMDatasetBundle(SupersetAssetBundle): datasource_bundle: SupersetDatasourceBundle visualization_bundle: SupersetVisualizationBundle = pydantic.Field(default_factory=SupersetVisualizationBundle) - def with_visualization_bundle(self, visualization_bundle: SupersetVisualizationBundle) -> Self: + @property + def identifiers(self) -> MitMDatasetIdentifierBundle: + return MitMDatasetIdentifierBundle(mitm_dataset=self.mitm_dataset.identifier, + database=self.datasource_bundle.database_identifier, + ds_id_map=self.datasource_bundle.dataset_identifier_map, + viz_id_map=self.visualization_bundle.viz_identifier_map) + + def replace_visualization_bundle(self, visualization_bundle: SupersetVisualizationBundle) -> Self: mitm_ds = self.mitm_dataset from mitm_tooling.transformation.superset.factories.mitm_dataset import mk_mitm_dataset return self.__class__( @@ -84,7 +129,7 @@ class SupersetMitMDatasetBundle(SupersetAssetBundle): datasource_bundle=self.datasource_bundle, visualization_bundle=visualization_bundle) - def to_import(self) -> SupersetMitMDatasetImport: + def to_import(self, meta_data_type: MetadataType = MetadataType.Asset) -> SupersetMitMDatasetImport: base_assets = mk_assets_import(databases=[self.datasource_bundle.database], datasets=self.datasource_bundle.datasets, charts=self.visualization_bundle.charts, diff --git a/mitm_tooling/transformation/superset/asset_bundles/identifier.py b/mitm_tooling/transformation/superset/asset_bundles/identifier.py new file mode 100644 index 0000000000000000000000000000000000000000..4fcb5ed37f8f2fbae63b3900a2f084be20d9ffae --- /dev/null +++ b/mitm_tooling/transformation/superset/asset_bundles/identifier.py @@ -0,0 +1,29 @@ +from uuid import UUID + +import pydantic + +from mitm_tooling.transformation.superset.definitions import DatasetIdentifierMap, \ + DatabaseIdentifier, MitMDatasetIdentifier, BaseSupersetDefinition, DashboardIdentifier + +VizDashboardIdentifierMap = dict[str, DashboardIdentifier] +VizCollectionIdentifierMap = dict[str, VizDashboardIdentifierMap] + + +class DatasourceIdentifierBundle(BaseSupersetDefinition): + database: DatabaseIdentifier | None = None + ds_id_map: DatasetIdentifierMap = pydantic.Field(default_factory=dict) + + @property + def database_uuid(self) -> UUID | None: + if self.database is not None: + return self.database.uuid + + +class MitMDatasetIdentifierBundle(DatasourceIdentifierBundle): + mitm_dataset: MitMDatasetIdentifier | None = None + viz_id_map: VizCollectionIdentifierMap = pydantic.Field(default_factory=dict) + + @property + def mitm_dataset_uuid(self) -> UUID | None: + if self.mitm_dataset is not None: + return self.mitm_dataset.uuid diff --git a/mitm_tooling/transformation/superset/common.py b/mitm_tooling/transformation/superset/common.py index 9224964305c0809fbd2d29903ed323dc5a2d2200..6b8c7f32d68c999aa21b7db2abf0671dc2874924 100644 --- a/mitm_tooling/transformation/superset/common.py +++ b/mitm_tooling/transformation/superset/common.py @@ -4,11 +4,12 @@ import pydantic import sqlalchemy as sa from pydantic import AnyUrl, ConfigDict -from mitm_tooling.representation import SchemaName +from mitm_tooling.representation import SchemaName, Header from mitm_tooling.representation.sql_representation import SQL_REPRESENTATION_DEFAULT_SCHEMA from mitm_tooling.utilities.io_utils import FilePath from mitm_tooling.utilities.sql_utils import create_sa_engine, dialect_cls_from_url, any_url_into_sa_url from .definitions import StrUrl +from ...definition import MITM SQLiteFileOrEngine = FilePath | sa.Engine @@ -37,6 +38,13 @@ class DBConnectionInfo(pydantic.BaseModel): def dialect_cls(self) -> Type[sa.engine.Dialect]: return dialect_cls_from_url(self.sql_alchemy_uri) +class MitMDatasetInfo(pydantic.BaseModel): + dataset_name: str + mitm: MITM + + @classmethod + def named_header(cls, name:str, header: Header): + return cls(dataset_name=name, mitm=header.mitm) def _mk_engine(arg: SQLiteFileOrEngine) -> sa.Engine: if isinstance(arg, sa.Engine): diff --git a/mitm_tooling/transformation/superset/definitions/__init__.py b/mitm_tooling/transformation/superset/definitions/__init__.py index b520dac2a31467bf575e7783a59069714117121e..a280f48fe079051128b641a3ddda49c456803dae 100644 --- a/mitm_tooling/transformation/superset/definitions/__init__.py +++ b/mitm_tooling/transformation/superset/definitions/__init__.py @@ -1,3 +1,4 @@ +from .identifiers import * from .constants import * from .core import * from .database import * diff --git a/mitm_tooling/transformation/superset/definitions/chart.py b/mitm_tooling/transformation/superset/definitions/chart.py index da401e64c251f4a4ef8a8ee611e11301267ad5eb..f71049bd92153dd636c11b3cda2dcd64d9283daf 100644 --- a/mitm_tooling/transformation/superset/definitions/chart.py +++ b/mitm_tooling/transformation/superset/definitions/chart.py @@ -1,14 +1,15 @@ -from typing import Any, Literal +from typing import Any, Literal, Annotated import pydantic +from . import ChartIdentifier from .constants import StrUrl, StrUUID -from .core import FormData, DatasourceIdentifier, SupersetVizType, SupersetAdhocFilter, SupersetId, ColorScheme, \ - SupersetAdhocMetric, ColumnName, TimeGrain, AnnotationLayer, SupersetDefFile, QueryContext +from .core import FormData, DatasetIdentifier, SupersetVizType, SupersetAdhocFilter, SupersetId, ColorScheme, \ + SupersetAdhocMetric, ColumnName, TimeGrain, AnnotationLayer, SupersetDefFile, QueryContext, SupersetObjectMixin class ChartParams(FormData): - datasource: str | DatasourceIdentifier + datasource: str | DatasetIdentifier viz_type: SupersetVizType groupby: list[str] = pydantic.Field(default_factory=list) adhoc_filters: list[SupersetAdhocFilter] = pydantic.Field(default_factory=list) @@ -79,8 +80,13 @@ class TimeSeriesLineParams(TimeSeriesChartParams): markerSize: int = 6 seriesType: str = 'line' +JsonQueryContext = Annotated[QueryContext, pydantic.PlainSerializer( + lambda x: x.model_dump_json(by_alias=True, serialize_as_any=True, exclude_none=True) if isinstance(x, + pydantic.BaseModel) else x, + return_type=pydantic.Json), pydantic.BeforeValidator(lambda x : QueryContext.model_validate(**x) if isinstance(x, dict) else x)] -class SupersetChartDef(SupersetDefFile): + +class SupersetChartDef(SupersetObjectMixin, SupersetDefFile): uuid: StrUUID slice_name: str viz_type: SupersetVizType @@ -89,12 +95,7 @@ class SupersetChartDef(SupersetDefFile): certified_by: str | None = None certification_details: str | None = None params: ChartParams | None = None - query_context: QueryContext | None = None - - # query_context: Annotated[pydantic.Json | QueryContext | None, pydantic.PlainSerializer( - # lambda x: x.model_dump_json(by_alias=True, serialize_as_any=True, exclude_none=True) if isinstance(x, - # pydantic.BaseModel) else x, - # return_type=pydantic.Json), pydantic.Field(default=None)] + query_context: JsonQueryContext | None = None cache_timeout: int | None = None version: str = '1.0.0' @@ -104,3 +105,7 @@ class SupersetChartDef(SupersetDefFile): @property def filename(self) -> str: return f'{self.slice_name}_{self.dataset_uuid}' + + @property + def identifier(self) -> ChartIdentifier: + return ChartIdentifier(uuid=self.uuid, slice_name=self.slice_name, id=-1) \ No newline at end of file diff --git a/mitm_tooling/transformation/superset/definitions/core.py b/mitm_tooling/transformation/superset/definitions/core.py index aa0f70e906dda1b3a17476757133ba1b28657adf..f0a89c6dc2492fb02977d473266a914b55008232 100644 --- a/mitm_tooling/transformation/superset/definitions/core.py +++ b/mitm_tooling/transformation/superset/definitions/core.py @@ -1,20 +1,15 @@ from typing import Any -from mitm_tooling.representation import ColumnName, TableName +from mitm_tooling.representation import ColumnName from .constants import * +from .identifiers import DatasetIdentifier, SupersetObjectIdentifier -class DatasourceIdentifier(FrozenSupersetDefinition): - id: SupersetId = -1 # -1 as a placeholder - type: Literal['table', 'annotation'] = 'table' - - uuid: StrUUID = pydantic.Field(exclude=False) - +class SupersetObjectMixin(ABC): @property - def datasource_uid(self): - return f'{self.id}__{self.type}' - -DatasourceIdentifierMap = dict[TableName, DatasourceIdentifier] + @abstractmethod + def identifier(self) -> SupersetObjectIdentifier: + ... class SupersetPostProcessing(pydantic.BaseModel, ABC): operation: str @@ -139,11 +134,23 @@ AnnotationLayers = Annotated[list[AnnotationLayer] | None, pydantic.SerializeAsA PostProcessingList = Annotated[list[SupersetPostProcessing | dict[str, Any]], pydantic.SerializeAsAny] +class ChartDatasource(DatasetIdentifier): + type: Literal['table', 'annotation'] = 'table' + + @property + def datasource_uid(self): + return f'{self.id}__{self.type}' + + @classmethod + def from_identifier(cls, ds_id: DatasetIdentifier) -> Self: + return cls(id=ds_id.id, uuid=ds_id.uuid, table_name=ds_id.table_name) + + class QueryObject(BaseSupersetDefinition): annotation_layers: list[AnnotationLayer] = pydantic.Field(default_factory=list) applied_time_extras: dict[str, str] = pydantic.Field(default_factory=dict) columns: list[ColumnName | SupersetAdhocColumn] = pydantic.Field(default_factory=list) - datasource: DatasourceIdentifier | None = None + datasource: ChartDatasource | None = None extras: QueryObjectExtras = pydantic.Field(default_factory=QueryObjectExtras) filters: list[QueryObjectFilterClause] = pydantic.Field(default_factory=list) metrics: list[SupersetAdhocMetric] | None = None @@ -174,12 +181,10 @@ class FormData(BaseSupersetDefinition): class QueryContext(BaseSupersetDefinition): - datasource: DatasourceIdentifier + datasource: ChartDatasource queries: list[QueryObject] = pydantic.Field(default_factory=list) form_data: FormData | None = pydantic.Field(default=None, strict=False) result_type: ChartDataResultType = ChartDataResultType.FULL result_format: ChartDataResultFormat = ChartDataResultFormat.JSON force: bool = False custom_cache_timeout: int | None = None - - diff --git a/mitm_tooling/transformation/superset/definitions/dashboard.py b/mitm_tooling/transformation/superset/definitions/dashboard.py index a693ded650dbbee2646585e20a68b44efcefd7fb..842016fbcea6c2d20420934633fa087842b8f9ab 100644 --- a/mitm_tooling/transformation/superset/definitions/dashboard.py +++ b/mitm_tooling/transformation/superset/definitions/dashboard.py @@ -3,8 +3,9 @@ from typing import Literal import pydantic -from .core import SupersetDefFile -from .constants import StrUUID, StrUrl +from . import DashboardIdentifier +from .core import SupersetDefFile, SupersetObjectMixin +from .constants import StrUUID, StrUrl, SupersetId DashboardInternalID = str @@ -60,7 +61,7 @@ class ChartMeta(ComponentMeta): uuid: StrUUID width: int = pydantic.Field(ge=1, le=12) height: int - chartId: int | None = None + chartId: SupersetId = -1 # Placeholder value just so the key exists. Alternatively, use .model_dump(exclude_none=False) sliceName: str | None = None @@ -114,7 +115,7 @@ class DashboardMetadata(pydantic.BaseModel): native_filter_configuration: list[NativeFilterConfig] = pydantic.Field(default_factory=list) -class SupersetDashboardDef(SupersetDefFile): +class SupersetDashboardDef(SupersetObjectMixin, SupersetDefFile): uuid: StrUUID dashboard_title: str position: DashboardPositionData @@ -132,3 +133,7 @@ class SupersetDashboardDef(SupersetDefFile): @property def filename(self) -> str: return f'{self.dashboard_title}_{self.uuid}' + + @property + def identifier(self) -> DashboardIdentifier: + return DashboardIdentifier(uuid=self.uuid, dashboard_title=self.dashboard_title, id=-1) \ No newline at end of file diff --git a/mitm_tooling/transformation/superset/definitions/database.py b/mitm_tooling/transformation/superset/definitions/database.py index f72c3686bb5666444237f78e52b4758be1281587..27dcb9b3a8c14fb1f079c30e9a27fdc457667d43 100644 --- a/mitm_tooling/transformation/superset/definitions/database.py +++ b/mitm_tooling/transformation/superset/definitions/database.py @@ -2,10 +2,11 @@ from typing import Any import pydantic -from .core import SupersetDefFile, StrUrl, StrUUID +from . import BaseSupersetDefinition, SupersetId, DatabaseIdentifier +from .core import SupersetDefFile, StrUrl, StrUUID, SupersetObjectMixin -class SupersetDatabaseDef(SupersetDefFile): +class SupersetDatabaseDef(SupersetObjectMixin, SupersetDefFile): database_name: str sqlalchemy_uri: StrUrl uuid: StrUUID @@ -27,3 +28,7 @@ class SupersetDatabaseDef(SupersetDefFile): @property def filename(self): return self.database_name + + @property + def identifier(self) -> DatabaseIdentifier: + return DatabaseIdentifier(uuid=self.uuid, database_name=self.database_name, id=-1) \ No newline at end of file diff --git a/mitm_tooling/transformation/superset/definitions/dataset.py b/mitm_tooling/transformation/superset/definitions/dataset.py index 6b6d3a0dd19fedcd537e8224d5efa8b3d010ce05..3b6b613e53943ece9429c7fa39f9c90025186c5c 100644 --- a/mitm_tooling/transformation/superset/definitions/dataset.py +++ b/mitm_tooling/transformation/superset/definitions/dataset.py @@ -2,6 +2,7 @@ from typing import Any import pydantic +from . import DatasetIdentifier from .core import SupersetDefFile, StrUUID, SupersetMetric, SupersetColumn @@ -33,3 +34,7 @@ class SupersetDatasetDef(SupersetDefFile): @property def filename(self): return self.table_name + + @property + def identifier(self) -> DatasetIdentifier: + return DatasetIdentifier(uuid=self.uuid, table_name=self.table_name, id=-1) \ No newline at end of file diff --git a/mitm_tooling/transformation/superset/definitions/identifiers.py b/mitm_tooling/transformation/superset/definitions/identifiers.py new file mode 100644 index 0000000000000000000000000000000000000000..5063fd093161a59d8e80d4fd1b7af410dad49820 --- /dev/null +++ b/mitm_tooling/transformation/superset/definitions/identifiers.py @@ -0,0 +1,39 @@ +import pydantic + +from mitm_tooling.representation import TableName +from mitm_tooling.utilities.identifiers import mk_uuid +from .constants import * + + +class SupersetObjectIdentifier(BaseSupersetDefinition): + id: SupersetId | None = None + uuid: StrUUID = pydantic.Field(default_factory=mk_uuid) + + +class DatabaseIdentifier(SupersetObjectIdentifier): + database_name: str | None = None + + +class DatasetIdentifier(SupersetObjectIdentifier): + table_name: str | None = None + + +DatasetIdentifierMap = dict[TableName, DatasetIdentifier] + + +class ChartIdentifier(SupersetObjectIdentifier): + slice_name: str | None = None + + +ChartIdentifierMap = dict[str, ChartIdentifier] + + +class DashboardIdentifier(SupersetObjectIdentifier): + dashboard_title: str | None = None + + +DashboardIdentifierMap = dict[str, DashboardIdentifier] +DashboardGroupsIdentifierMap = dict[str, DashboardIdentifierMap] + +class MitMDatasetIdentifier(SupersetObjectIdentifier): + dataset_name: str | None = None diff --git a/mitm_tooling/transformation/superset/definitions/mitm_dataset.py b/mitm_tooling/transformation/superset/definitions/mitm_dataset.py index c215b6c77ab73eb5a74d01c9683e268c309282b0..8df850e8ed813be58e784a7bd2e6dd486cb5396c 100644 --- a/mitm_tooling/transformation/superset/definitions/mitm_dataset.py +++ b/mitm_tooling/transformation/superset/definitions/mitm_dataset.py @@ -1,33 +1,15 @@ from mitm_tooling.definition import MITM from mitm_tooling.representation import Header -from mitm_tooling.transformation.superset.definitions import SupersetDefFile, StrUUID, BaseSupersetDefinition, \ - SupersetId +from mitm_tooling.transformation.superset.definitions import SupersetDefFile, StrUUID, DatasetIdentifier, \ + DashboardIdentifier, ChartIdentifier, \ + MitMDatasetIdentifier, SupersetObjectMixin - -class MitMDatasetIdentifier(BaseSupersetDefinition): - dataset_name: str - id: SupersetId | None = None - uuid: StrUUID | None = None - - -class RelatedObjectIdentifier(BaseSupersetDefinition): - id: SupersetId | None = None - uuid: StrUUID - - -class RelatedTable(RelatedObjectIdentifier): - table_name: str | None = None - - -class RelatedSlice(RelatedObjectIdentifier): - slice_name: str | None = None - - -class RelatedDashboard(RelatedObjectIdentifier): - dashboard_title: str | None = None +RelatedTable = DatasetIdentifier +RelatedSlice = ChartIdentifier +RelatedDashboard = DashboardIdentifier -class SupersetMitMDatasetDef(SupersetDefFile): +class SupersetMitMDatasetDef(SupersetObjectMixin, SupersetDefFile): uuid: StrUUID dataset_name: str mitm: MITM @@ -40,7 +22,7 @@ class SupersetMitMDatasetDef(SupersetDefFile): @property def identifier(self) -> MitMDatasetIdentifier: - return MitMDatasetIdentifier(dataset_name=self.dataset_name, uuid=self.uuid) + return MitMDatasetIdentifier(uuid=self.uuid, dataset_name=self.dataset_name, id=-1) @property def filename(self) -> str: diff --git a/mitm_tooling/transformation/superset/factories/custom_charts.py b/mitm_tooling/transformation/superset/factories/custom_charts.py index 85e6afb578558fa202dd8f9aa23329e5c9066124..949c12c690cb5d0cdc8d8e8a8da2484a75acbccf 100644 --- a/mitm_tooling/transformation/superset/factories/custom_charts.py +++ b/mitm_tooling/transformation/superset/factories/custom_charts.py @@ -1,7 +1,7 @@ from typing import Literal from mitm_tooling.transformation.superset.definitions import SupersetChartDef, SupersetVizType, ChartParams, \ - DatasourceIdentifier, MitMDatasetIdentifier + DatasetIdentifier, MitMDatasetIdentifier from .chart import mk_chart_def from .query import mk_query_object, mk_query_context @@ -13,7 +13,7 @@ class MAEDCustomChartParams(ChartParams): def mk_maed_custom_chart(name: str, mitm_dataset_identifier: MitMDatasetIdentifier, - datasource_identifier: DatasourceIdentifier, + datasource_identifier: DatasetIdentifier, **kwargs) -> SupersetChartDef: params = MAEDCustomChartParams(datasource=datasource_identifier, mitm_dataset=mitm_dataset_identifier) qo = mk_query_object() diff --git a/mitm_tooling/transformation/superset/factories/generic_charts.py b/mitm_tooling/transformation/superset/factories/generic_charts.py index f615919dbaa9a34aee985df89b8a059380986ca9..34cdfc1a792963acaa7c369bc08b1fe77829f570 100644 --- a/mitm_tooling/transformation/superset/factories/generic_charts.py +++ b/mitm_tooling/transformation/superset/factories/generic_charts.py @@ -6,33 +6,33 @@ from .core import mk_adhoc_metric, mk_empty_adhoc_time_filter, \ mk_pivot_post_processing, mk_adhoc_column from .query import mk_query_object, \ mk_empty_query_object_time_filter_clause, mk_query_context -from ..definitions import DatasourceIdentifier, SupersetChartDef, SupersetAggregate, \ +from ..definitions import DatasetIdentifier, SupersetChartDef, SupersetAggregate, \ PieChartParams, SupersetVizType, SupersetAdhocFilter, TimeGrain, TimeSeriesBarParams, QueryObjectFilterClause, \ TimeSeriesLineParams, QueryObjectExtras -def mk_pie_chart(name: str, datasource_identifier: DatasourceIdentifier, col: ColumnName, dt: MITMDataType, +def mk_pie_chart(name: str, dataset_identifier: DatasetIdentifier, col: ColumnName, dt: MITMDataType, groupby_cols: list[ColumnName] | None = None) -> SupersetChartDef: groupby_cols = groupby_cols or [] metric = mk_adhoc_metric(col, agg=SupersetAggregate.COUNT, dt=dt) - params = PieChartParams(datasource=datasource_identifier, + params = PieChartParams(datasource=dataset_identifier, metric=metric, groupby=groupby_cols, adhoc_filters=[mk_empty_adhoc_time_filter()]) # TODO may not be necessary to add groupby qo = mk_query_object(unique([col], groupby_cols), metrics=[metric], filters=[mk_empty_query_object_time_filter_clause()]) - qc = mk_query_context(datasource=datasource_identifier, queries=[qo], form_data=params) + qc = mk_query_context(datasource=dataset_identifier, queries=[qo], form_data=params) return mk_chart_def(name=name, viz_type=SupersetVizType.PIE, - dataset_uuid=datasource_identifier.uuid, + dataset_uuid=dataset_identifier.uuid, params=params, query_context=qc) def mk_time_series_bar_chart(name: str, - datasource_identifier: DatasourceIdentifier, + dataset_identifier: DatasetIdentifier, y_col: ColumnName, y_dt: MITMDataType, x_col: ColumnName, @@ -44,7 +44,7 @@ def mk_time_series_bar_chart(name: str, adhoc_filters = [mk_empty_adhoc_time_filter()] if filters: adhoc_filters.extend(filters) - params = TimeSeriesBarParams(datasource=datasource_identifier, + params = TimeSeriesBarParams(datasource=dataset_identifier, metrics=[metric], groupby=groupby_cols, adhoc_filters=adhoc_filters, @@ -60,17 +60,17 @@ def mk_time_series_bar_chart(name: str, filters=[QueryObjectFilterClause.from_adhoc_filter(af) for af in adhoc_filters], post_processing=pp, series_columns=[y_col]) - qc = mk_query_context(datasource=datasource_identifier, queries=[qo], form_data=params) + qc = mk_query_context(datasource=dataset_identifier, queries=[qo], form_data=params) return mk_chart_def(name=name, viz_type=SupersetVizType.TIMESERIES_BAR, - dataset_uuid=datasource_identifier.uuid, + dataset_uuid=dataset_identifier.uuid, params=params, query_context=qc) def mk_avg_count_time_series_chart(name: str, - datasource_identifier: DatasourceIdentifier, + dataset_identifier: DatasetIdentifier, groupby_cols: list[ColumnName], time_col: ColumnName = 'time', filters: list[SupersetAdhocFilter] | None = None, @@ -80,7 +80,7 @@ def mk_avg_count_time_series_chart(name: str, adhoc_filters = [mk_empty_adhoc_time_filter()] if filters: adhoc_filters.extend(filters) - params = TimeSeriesLineParams(datasource=datasource_identifier, + params = TimeSeriesLineParams(datasource=dataset_identifier, metrics=[metric], groupby=groupby_cols, adhoc_filters=adhoc_filters, @@ -97,10 +97,10 @@ def mk_avg_count_time_series_chart(name: str, post_processing=pp, series_columns=groupby_cols, extras=QueryObjectExtras(time_grain_sqla=time_grain)) - qc = mk_query_context(datasource=datasource_identifier, queries=[qo], form_data=params) + qc = mk_query_context(datasource=dataset_identifier, queries=[qo], form_data=params) return mk_chart_def(name=name, viz_type=SupersetVizType.TIMESERIES_LINE, - dataset_uuid=datasource_identifier.uuid, + dataset_uuid=dataset_identifier.uuid, params=params, query_context=qc) diff --git a/mitm_tooling/transformation/superset/factories/importable.py b/mitm_tooling/transformation/superset/factories/importable.py index 7ae04a7b8c3f683f889836f655d3a08e0afd66a0..eb56c4c979276f3b230f4bfca0160ccde5b37d3a 100644 --- a/mitm_tooling/transformation/superset/factories/importable.py +++ b/mitm_tooling/transformation/superset/factories/importable.py @@ -3,7 +3,7 @@ from ..definitions import SupersetMetadataDef, SupersetDatabaseDef, SupersetDash def mk_metadata(metadata_type: MetadataType) -> SupersetMetadataDef: - return SupersetMetadataDef(type=metadata_type) + return SupersetMetadataDef(type=metadata_type or MetadataType.Asset) def mk_assets_import(databases: list[SupersetDatabaseDef] = None, diff --git a/mitm_tooling/transformation/superset/factories/query.py b/mitm_tooling/transformation/superset/factories/query.py index d70875ee59e5314d9522f89e1f540e6f9ffc7a1c..b3e4685207640c494d33af4a25979f6557f3a2ac 100644 --- a/mitm_tooling/transformation/superset/factories/query.py +++ b/mitm_tooling/transformation/superset/factories/query.py @@ -1,7 +1,7 @@ from mitm_tooling.representation import ColumnName -from ..definitions import QueryObject, QueryContext, DatasourceIdentifier, SupersetAdhocMetric, \ +from ..definitions import QueryObject, QueryContext, DatasetIdentifier, SupersetAdhocMetric, \ QueryObjectFilterClause, FormData, FilterValues, FilterOperator, SupersetPostProcessing, \ - SupersetAdhocColumn + SupersetAdhocColumn, ChartDatasource def mk_query_object_filter_clause(col: ColumnName, op: FilterOperator, @@ -32,6 +32,6 @@ def mk_query_object(columns: list[ColumnName | SupersetAdhocColumn] | None = Non row_limit=row_limit, **kwargs) -def mk_query_context(datasource: DatasourceIdentifier, queries: list[QueryObject], form_data: FormData, +def mk_query_context(datasource: DatasetIdentifier, queries: list[QueryObject], form_data: FormData, **kwargs) -> QueryContext: - return QueryContext(datasource=datasource, queries=queries, form_data=form_data, **kwargs) + return QueryContext(datasource=ChartDatasource.from_identifier(datasource), queries=queries, form_data=form_data, **kwargs) diff --git a/mitm_tooling/transformation/superset/from_intermediate.py b/mitm_tooling/transformation/superset/from_intermediate.py index 5ef8f787208d7407c062b9e48d0600a9a601336d..bd8c6fdfde0e0fdb08dc62f4bc61cf26697f3966 100644 --- a/mitm_tooling/transformation/superset/from_intermediate.py +++ b/mitm_tooling/transformation/superset/from_intermediate.py @@ -1,37 +1,30 @@ -from uuid import UUID - from mitm_tooling.representation import Header from .asset_bundles.asset_bundles import SupersetDatasourceBundle, \ SupersetMitMDatasetBundle -from .common import DBConnectionInfo -from .definitions import DatasourceIdentifierMap -from .definitions.mitm_dataset import MitMDatasetIdentifier +from .asset_bundles.identifier import MitMDatasetIdentifierBundle, DatasourceIdentifierBundle +from .common import DBConnectionInfo, MitMDatasetInfo def header_into_superset_datasource_bundle(header: Header, db_conn_info: DBConnectionInfo, - database_uuid: UUID | None = None, - ds_id_map: DatasourceIdentifierMap | None = None) -> SupersetDatasourceBundle: + identifiers: DatasourceIdentifierBundle | None = None) -> SupersetDatasourceBundle: from ..sql.from_intermediate import header_into_db_meta from .from_sql import db_meta_into_superset_datasource_bundle db_meta = header_into_db_meta(header) return db_meta_into_superset_datasource_bundle(db_meta, db_conn_info, - database_uuid=database_uuid, - ds_id_map=ds_id_map) + identifiers) def header_into_mitm_dataset_bundle(header: Header, db_conn_info: DBConnectionInfo, - dataset_identifier: MitMDatasetIdentifier, - database_uuid: UUID | None = None, - ds_id_map: DatasourceIdentifierMap | None = None) -> SupersetMitMDatasetBundle: + dataset_name: str, + identifiers: MitMDatasetIdentifierBundle | None = None) -> SupersetMitMDatasetBundle: from ..sql.from_intermediate import header_into_db_meta from .from_sql import db_meta_into_mitm_dataset_bundle db_meta = header_into_db_meta(header) + info = MitMDatasetInfo(dataset_name=dataset_name, mitm=header.mitm) return db_meta_into_mitm_dataset_bundle(db_meta, db_conn_info, - dataset_identifier, - header.mitm, - database_uuid=database_uuid, - ds_id_map=ds_id_map) + info, + identifiers) diff --git a/mitm_tooling/transformation/superset/from_sql.py b/mitm_tooling/transformation/superset/from_sql.py index f5b28b006fb593c8118535950f705c525997765f..879e2f257ce5015e02147f86fc0b1da04802c41f 100644 --- a/mitm_tooling/transformation/superset/from_sql.py +++ b/mitm_tooling/transformation/superset/from_sql.py @@ -1,15 +1,13 @@ from uuid import UUID -from mitm_tooling.definition import MITM from mitm_tooling.extraction.sql.data_models import DBMetaInfo from mitm_tooling.extraction.sql.data_models.db_meta import DBMetaInfoBase from mitm_tooling.extraction.sql.db import connect_and_reflect from .asset_bundles import SupersetDatasourceBundle, \ SupersetMitMDatasetBundle -from .common import DBConnectionInfo +from .asset_bundles.identifier import DatasourceIdentifierBundle, MitMDatasetIdentifierBundle +from .common import DBConnectionInfo, MitMDatasetInfo from .common import _mk_engine, SQLiteFileOrEngine -from .definitions import DatasourceIdentifierMap -from .definitions.mitm_dataset import MitMDatasetIdentifier from .factories.database import mk_database from .factories.dataset import mk_dataset from .factories.mitm_dataset import mk_mitm_dataset @@ -17,17 +15,20 @@ from .factories.mitm_dataset import mk_mitm_dataset def db_meta_into_superset_datasource_bundle(db_meta: DBMetaInfoBase, db_conn_info: DBConnectionInfo, - database_uuid: UUID | None = None, - ds_id_map: DatasourceIdentifierMap | None = None) -> SupersetDatasourceBundle: + identifiers: DatasourceIdentifierBundle | None = None) -> SupersetDatasourceBundle: sqlalchemy_uri = db_conn_info.sql_alchemy_uri db_name = db_conn_info.db_name dialect = db_conn_info.dialect_cls() + identifiers = identifiers or DatasourceIdentifierBundle() - database = mk_database(name=db_name, sqlalchemy_uri=sqlalchemy_uri, uniquify_name=True, uuid=database_uuid) + database = mk_database(name=db_name, + sqlalchemy_uri=sqlalchemy_uri, + uniquify_name=True, + uuid=identifiers.database_uuid) database_uuid = database.uuid datasets = [] - ds_id_map = ds_id_map or {} + ds_id_map = identifiers.ds_id_map or {} def ds_uuid(tn: str) -> UUID | None: if (ds_id := ds_id_map.get(tn)) is not None: @@ -42,15 +43,17 @@ def db_meta_into_superset_datasource_bundle(db_meta: DBMetaInfoBase, def db_meta_into_mitm_dataset_bundle(db_meta: DBMetaInfoBase, db_conn_info: DBConnectionInfo, - dataset_identifier: MitMDatasetIdentifier, - mitm: MITM, - database_uuid: UUID | None = None, - ds_id_map: DatasourceIdentifierMap | None = None) -> SupersetMitMDatasetBundle: + dataset_info: MitMDatasetInfo, + identifiers: MitMDatasetIdentifierBundle | None = None) -> SupersetMitMDatasetBundle: + identifiers = identifiers or MitMDatasetIdentifierBundle() + + datasource_bundle = db_meta_into_superset_datasource_bundle(db_meta, db_conn_info, - database_uuid=database_uuid, - ds_id_map=ds_id_map) - mitm_dataset = mk_mitm_dataset(dataset_identifier.dataset_name, mitm, uuid=dataset_identifier.uuid, + identifiers) + + mitm_dataset = mk_mitm_dataset(dataset_info.dataset_name, dataset_info.mitm, + uuid=identifiers.mitm_dataset_uuid, database_uuid=datasource_bundle.database_uuid, table_uuids=datasource_bundle.dataset_uuids) return SupersetMitMDatasetBundle(mitm_dataset=mitm_dataset, datasource_bundle=datasource_bundle) @@ -58,8 +61,9 @@ def db_meta_into_mitm_dataset_bundle(db_meta: DBMetaInfoBase, def db_into_superset_datasource_bundle(arg: SQLiteFileOrEngine, db_conn_info: DBConnectionInfo, - database_uuid: UUID | None = None, - ds_id_map: DatasourceIdentifierMap | None = None) -> SupersetDatasourceBundle: + identifiers: DatasourceIdentifierBundle | None = None) -> SupersetDatasourceBundle: + identifiers = identifiers or DatasourceIdentifierBundle() + engine = _mk_engine(arg) meta, _ = connect_and_reflect(engine, allowed_schemas=[db_conn_info.schema_name]) @@ -67,21 +71,19 @@ def db_into_superset_datasource_bundle(arg: SQLiteFileOrEngine, return db_meta_into_superset_datasource_bundle(db_meta, db_conn_info, - database_uuid=database_uuid, - ds_id_map=ds_id_map) + identifiers) def db_into_mitm_dataset_bundle(arg: SQLiteFileOrEngine, db_conn_info: DBConnectionInfo, - dataset_identifier: MitMDatasetIdentifier, - mitm: MITM, - database_uuid: UUID | None = None, - ds_id_map: DatasourceIdentifierMap | None = None) -> SupersetMitMDatasetBundle: + dataset_info: MitMDatasetInfo, + identifiers: MitMDatasetIdentifierBundle | None = None) -> SupersetMitMDatasetBundle: + identifiers = identifiers or MitMDatasetIdentifierBundle() + datasource_bundle = db_into_superset_datasource_bundle(arg, db_conn_info, - database_uuid=database_uuid, - ds_id_map=ds_id_map) - mitm_dataset = mk_mitm_dataset(dataset_identifier.dataset_name, mitm, uuid=dataset_identifier.uuid, + identifiers) + mitm_dataset = mk_mitm_dataset(dataset_info.dataset_name, dataset_info.mitm, uuid=identifiers.mitm_dataset_uuid, database_uuid=datasource_bundle.database_uuid, table_uuids=datasource_bundle.dataset_uuids) return SupersetMitMDatasetBundle(mitm_dataset=mitm_dataset, datasource_bundle=datasource_bundle) diff --git a/mitm_tooling/transformation/superset/interface.py b/mitm_tooling/transformation/superset/interface.py index 52dbf442ab421d7df970472df54fecf90ccc572f..4a6a4fbed520ab3ce758766ae5ca0aca3204d3f1 100644 --- a/mitm_tooling/transformation/superset/interface.py +++ b/mitm_tooling/transformation/superset/interface.py @@ -1,51 +1,43 @@ from collections.abc import Iterable -from uuid import UUID from mitm_tooling.representation import Header +from .asset_bundles import DatasourceIdentifierBundle from .asset_bundles.asset_bundles import SupersetDatasourceBundle, \ SupersetVisualizationBundle, SupersetMitMDatasetBundle -from .common import DBConnectionInfo -from .definitions import DatasourceIdentifierMap -from .definitions.mitm_dataset import MitMDatasetIdentifier +from .asset_bundles.identifier import MitMDatasetIdentifierBundle +from .common import DBConnectionInfo, MitMDatasetInfo from .from_intermediate import header_into_superset_datasource_bundle, header_into_mitm_dataset_bundle from .visualizations.registry import VisualizationType, mk_visualization -def mk_superset_datasource_bundle(header: Header, db_conn_info: DBConnectionInfo, database_uuid: UUID | None = None, - ds_id_map: DatasourceIdentifierMap | None = None) -> SupersetDatasourceBundle: +def mk_superset_datasource_bundle(header: Header, + db_conn_info: DBConnectionInfo, + identifiers: DatasourceIdentifierBundle | None) -> SupersetDatasourceBundle: return header_into_superset_datasource_bundle(header, - db_conn_info, - database_uuid=database_uuid, - ds_id_map=ds_id_map) + db_conn_info, identifiers) def mk_superset_visualization_bundle(header: Header, - mitm_dataset_identifier: MitMDatasetIdentifier, - ds_id_map: DatasourceIdentifierMap, + mitm_dataset_identifiers: MitMDatasetIdentifierBundle, visualization_types: Iterable[ VisualizationType]) -> SupersetVisualizationBundle: return SupersetVisualizationBundle.combine(*( - mk_visualization(vzt, header, mitm_dataset_identifier, ds_id_map) for vzt + mk_visualization(vzt, header, mitm_dataset_identifiers) for vzt in set(visualization_types) )) def mk_superset_mitm_dataset_bundle(header: Header, db_conn_info: DBConnectionInfo, - mitm_dataset_identifier: MitMDatasetIdentifier, - database_uuid: UUID | None = None, - ds_id_map: DatasourceIdentifierMap | None = None, + dataset_name: str, + identifiers: MitMDatasetIdentifierBundle | None = None, visualization_types: Iterable[ VisualizationType] | None = None) -> SupersetMitMDatasetBundle: mitm_dataset_bundle = header_into_mitm_dataset_bundle(header, db_conn_info, - mitm_dataset_identifier, - database_uuid=database_uuid, - ds_id_map=ds_id_map) + dataset_name, identifiers) if visualization_types is not None: - mdi = mitm_dataset_bundle.mitm_dataset.identifier - ds_id_map = mitm_dataset_bundle.datasource_bundle.placeholder_dataset_identifiers - mitm_dataset_bundle = mitm_dataset_bundle.with_visualization_bundle( - mk_superset_visualization_bundle(header, mdi, ds_id_map, visualization_types)) + mitm_dataset_bundle = mitm_dataset_bundle.replace_visualization_bundle( + mk_superset_visualization_bundle(header, mitm_dataset_bundle.identifiers, visualization_types)) return mitm_dataset_bundle diff --git a/mitm_tooling/transformation/superset/visualizations/abstract.py b/mitm_tooling/transformation/superset/visualizations/abstract.py deleted file mode 100644 index cc19d5da871bbb67a7052296b52a65a56b63d51a..0000000000000000000000000000000000000000 --- a/mitm_tooling/transformation/superset/visualizations/abstract.py +++ /dev/null @@ -1,105 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Self, Type, Callable - -from mitm_tooling.representation import TableName, Header -from ..asset_bundles.asset_bundles import SupersetVisualizationBundle -from ..definitions import DatasourceIdentifier, SupersetChartDef, \ - SupersetDashboardDef, DatasourceIdentifierMap -from ..definitions.mitm_dataset import MitMDatasetIdentifier - -ChartDefCollection = dict[str, SupersetChartDef] -DashboardDefCollection = dict[str, SupersetDashboardDef] - - -class ChartCreator(ABC): - - @abstractmethod - def mk_chart(self, datasource_identifier: DatasourceIdentifier) -> SupersetChartDef: - ... - - -class ChartCollectionCreator(ABC): - - @abstractmethod - def mk_chart_collection(self, ds_id_map: DatasourceIdentifierMap) -> ChartDefCollection: - ... - - @classmethod - def cls_from_dict(cls, chart_creators: dict[str, tuple[TableName, ChartCreator]]) -> Type[Self]: - chart_creators = dict(chart_creators) - - class ConcreteChartCollectionCreator(cls): - - def __init__(self, header: Header): - super().__init__(header) - self._chart_creators = chart_creators - - def mk_chart_collection(self, ds_id_map: DatasourceIdentifierMap) -> ChartDefCollection: - return {name: cc.mk_chart(ds_id_map[table_name]) for name, (table_name, cc) in - self._chart_creators.items()} - - return ConcreteChartCollectionCreator - - -class DashboardCreator(ABC): - - @property - @abstractmethod - def chart_collection_creator(self) -> ChartCollectionCreator: - ... - - @abstractmethod - def mk_dashboard(self, chart_collection: ChartDefCollection) -> SupersetDashboardDef: - ... - - def mk_bundle(self, ds_id_map: DatasourceIdentifierMap) -> SupersetVisualizationBundle: - chart_collection = self.chart_collection_creator.mk_chart_collection(ds_id_map) - return SupersetVisualizationBundle(charts=list(chart_collection.values()), - dashboards=[self.mk_dashboard(chart_collection)]) - - -class VisualizationCreator(ABC): - - def __init__(self, header: Header, **kwargs): - self.header = header - - @property - @abstractmethod - def dashboard_creator_constructors(self) -> dict[str, Callable[ - [Header, MitMDatasetIdentifier], DashboardCreator]]: - ... - - def mk_dashboard_bundles(self, - mitm_dataset_identifier: MitMDatasetIdentifier, - ds_id_map: DatasourceIdentifierMap) -> dict[str, SupersetVisualizationBundle]: - return {name: constr(self.header, mitm_dataset_identifier).mk_bundle(ds_id_map) for name, constr in - self.dashboard_creator_constructors.items()} - - def mk_bundle(self, - mitm_dataset_identifier: MitMDatasetIdentifier, - ds_id_map: DatasourceIdentifierMap) -> SupersetVisualizationBundle: - bundle_map = self.mk_dashboard_bundles(mitm_dataset_identifier, ds_id_map) - return SupersetVisualizationBundle.combine(*bundle_map.values()) - - @classmethod - def wrap_single(cls, dashboard_creator_constr: Callable[ - [Header, MitMDatasetIdentifier], DashboardCreator]) -> Type[Self]: - dashboard_creator_constr_ = dashboard_creator_constr - - class ConcreteVisualizationCreator(cls): - @property - def dashboard_creator_constructors(self) -> dict[str, Callable[ - [Header, MitMDatasetIdentifier], DashboardCreator]]: - return {'default': dashboard_creator_constr_} - - return ConcreteVisualizationCreator - - @classmethod - def empty(cls) -> Type[Self]: - class ConcreteVisualizationCreator(cls): - @property - def dashboard_creator_constructors(self) -> dict[str, Callable[ - [Header, MitMDatasetIdentifier], DashboardCreator]]: - return {} - - return ConcreteVisualizationCreator diff --git a/mitm_tooling/transformation/superset/visualizations/abstract/__init__.py b/mitm_tooling/transformation/superset/visualizations/abstract/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..048b274712982d3d3717592506ecf4f87094c1ec --- /dev/null +++ b/mitm_tooling/transformation/superset/visualizations/abstract/__init__.py @@ -0,0 +1,2 @@ +from .base import ChartCreator, ChartCollectionCreator, DashboardCreator, SupersetChartDef, ChartDefCollection, SupersetDashboardDef +from .mitm import MitMDashboardCreator, VisualizationsCreator, SupersetVisualizationBundle \ No newline at end of file diff --git a/mitm_tooling/transformation/superset/visualizations/abstract/base.py b/mitm_tooling/transformation/superset/visualizations/abstract/base.py new file mode 100644 index 0000000000000000000000000000000000000000..50a2abc0bcd11703946ffe2b8d16349e7b571226 --- /dev/null +++ b/mitm_tooling/transformation/superset/visualizations/abstract/base.py @@ -0,0 +1,71 @@ +from abc import ABC, abstractmethod +from typing import Self, Type, Callable + +from mitm_tooling.representation import TableName, Header, SQLRepresentationSchema +from ...asset_bundles.asset_bundles import SupersetVisualizationBundle +from ...definitions import DatasetIdentifier, SupersetChartDef, \ + SupersetDashboardDef, DatasetIdentifierMap, DashboardIdentifier, MitMDatasetIdentifier + +ChartDefCollection = dict[str, SupersetChartDef] +DashboardDefCollection = dict[str, SupersetDashboardDef] + + +class ChartCreator(ABC): + + @abstractmethod + def mk_chart(self, dataset_identifier: DatasetIdentifier) -> SupersetChartDef: + ... + + +class ChartCollectionCreator(ABC): + + @abstractmethod + def mk_chart_collection(self, ds_id_map: DatasetIdentifierMap) -> ChartDefCollection: + ... + + @classmethod + def cls_from_dict(cls, chart_creators: dict[str, tuple[TableName, ChartCreator]]) -> Type[Self]: + chart_creators = dict(chart_creators) + + class ConcreteChartCollectionCreator(cls): + + def __init__(self, header: Header): + super().__init__(header) + self._chart_creators = chart_creators + + def mk_chart_collection(self, ds_id_map: DatasetIdentifierMap) -> ChartDefCollection: + return {name: cc.mk_chart(ds_id_map[table_name]) for name, (table_name, cc) in + self._chart_creators.items()} + + return ConcreteChartCollectionCreator + + +class DashboardCreator(ABC): + + @property + def viz_name(self) -> str: + return self.__class__.__name__ + + @property + @abstractmethod + def chart_collection_creator(self) -> ChartCollectionCreator: + ... + + @abstractmethod + def mk_dashboard(self, + chart_collection: ChartDefCollection, + dashboard_identifier: DashboardIdentifier| None = None) -> SupersetDashboardDef: + ... + + def mk_bundle(self, + ds_id_map: DatasetIdentifierMap, + dashboard_identifier: DashboardIdentifier | None = None, collection_name: str = 'default') -> SupersetVisualizationBundle: + chart_collection = self.chart_collection_creator.mk_chart_collection(ds_id_map) + dashboard = self.mk_dashboard(chart_collection, dashboard_identifier) + viz_name = self.viz_name + return SupersetVisualizationBundle(charts=list(chart_collection.values()), + dashboards=[dashboard], + viz_collections={ + collection_name: {viz_name: dashboard.identifier}}) + + diff --git a/mitm_tooling/transformation/superset/visualizations/abstract/mitm.py b/mitm_tooling/transformation/superset/visualizations/abstract/mitm.py new file mode 100644 index 0000000000000000000000000000000000000000..73ba5833f0edf0d6a22b374f0f6d948818b76d33 --- /dev/null +++ b/mitm_tooling/transformation/superset/visualizations/abstract/mitm.py @@ -0,0 +1,68 @@ +from abc import ABC, abstractmethod +from typing import Self, Type, Callable + +from mitm_tooling.representation import Header, SQLRepresentationSchema, mk_sql_rep_schema +from .base import DashboardCreator +from ...asset_bundles.asset_bundles import SupersetVisualizationBundle +from ...asset_bundles.identifier import MitMDatasetIdentifierBundle +from ...definitions import MitMDatasetIdentifier + +DashboardCreatorConstructor = Callable[[Header, MitMDatasetIdentifier | None], DashboardCreator] + + +class MitMDashboardCreator(DashboardCreator, ABC): + def __init__(self, header: Header, sql_rep_schema: SQLRepresentationSchema | None = None, **kwargs): + super().__init__(**kwargs) + self.header = header + self.sql_rep_schema = sql_rep_schema or mk_sql_rep_schema(header) + + +class VisualizationsCreator(ABC): + + def __init__(self, header: Header, **kwargs): + super().__init__(**kwargs) + self.header = header + + @property + @abstractmethod + def dashboard_creator_constructors(self) -> dict[str, DashboardCreatorConstructor]: + ... + + def mk_dashboard_bundles(self, mitm_dataset_identifiers: MitMDatasetIdentifierBundle) -> dict[ + str, SupersetVisualizationBundle]: + creators = {name: constr(self.header, mitm_dataset_identifiers.mitm_dataset) for name, constr in + self.dashboard_creator_constructors.items()} + + def get_dash_id(creator_name, viz_name): + return mitm_dataset_identifiers.viz_id_map.get(creator_name, {}).get(viz_name) + + ds_id_map = mitm_dataset_identifiers.ds_id_map + bundles = {name: creator.mk_bundle(ds_id_map, get_dash_id(name, creator.viz_name), collection_name=name) for + name, creator in creators.items()} + return bundles + + def mk_bundle(self, + mitm_dataset_identifiers: MitMDatasetIdentifierBundle) -> SupersetVisualizationBundle: + bundle_map = self.mk_dashboard_bundles(mitm_dataset_identifiers) + return SupersetVisualizationBundle.combine(*bundle_map.values()) + + @classmethod + def wrap_single(cls, name: str, dashboard_creator_constr: DashboardCreatorConstructor) -> Type[Self]: + dashboard_creator_constr_ = dashboard_creator_constr + name_ = name + + class ConcreteVisualizationCreator(cls): + @property + def dashboard_creator_constructors(self) -> dict[str, DashboardCreatorConstructor]: + return {name_: dashboard_creator_constr_} + + return ConcreteVisualizationCreator + + @classmethod + def empty(cls) -> Type[Self]: + class ConcreteVisualizationCreator(cls): + @property + def dashboard_creator_constructors(self) -> dict[str, DashboardCreatorConstructor]: + return {} + + return ConcreteVisualizationCreator diff --git a/mitm_tooling/transformation/superset/visualizations/maed/charts.py b/mitm_tooling/transformation/superset/visualizations/maed/charts.py index 5b5895adff32013ea8b52738e4f8cb21d97fb742..ecba5b7439d916671017e9d6a00bfe0c3ae37dc3 100644 --- a/mitm_tooling/transformation/superset/visualizations/maed/charts.py +++ b/mitm_tooling/transformation/superset/visualizations/maed/charts.py @@ -5,7 +5,7 @@ from mitm_tooling.definition import get_mitm_def, ConceptName, MITM, RelationNam from mitm_tooling.representation import SQLRepresentationSchema, Header, mk_sql_rep_schema from mitm_tooling.utilities.identifiers import naive_pluralize from ..abstract import ChartDefCollection, ChartCollectionCreator, ChartCreator -from ...definitions import FilterOperator, DatasourceIdentifier, SupersetChartDef, DatasourceIdentifierMap +from ...definitions import FilterOperator, DatasetIdentifier, SupersetChartDef, DatasetIdentifierMap from ...factories.core import mk_adhoc_filter from ...factories.generic_charts import mk_pie_chart, mk_time_series_bar_chart, mk_avg_count_time_series_chart @@ -26,12 +26,12 @@ class ConceptCountTS(ChartCreator): assert set(self.groupby_relations) <= defined_relations assert self.time_relation in self.relations.relation_names - def mk_chart(self, datasource_identifier: DatasourceIdentifier) -> SupersetChartDef: + def mk_chart(self, dataset_identifier: DatasetIdentifier) -> SupersetChartDef: filters = [mk_adhoc_filter('kind', FilterOperator.EQUALS, self.props.key)] if self.props.is_sub else None return mk_time_series_bar_chart(f'{self.concept.title()} Counts', - datasource_identifier, + dataset_identifier, 'type', MITMDataType.Text, x_col=self.time_relation, @@ -46,9 +46,9 @@ class RelationPie(ChartCreator): self.relation = relation assert relation in get_mitm_def(MITM.MAED).get_relations(concept).relation_names - def mk_chart(self, datasource_identifier: DatasourceIdentifier) -> SupersetChartDef: + def mk_chart(self, dataset_identifier: DatasetIdentifier) -> SupersetChartDef: return mk_pie_chart(naive_pluralize(self.relation).title(), - datasource_identifier, + dataset_identifier, col=self.relation, dt=MITMDataType.Text) @@ -71,9 +71,9 @@ class ConceptTypeAvgCountTS(ChartCreator): assert set(self.groupby_relations) <= defined_relations assert self.time_relation in self.relations.relation_names - def mk_chart(self, datasource_identifier: DatasourceIdentifier) -> SupersetChartDef: + def mk_chart(self, dataset_identifier: DatasetIdentifier) -> SupersetChartDef: return mk_avg_count_time_series_chart(f'{self.type_name.title()} Time Series', - datasource_identifier, + dataset_identifier, groupby_cols=self.groupby_relations, time_col=self.time_relation, ) @@ -87,7 +87,7 @@ class ConceptTypesAvgCountTSCollection(ChartCollectionCreator): self.concept = concept assert self.concept in self.sql_rep_schema.type_tables - def mk_chart_collection(self, ds_id_map: DatasourceIdentifierMap) -> ChartDefCollection: + def mk_chart_collection(self, ds_id_map: DatasetIdentifierMap) -> ChartDefCollection: charts: ChartDefCollection = {} for type_name, tbl in self.sql_rep_schema.type_tables[self.concept].items(): ds_id = ds_id_map[tbl.name] @@ -103,7 +103,7 @@ class BaselineMAEDCharts(ChartCollectionCreator): self.sql_rep_schema = sql_rep_schema or mk_sql_rep_schema(header) self.mitm_def = header.mitm_def - def mk_chart_collection(self, ds_id_map: DatasourceIdentifierMap) -> ChartDefCollection: + def mk_chart_collection(self, ds_id_map: DatasetIdentifierMap) -> ChartDefCollection: charts = {} observation_table_name = self.sql_rep_schema.concept_tables['observation'].name # alternatively, mk_table_name('observation') diff --git a/mitm_tooling/transformation/superset/visualizations/maed/dashboards.py b/mitm_tooling/transformation/superset/visualizations/maed/dashboards.py index c4c7954fa1b9b5ea75b31e65cb79c200900688ae..7dfd8e9037e847de1032d19ac3f49371e6263a6c 100644 --- a/mitm_tooling/transformation/superset/visualizations/maed/dashboards.py +++ b/mitm_tooling/transformation/superset/visualizations/maed/dashboards.py @@ -1,26 +1,27 @@ from mitm_tooling.representation import Header, SQLRepresentationSchema, mk_sql_rep_schema -from mitm_tooling.transformation.superset.definitions import SupersetDashboardDef, DatasourceIdentifierMap +from mitm_tooling.transformation.superset.definitions import SupersetDashboardDef, DatasetIdentifierMap, \ + DashboardIdentifier from mitm_tooling.transformation.superset.definitions.mitm_dataset import MitMDatasetIdentifier from mitm_tooling.transformation.superset.factories.custom_charts import mk_maed_custom_chart from mitm_tooling.transformation.superset.factories.dashboard import mk_dashboard_chart, mk_dashboard_def from mitm_tooling.transformation.superset.visualizations.abstract import DashboardCreator, \ - ChartCollectionCreator, ChartDefCollection + ChartCollectionCreator, ChartDefCollection, MitMDashboardCreator from mitm_tooling.transformation.superset.visualizations.maed.charts import BaselineMAEDCharts +from mitm_tooling.utilities.identifiers import mk_uuid from mitm_tooling.utilities.python_utils import take_first -class BaselineMAEDDashboard(DashboardCreator): - - def __init__(self, header: Header, sql_rep_schema: SQLRepresentationSchema | None = None): - super().__init__() - self.header = header - self.sql_rep_schema = sql_rep_schema or mk_sql_rep_schema(header) +class BaselineMAEDDashboard(MitMDashboardCreator): @property def chart_collection_creator(self) -> ChartCollectionCreator: return BaselineMAEDCharts(self.header, self.sql_rep_schema) - def mk_dashboard(self, chart_collection: ChartDefCollection) -> SupersetDashboardDef: + def mk_dashboard(self, + chart_collection: ChartDefCollection, + dashboard_identifier: DashboardIdentifier| None = None) -> SupersetDashboardDef: + dashboard_identifier = dashboard_identifier or DashboardIdentifier() + x = take_first(self.header.as_dict['measurement']) chart_grid = [ @@ -28,21 +29,20 @@ class BaselineMAEDDashboard(DashboardCreator): mk_dashboard_chart(chart_uuid=chart_collection['event-count-ts'].uuid, width=4, height=50), mk_dashboard_chart(chart_uuid=chart_collection['measurement-count-ts'].uuid, width=4, height=50)], [mk_dashboard_chart(chart_uuid=chart_collection[f'measurement-{x}-ts'].uuid, width=12, height=100)]] - return mk_dashboard_def('MAED Dashboard', + return mk_dashboard_def(dashboard_identifier.dashboard_title or 'MAED Dashboard', chart_grid=chart_grid, - description='A rudimentary dashboard to view MAED data.') + description='A rudimentary dashboard to view MAED data.', + uuid=dashboard_identifier.uuid) -class ExperimentalMAEDDashboard(DashboardCreator): +class ExperimentalMAEDDashboard(MitMDashboardCreator): def __init__(self, header: Header, - mitm_dataset_identifier: MitMDatasetIdentifier, + mitm_dataset_identifier: MitMDatasetIdentifier | None = None, sql_rep_schema: SQLRepresentationSchema | None = None): - super().__init__() - self.header = header - self.mitm_dataset_identifier = mitm_dataset_identifier - self.sql_rep_schema = sql_rep_schema or mk_sql_rep_schema(header) + super().__init__(header, sql_rep_schema) + self.mitm_dataset_identifier = mitm_dataset_identifier or MitMDatasetIdentifier() @property def chart_collection_creator(self) -> ChartCollectionCreator: @@ -50,7 +50,7 @@ class ExperimentalMAEDDashboard(DashboardCreator): class CustomChartCollectionCreator(ChartCollectionCreator): - def mk_chart_collection(self, ds_id_map: DatasourceIdentifierMap) -> ChartDefCollection: + def mk_chart_collection(self, ds_id_map: DatasetIdentifierMap) -> ChartDefCollection: return { 'custom': mk_maed_custom_chart('Custom MAED Chart', mitm_dataset_identifier, @@ -58,8 +58,13 @@ class ExperimentalMAEDDashboard(DashboardCreator): return CustomChartCollectionCreator() - def mk_dashboard(self, chart_collection: ChartDefCollection) -> SupersetDashboardDef: + def mk_dashboard(self, + chart_collection: ChartDefCollection, + dashboard_identifier: DashboardIdentifier| None = None) -> SupersetDashboardDef: + dashboard_identifier = dashboard_identifier or DashboardIdentifier() + chart_grid = [[mk_dashboard_chart(chart_uuid=chart_collection['custom'].uuid, width=12, height=400)]] - return mk_dashboard_def('Experimental MAED Dashboard', + return mk_dashboard_def(dashboard_identifier.dashboard_title or 'Experimental MAED Dashboard', chart_grid, - description='An experimental dashboard to view MAED data.') + description='An experimental dashboard to view MAED data.', + uuid=dashboard_identifier.uuid) diff --git a/mitm_tooling/transformation/superset/visualizations/maed/registry.py b/mitm_tooling/transformation/superset/visualizations/maed/registry.py index 3f0602e06e1f4c4e27a38aa017f04b1b2e60ae91..46aaeda1f5165871094d64f9dd31fc51b0f1cb32 100644 --- a/mitm_tooling/transformation/superset/visualizations/maed/registry.py +++ b/mitm_tooling/transformation/superset/visualizations/maed/registry.py @@ -1,7 +1,7 @@ from enum import StrEnum from typing import Type -from mitm_tooling.transformation.superset.visualizations.abstract import VisualizationCreator +from mitm_tooling.transformation.superset.visualizations.abstract import VisualizationsCreator from mitm_tooling.transformation.superset.visualizations.maed.dashboards import BaselineMAEDDashboard, \ ExperimentalMAEDDashboard @@ -11,8 +11,10 @@ class MAEDVisualizationType(StrEnum): Experimental = 'experimental' -maed_visualization_creators: dict[MAEDVisualizationType, Type[VisualizationCreator]] = { - MAEDVisualizationType.Baseline: VisualizationCreator.wrap_single(lambda h, mdi: BaselineMAEDDashboard(h)), - MAEDVisualizationType.Experimental: VisualizationCreator.wrap_single(lambda h, mdi: ExperimentalMAEDDashboard(h, - mdi)), +maed_visualization_creators: dict[MAEDVisualizationType, Type[VisualizationsCreator]] = { + MAEDVisualizationType.Baseline: VisualizationsCreator.wrap_single('baseline', + lambda h, mdi: BaselineMAEDDashboard(h)), + MAEDVisualizationType.Experimental: VisualizationsCreator.wrap_single('experimental', + lambda h, mdi: ExperimentalMAEDDashboard(h, + mdi)), } diff --git a/mitm_tooling/transformation/superset/visualizations/registry.py b/mitm_tooling/transformation/superset/visualizations/registry.py index 957c299c5a5eb3228a1b4c226e1d9e15d2f5893b..48da14020b4396adf0a95aa7933a6eadd7674726 100644 --- a/mitm_tooling/transformation/superset/visualizations/registry.py +++ b/mitm_tooling/transformation/superset/visualizations/registry.py @@ -3,9 +3,8 @@ from typing import Type from mitm_tooling.definition import MITM from mitm_tooling.representation import Header from .maed.registry import MAEDVisualizationType, maed_visualization_creators -from ..definitions.mitm_dataset import MitMDatasetIdentifier -from ..visualizations.abstract import VisualizationCreator, SupersetVisualizationBundle -from ..definitions import DatasourceIdentifierMap +from ..asset_bundles.identifier import MitMDatasetIdentifierBundle +from ..visualizations.abstract import VisualizationsCreator, SupersetVisualizationBundle VisualizationType = MAEDVisualizationType | None @@ -15,7 +14,7 @@ mitm_visualization_creators = { def get_mitm_visualization_creator(mitm: MITM, visualization_type: VisualizationType) -> Type[ - VisualizationCreator] | None: + VisualizationsCreator] | None: if creators := mitm_visualization_creators.get(mitm): if (creator := creators.get(visualization_type)) is not None: return creator @@ -23,7 +22,6 @@ def get_mitm_visualization_creator(mitm: MITM, visualization_type: Visualization def mk_visualization(visualization_type: VisualizationType, header: Header, - mitm_dataset_identifier: MitMDatasetIdentifier, - ds_id_map: DatasourceIdentifierMap) -> SupersetVisualizationBundle | None: + mitm_dataset_identifiers: MitMDatasetIdentifierBundle) -> SupersetVisualizationBundle | None: if (creator := get_mitm_visualization_creator(header.mitm, visualization_type)) is not None: - return creator(header).mk_bundle(mitm_dataset_identifier, ds_id_map) + return creator(header).mk_bundle(mitm_dataset_identifiers) diff --git a/mitm_tooling/utilities/python_utils.py b/mitm_tooling/utilities/python_utils.py index d840fdd315a88826cfebad115ee14640c9e7b386..da5f4cf3d0b2a4a7c819ffb7bfbaacc9a68163d2 100644 --- a/mitm_tooling/utilities/python_utils.py +++ b/mitm_tooling/utilities/python_utils.py @@ -120,6 +120,23 @@ def unique(*its: Iterable[T]) -> list[T]: def unique(*its: Iterable[Union[*Ts]]) -> list[Union[Unpack[Ts]]]: return list(set(itertools.chain(*its))) +def deep_merge_dicts(*dicts: dict) -> dict: + """ + Recursively merges multiple dictionaries and returns the result. + Values in later dictionaries override values in earlier ones. + """ + result = {} + for d in dicts: + for key, value in d.items(): + if ( + key in result + and isinstance(result[key], dict) + and isinstance(value, dict) + ): + result[key] = deep_merge_dicts(result[key], value) + else: + result[key] = value + return result class ExtraInfoExc(Exception): def __init__(self, msg=None): diff --git a/pyproject.toml b/pyproject.toml index d973f69aad1ca223b5a8d680691d4318da34201e..006bad0f6e607f144be40d3a648f55750154d1d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mitm-tooling" -version = "0.4.9" +version = "0.5.0" description = "" authors = [{ name = "Leah Tacke genannt Unterberg", email = "l.tgu@pads.rwth-aachen.de" }] requires-python = ">=3.11,<3.14" diff --git a/test/something.py b/test/something.py index f48825fe2fc0f27fd10b2bd3f242f71511896254..74047c8734910f9b29c0938e07e9486753352ebb 100644 --- a/test/something.py +++ b/test/something.py @@ -4,7 +4,6 @@ import unittest from pydantic import AnyUrl from mitm_tooling.transformation.superset.common import DBConnectionInfo -from mitm_tooling.transformation.superset.definitions.mitm_dataset import MitMDatasetIdentifier from mitm_tooling.transformation.superset.visualizations.maed.registry import MAEDVisualizationType from mitm_tooling.utilities.identifiers import name_plus_uuid @@ -68,33 +67,44 @@ class MyTestCase(unittest.TestCase): from mitm_tooling.io import importing, MITM syn = importing.read_zip('synthetic.maed', MITM.MAED, header_only=True) from mitm_tooling.transformation.superset import mk_superset_mitm_dataset_bundle, write_superset_import_as_zip + from mitm_tooling.transformation.superset.definitions import MetadataType dataset_bundle = mk_superset_mitm_dataset_bundle(syn.header, DBConnectionInfo( sql_alchemy_uri=AnyUrl( 'sqlite://synthetic-variation.sqlite'), explicit_db_name='SyntheticExampleDB', schema_name='main'), - MitMDatasetIdentifier( - dataset_name=name_plus_uuid('SyntheticExampleDataset')) + name_plus_uuid( + 'SyntheticExampleDataset'), ) - write_superset_import_as_zip('superset_datasource_import', dataset_bundle.datasource_bundle.to_import()) + write_superset_import_as_zip('superset_datasource_import', + dataset_bundle.datasource_bundle.to_import(MetadataType.SqlaTable)) write_superset_import_as_zip('superset_mitm_dataset_import', dataset_bundle.to_import()) - def test_superset_viz_assets(self): + def test_superset_mitm_dataset(self): from mitm_tooling.io import importing, MITM syn = importing.read_zip('synthetic.maed', MITM.MAED, header_only=True) from mitm_tooling.transformation.superset import mk_superset_mitm_dataset_bundle, write_superset_import_as_zip + from mitm_tooling.transformation.superset.definitions import MetadataType full_bundle = mk_superset_mitm_dataset_bundle(syn.header, DBConnectionInfo( sql_alchemy_uri=AnyUrl( - 'sqlite://synthetic-variation.sqlite'), - explicit_db_name='SyntheticExampleDB', - schema_name='main'), - MitMDatasetIdentifier( - dataset_name=name_plus_uuid('SyntheticExampleDataset')), + 'postgresql://mitm-pg-user:superweirdpasswordpleasedonotcrack@mitm-db:5432/mitm_db'), + explicit_db_name='MitM DB', + schema_name='myname_0_39f207b32588'), + name_plus_uuid( + 'SyntheticExampleDataset'), visualization_types=[MAEDVisualizationType.Baseline] ) + write_superset_import_as_zip('superset_mitm_dataset_db_import', + full_bundle.datasource_bundle.to_import(MetadataType.Database)) + write_superset_import_as_zip('superset_mitm_dataset_datasource_import', + full_bundle.datasource_bundle.to_import(MetadataType.SqlaTable)) + write_superset_import_as_zip('superset_mitm_dataset_chart_import', + full_bundle.visualization_bundle.to_import(MetadataType.Slice)) + write_superset_import_as_zip('superset_mitm_dataset_dashboard_import', + full_bundle.visualization_bundle.to_import(MetadataType.Dashboard)) write_superset_import_as_zip('superset_mitm_dataset_with_viz_import', full_bundle.to_import()) write_superset_import_as_zip('superset_viz_import', full_bundle.visualization_bundle.to_import())