diff --git a/dbt_semantic_interfaces/implementations/export.py b/dbt_semantic_interfaces/implementations/export.py index d3626ef9..62a58e22 100644 --- a/dbt_semantic_interfaces/implementations/export.py +++ b/dbt_semantic_interfaces/implementations/export.py @@ -3,6 +3,7 @@ from typing import Optional from typing_extensions import override +from pydantic import Field from dbt_semantic_interfaces.implementations.base import HashableBaseModel from dbt_semantic_interfaces.protocols import ProtocolHint @@ -10,6 +11,18 @@ from dbt_semantic_interfaces.type_enums.export_destination_type import ExportDestinationType +class PydanticExportConfig(HashableBaseModel, ProtocolHint[ExportConfig]): + """Pydantic implementation of ExportConfig.""" + + @override + def _implements_protocol(self) -> ExportConfig: + return self + + export_as: ExportDestinationType + schema_name: Optional[str] = Field(alias="schema") # `schema` is a BaseModel attribute + alias: Optional[str] = None + + class PydanticExport(HashableBaseModel, ProtocolHint[Export]): """Pydantic implementation of Export.""" @@ -19,11 +32,3 @@ def _implements_protocol(self) -> Export: name: str config: PydanticExportConfig - - -class PydanticExportConfig(HashableBaseModel, ProtocolHint[ExportConfig]): - """Pydantic implementation of ExportConfig.""" - - export_as: ExportDestinationType - schema: Optional[str] = None - alias: Optional[str] = None diff --git a/dbt_semantic_interfaces/parsing/schemas.py b/dbt_semantic_interfaces/parsing/schemas.py index 9144c2b2..61f3e79e 100644 --- a/dbt_semantic_interfaces/parsing/schemas.py +++ b/dbt_semantic_interfaces/parsing/schemas.py @@ -39,6 +39,10 @@ time_dimension_type_values = ["TIME", "time"] +export_destination_type_values = ["TABLE", "VIEW"] +export_destination_type_values += [x.lower() for x in export_destination_type_values] + + filter_schema = { "$id": "filter_schema", "oneOf": [ @@ -288,6 +292,29 @@ "required": ["time_spine_table_configurations"], } +export_config_schema = { + "$id": "export_config_schema", + "type": "object", + "properties": { + "export_as": {"enum": export_destination_type_values}, + "schema": {"type": "string"}, + "alias": {"type": "string"}, + }, + "required": ["export_as"], + "additionalProperties": False, +} + + +export_schema = { + "$id": "export_schema", + "type": "object", + "properties": { + "name": {"type": "string"}, + "config": {"$ref": "export_config_schema"}, + }, + "required": ["name", "config"], + "additionalProperties": False, +} saved_query_schema = { "$id": "saved_query_schema", @@ -305,6 +332,7 @@ }, "where": {"$ref": "filter_schema"}, "label": {"type": "string"}, + "exports": {"type": "array", "items": {"$ref": "export_schema"}}, }, "required": ["name", "metrics"], "additionalProperties": False, @@ -355,6 +383,8 @@ node_relation_schema["$id"]: node_relation_schema, semantic_model_defaults_schema["$id"]: semantic_model_defaults_schema, time_spine_table_configuration_schema["$id"]: time_spine_table_configuration_schema, + export_schema["$id"]: export_schema, + export_config_schema["$id"]: export_config_schema, } resources: List[Tuple[str, Resource]] = [(str(k), DRAFT7.create_resource(v)) for k, v in schema_store.items()] diff --git a/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/saved_queries.yaml b/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/saved_queries.yaml index e822550f..f349a2e7 100644 --- a/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/saved_queries.yaml +++ b/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/saved_queries.yaml @@ -10,3 +10,9 @@ saved_query: - Dimension('listing__capacity_latest') where: - "{{ Dimension('listing__capacity_latest') }} > 3" + exports: + - name: bookings + config: + export_as: table + schema: exports_schema + alias: bookings_export_table \ No newline at end of file diff --git a/tests/parsing/test_saved_query_parsing.py b/tests/parsing/test_saved_query_parsing.py index 95b0e6aa..7edf285a 100644 --- a/tests/parsing/test_saved_query_parsing.py +++ b/tests/parsing/test_saved_query_parsing.py @@ -7,6 +7,7 @@ from tests.example_project_configuration import ( EXAMPLE_PROJECT_CONFIGURATION_YAML_CONFIG_FILE, ) +from dbt_semantic_interfaces.type_enums.export_destination_type import ExportDestinationType def test_saved_query_metadata_parsing() -> None: @@ -134,3 +135,43 @@ def test_saved_query_where() -> None: assert saved_query.where is not None assert len(saved_query.where.where_filters) == 1 assert where == saved_query.where.where_filters[0].where_sql_template + + +def test_saved_query_exports() -> None: + """Test for parsing exports referenced in a saved query.""" + yaml_contents = textwrap.dedent( + """\ + saved_query: + name: test_exports + metrics: + - test_metric_a + exports: + - name: test_exports1 + config: + export_as: VIEW + schema: my_schema + alias: my_view_name + - name: test_exports2 + config: + export_as: table + """ + ) + file = YamlConfigFile(filepath="inline_for_test", contents=yaml_contents) + + build_result = parse_yaml_files_to_semantic_manifest(files=[file, EXAMPLE_PROJECT_CONFIGURATION_YAML_CONFIG_FILE]) + + assert len(build_result.semantic_manifest.saved_queries) == 1 + saved_query = build_result.semantic_manifest.saved_queries[0] + assert len(saved_query.exports) == 2 + names_to_exports = {export.name: export for export in saved_query.exports} + assert set(names_to_exports.keys()) == {"test_exports1", "test_exports2"} + + export1_config = names_to_exports["test_exports1"].config + assert export1_config.export_as == ExportDestinationType.VIEW + assert export1_config.schema_name == "my_schema" + assert export1_config.alias == "my_view_name" + + export2_config = names_to_exports["test_exports2"].config + assert export2_config.export_as == ExportDestinationType.TABLE + assert export2_config.schema_name is None + assert export2_config.alias is None diff --git a/tests/test_implements_satisfy_protocols.py b/tests/test_implements_satisfy_protocols.py index 6bc1a6e3..4ee80cda 100644 --- a/tests/test_implements_satisfy_protocols.py +++ b/tests/test_implements_satisfy_protocols.py @@ -31,6 +31,7 @@ from dbt_semantic_interfaces.implementations.semantic_manifest import ( PydanticSemanticManifest, ) +from dbt_semantic_interfaces.implementations.export import PydanticExport from dbt_semantic_interfaces.implementations.semantic_model import PydanticSemanticModel from dbt_semantic_interfaces.implementations.time_spine_table_configuration import ( PydanticTimeSpineTableConfiguration, @@ -124,6 +125,7 @@ description=OPTIONAL_STR_STRATEGY, metadata=OPTIONAL_METADATA_STRATEGY, label=OPTIONAL_STR_STRATEGY, + exports=from_type(List[PydanticExport]), )