From ccd66653eef765ec2b818195e172f99276e97890 Mon Sep 17 00:00:00 2001 From: Courtney Holcomb Date: Tue, 4 Jun 2024 21:26:19 -0700 Subject: [PATCH 1/4] WIP - enable object entity syntax in metric filters --- .../where_filter/parameter_set_factory.py | 17 ++++++++++++--- .../where_filter/where_filter_entity.py | 16 ++------------ .../where_filter/where_filter_stubs.py | 21 +++++++++++++++++++ 3 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 dbt_semantic_interfaces/parsing/where_filter/where_filter_stubs.py diff --git a/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py b/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py index ce40343a..67105e0c 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py +++ b/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py @@ -1,4 +1,4 @@ -from typing import Optional, Sequence +from typing import Optional, Sequence, Union from dbt_semantic_interfaces.call_parameter_sets import ( DimensionCallParameterSet, @@ -12,6 +12,7 @@ METRIC_TIME_ELEMENT_NAME, is_metric_time_name, ) +from dbt_semantic_interfaces.parsing.where_filter.where_filter_stubs import EntityStub from dbt_semantic_interfaces.references import ( DimensionReference, EntityReference, @@ -106,7 +107,7 @@ def create_entity(entity_name: str, entity_path: Sequence[str] = ()) -> EntityCa ) @staticmethod - def create_metric(metric_name: str, group_by: Sequence[str] = ()) -> MetricCallParameterSet: + def create_metric(metric_name: str, group_by: Sequence[Union[str, EntityStub]] = ()) -> MetricCallParameterSet: """Gets called by Jinja when rendering {{ Metric(...) }}.""" if not group_by: raise ParseWhereFilterException( @@ -115,5 +116,15 @@ def create_metric(metric_name: str, group_by: Sequence[str] = ()) -> MetricCallP ) return MetricCallParameterSet( metric_reference=MetricReference(element_name=metric_name), - group_by=tuple([LinkableElementReference(element_name=group_by_name) for group_by_name in group_by]), + group_by=tuple( + [ + LinkableElementReference( + # TODO: add entity_links + group_by_item + if isinstance(group_by_item, str) + else group_by_item.element_name + ) + for group_by_item in group_by + ] + ), ) diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py index b4d346a2..57f8cb12 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py @@ -12,27 +12,15 @@ from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( ParameterSetFactory, ) +from dbt_semantic_interfaces.parsing.where_filter.where_filter_stubs import EntityStub from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint from dbt_semantic_interfaces.protocols.query_interface import ( - QueryInterfaceEntity, QueryInterfaceEntityFactory, QueryInterfaceMetric, QueryInterfaceMetricFactory, ) -class EntityStub(ProtocolHint[QueryInterfaceEntity]): - """An Entity implementation that just satisfies the protocol. - - QueryInterfaceEntity currently has no methods and the parameter set is created in the factory. - So, there is nothing to do here. - """ - - @override - def _implements_protocol(self) -> QueryInterfaceEntity: - return self - - class MetricStub(ProtocolHint[QueryInterfaceMetric]): """A Metric implementation that just satisfies the protocol. @@ -60,7 +48,7 @@ def __init__(self) -> None: # noqa def create(self, entity_name: str, entity_path: Sequence[str] = ()) -> EntityStub: """Gets called by Jinja when rendering {{ Entity(...) }}.""" self.entity_call_parameter_sets.append(ParameterSetFactory.create_entity(entity_name, entity_path)) - return EntityStub() + return EntityStub(element_name=entity_name, entity_links=entity_path) class WhereFilterMetricFactory(ProtocolHint[QueryInterfaceMetricFactory]): diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_stubs.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_stubs.py new file mode 100644 index 00000000..ac31c2d9 --- /dev/null +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_stubs.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass +from typing import Sequence +from typing_extensions import override +from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint +from dbt_semantic_interfaces.protocols.query_interface import QueryInterfaceEntity + + +@dataclass +class EntityStub(ProtocolHint[QueryInterfaceEntity]): + """An Entity implementation that just satisfies the protocol. + + QueryInterfaceEntity currently has no methods and the parameter set is created in the factory. + So, there is nothing to do here. + """ + + element_name: str + entity_links: Sequence[str] = () + + @override + def _implements_protocol(self) -> QueryInterfaceEntity: + return self From 1c6e410261a5b2b930b32a0e14ce6915f029da3b Mon Sep 17 00:00:00 2001 From: Courtney Holcomb Date: Wed, 5 Jun 2024 11:01:58 -0700 Subject: [PATCH 2/4] WIP --- Makefile | 2 +- .../where_filter/where_filter_entity.py | 21 ++------- .../where_filter/where_filter_stubs.py | 44 ++++++++++++++++++- .../where_filter_time_dimension.py | 29 ++++++------ .../type_enums/__init__.py | 1 + 5 files changed, 62 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index ebb91ee9..7127e3cc 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ test: export FORMAT_JSON_LOGS="1" && hatch -v run dev-env:pytest -n auto tests lint: - hatch run dev-env:pre-commit run --show-diff-on-failure --color=always --all-files + hatch run dev-env:pre-commit run --color=always --all-files json_schema: hatch run dev-env:python dbt_semantic_interfaces/parsing/generate_json_schema_file.py diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py index 57f8cb12..31616e81 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py @@ -8,33 +8,20 @@ EntityCallParameterSet, MetricCallParameterSet, ) -from dbt_semantic_interfaces.errors import InvalidQuerySyntax from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( ParameterSetFactory, ) -from dbt_semantic_interfaces.parsing.where_filter.where_filter_stubs import EntityStub +from dbt_semantic_interfaces.parsing.where_filter.where_filter_stubs import ( + EntityStub, + MetricStub, +) from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint from dbt_semantic_interfaces.protocols.query_interface import ( QueryInterfaceEntityFactory, - QueryInterfaceMetric, QueryInterfaceMetricFactory, ) -class MetricStub(ProtocolHint[QueryInterfaceMetric]): - """A Metric implementation that just satisfies the protocol. - - QueryInterfaceMetric currently has no methods and the parameter set is created in the factory. - """ - - @override - def _implements_protocol(self) -> QueryInterfaceMetric: - return self - - def descending(self, _is_descending: bool) -> QueryInterfaceMetric: # noqa: D - raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec") - - class WhereFilterEntityFactory(ProtocolHint[QueryInterfaceEntityFactory]): """Executes in the Jinja sandbox to produce parameter sets and append them to a list.""" diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_stubs.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_stubs.py index ac31c2d9..90c57e75 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_stubs.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_stubs.py @@ -1,8 +1,16 @@ from dataclasses import dataclass -from typing import Sequence +from typing import Optional, Sequence + from typing_extensions import override + +from dbt_semantic_interfaces.errors import InvalidQuerySyntax from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint -from dbt_semantic_interfaces.protocols.query_interface import QueryInterfaceEntity +from dbt_semantic_interfaces.protocols.query_interface import ( + QueryInterfaceEntity, + QueryInterfaceMetric, + QueryInterfaceTimeDimension, +) +from dbt_semantic_interfaces.type_enums import DatePart, TimeGranularity @dataclass @@ -19,3 +27,35 @@ class EntityStub(ProtocolHint[QueryInterfaceEntity]): @override def _implements_protocol(self) -> QueryInterfaceEntity: return self + + +class MetricStub(ProtocolHint[QueryInterfaceMetric]): + """A Metric implementation that just satisfies the protocol. + + QueryInterfaceMetric currently has no methods and the parameter set is created in the factory. + """ + + @override + def _implements_protocol(self) -> QueryInterfaceMetric: + return self + + def descending(self, _is_descending: bool) -> QueryInterfaceMetric: # noqa: D + raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec") + + +@dataclass +class TimeDimensionStub(ProtocolHint[QueryInterfaceTimeDimension]): + """A TimeDimension implementation that just satisfies the protocol. + + QueryInterfaceTimeDimension currently has no methods and the parameter set is created in the factory. + So, there is nothing to do here. + """ + + element_name: str + time_granularity: Optional[TimeGranularity] = None + entity_path: Sequence[str] = () + date_part: Optional[DatePart] = None + + @override + def _implements_protocol(self) -> QueryInterfaceTimeDimension: + return self diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_time_dimension.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_time_dimension.py index 693c8344..3b948c2e 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_time_dimension.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_time_dimension.py @@ -9,23 +9,14 @@ from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( ParameterSetFactory, ) +from dbt_semantic_interfaces.parsing.where_filter.where_filter_stubs import ( + TimeDimensionStub, +) from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint from dbt_semantic_interfaces.protocols.query_interface import ( - QueryInterfaceTimeDimension, QueryInterfaceTimeDimensionFactory, ) - - -class TimeDimensionStub(ProtocolHint[QueryInterfaceTimeDimension]): - """A TimeDimension implementation that just satisfies the protocol. - - QueryInterfaceTimeDimension currently has no methods and the parameter set is created in the factory. - So, there is nothing to do here. - """ - - @override - def _implements_protocol(self) -> QueryInterfaceTimeDimension: - return self +from dbt_semantic_interfaces.type_enums import DatePart, TimeGranularity class WhereFilterTimeDimensionFactory(ProtocolHint[QueryInterfaceTimeDimensionFactory]): @@ -51,7 +42,15 @@ def create( raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec") self.time_dimension_call_parameter_sets.append( ParameterSetFactory.create_time_dimension( - time_dimension_name, time_granularity_name, entity_path, date_part_name + time_dimension_name=time_dimension_name, + time_granularity_name=time_granularity_name, + entity_path=entity_path, + date_part_name=date_part_name, ) ) - return TimeDimensionStub() + return TimeDimensionStub( + element_name=time_dimension_name, + time_granularity=TimeGranularity(time_granularity_name.lower()) if time_granularity_name else None, + entity_path=entity_path, + date_part=DatePart(date_part_name.lower()) if date_part_name else None, + ) diff --git a/dbt_semantic_interfaces/type_enums/__init__.py b/dbt_semantic_interfaces/type_enums/__init__.py index 0389d2c4..727c34cb 100644 --- a/dbt_semantic_interfaces/type_enums/__init__.py +++ b/dbt_semantic_interfaces/type_enums/__init__.py @@ -4,6 +4,7 @@ from dbt_semantic_interfaces.type_enums.conversion_calculation_type import ( # noqa:F401 ConversionCalculationType, ) +from dbt_semantic_interfaces.type_enums.date_part import DatePart # noqa:F401 from dbt_semantic_interfaces.type_enums.dimension_type import DimensionType # noqa:F401 from dbt_semantic_interfaces.type_enums.entity_type import EntityType # noqa:F401 from dbt_semantic_interfaces.type_enums.metric_type import MetricType # noqa:F401 From a0458f6db2317b31cf923b3bd95693906b187241 Mon Sep 17 00:00:00 2001 From: Courtney Holcomb Date: Wed, 5 Jun 2024 12:07:15 -0700 Subject: [PATCH 3/4] WIP --- .../where_filter/parameter_set_factory.py | 8 +- .../where_filter/where_filter_dimension.py | 64 ------------ .../where_filter/where_filter_entity.py | 55 ----------- .../where_filter/where_filter_factories.py | 99 +++++++++++++++++++ .../where_filter/where_filter_objects.py | 69 +++++++++++++ .../where_filter/where_filter_parser.py | 6 +- .../where_filter/where_filter_stubs.py | 61 ------------ .../where_filter_time_dimension.py | 56 ----------- .../protocols/query_interface.py | 99 ------------------- 9 files changed, 175 insertions(+), 342 deletions(-) delete mode 100644 dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py delete mode 100644 dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py create mode 100644 dbt_semantic_interfaces/parsing/where_filter/where_filter_factories.py create mode 100644 dbt_semantic_interfaces/parsing/where_filter/where_filter_objects.py delete mode 100644 dbt_semantic_interfaces/parsing/where_filter/where_filter_stubs.py delete mode 100644 dbt_semantic_interfaces/parsing/where_filter/where_filter_time_dimension.py delete mode 100644 dbt_semantic_interfaces/protocols/query_interface.py diff --git a/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py b/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py index 67105e0c..ab969bc6 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py +++ b/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py @@ -12,7 +12,9 @@ METRIC_TIME_ELEMENT_NAME, is_metric_time_name, ) -from dbt_semantic_interfaces.parsing.where_filter.where_filter_stubs import EntityStub +from dbt_semantic_interfaces.parsing.where_filter.where_filter_objects import ( + WhereFilterEntity, +) from dbt_semantic_interfaces.references import ( DimensionReference, EntityReference, @@ -107,7 +109,9 @@ def create_entity(entity_name: str, entity_path: Sequence[str] = ()) -> EntityCa ) @staticmethod - def create_metric(metric_name: str, group_by: Sequence[Union[str, EntityStub]] = ()) -> MetricCallParameterSet: + def create_metric( + metric_name: str, group_by: Sequence[Union[str, WhereFilterEntity]] = () + ) -> MetricCallParameterSet: """Gets called by Jinja when rendering {{ Metric(...) }}.""" if not group_by: raise ParseWhereFilterException( diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py deleted file mode 100644 index b03a7eab..00000000 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py +++ /dev/null @@ -1,64 +0,0 @@ -from __future__ import annotations - -from typing import List, Optional, Sequence - -from typing_extensions import override - -from dbt_semantic_interfaces.errors import InvalidQuerySyntax -from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint -from dbt_semantic_interfaces.protocols.query_interface import ( - QueryInterfaceDimension, - QueryInterfaceDimensionFactory, -) - - -class WhereFilterDimension(ProtocolHint[QueryInterfaceDimension]): - """A dimension that is passed in through the where filter parameter.""" - - @override - def _implements_protocol(self) -> QueryInterfaceDimension: - return self - - def __init__( # noqa - self, - name: str, - entity_path: Sequence[str], - ) -> None: - self.name = name - self.entity_path = entity_path - self.time_granularity_name: Optional[str] = None - self.date_part_name: Optional[str] = None - - def grain(self, time_granularity: str) -> QueryInterfaceDimension: - """The time granularity.""" - self.time_granularity_name = time_granularity - return self - - def descending(self, _is_descending: bool) -> QueryInterfaceDimension: - """Set the sort order for order-by.""" - raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec") - - def date_part(self, date_part_name: str) -> QueryInterfaceDimension: - """Date part to extract from the dimension.""" - self.date_part_name = date_part_name - return self - - -class WhereFilterDimensionFactory(ProtocolHint[QueryInterfaceDimensionFactory]): - """Creates a WhereFilterDimension. - - Each call to `create` adds a WhereFilterDimension to `created`. - """ - - @override - def _implements_protocol(self) -> QueryInterfaceDimensionFactory: - return self - - def __init__(self) -> None: # noqa - self.created: List[WhereFilterDimension] = [] - - def create(self, dimension_name: str, entity_path: Sequence[str] = ()) -> WhereFilterDimension: - """Gets called by Jinja when rendering {{ Dimension(...) }}.""" - dimension = WhereFilterDimension(dimension_name, entity_path) - self.created.append(dimension) - return dimension diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py deleted file mode 100644 index 31616e81..00000000 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py +++ /dev/null @@ -1,55 +0,0 @@ -from __future__ import annotations - -from typing import List, Sequence - -from typing_extensions import override - -from dbt_semantic_interfaces.call_parameter_sets import ( - EntityCallParameterSet, - MetricCallParameterSet, -) -from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( - ParameterSetFactory, -) -from dbt_semantic_interfaces.parsing.where_filter.where_filter_stubs import ( - EntityStub, - MetricStub, -) -from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint -from dbt_semantic_interfaces.protocols.query_interface import ( - QueryInterfaceEntityFactory, - QueryInterfaceMetricFactory, -) - - -class WhereFilterEntityFactory(ProtocolHint[QueryInterfaceEntityFactory]): - """Executes in the Jinja sandbox to produce parameter sets and append them to a list.""" - - @override - def _implements_protocol(self) -> QueryInterfaceEntityFactory: - return self - - def __init__(self) -> None: # noqa - self.entity_call_parameter_sets: List[EntityCallParameterSet] = [] - - def create(self, entity_name: str, entity_path: Sequence[str] = ()) -> EntityStub: - """Gets called by Jinja when rendering {{ Entity(...) }}.""" - self.entity_call_parameter_sets.append(ParameterSetFactory.create_entity(entity_name, entity_path)) - return EntityStub(element_name=entity_name, entity_links=entity_path) - - -class WhereFilterMetricFactory(ProtocolHint[QueryInterfaceMetricFactory]): - """Executes in the Jinja sandbox to produce parameter sets and append them to a list.""" - - @override - def _implements_protocol(self) -> QueryInterfaceMetricFactory: - return self - - def __init__(self) -> None: # noqa: D - self.metric_call_parameter_sets: List[MetricCallParameterSet] = [] - - def create(self, metric_name: str, group_by: Sequence[str] = ()) -> MetricStub: # noqa: D - self.metric_call_parameter_sets.append( - ParameterSetFactory.create_metric(metric_name=metric_name, group_by=group_by) - ) - return MetricStub() diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_factories.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_factories.py new file mode 100644 index 00000000..a4b0a59c --- /dev/null +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_factories.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +from typing import List, Optional, Sequence + +from dbt_semantic_interfaces.call_parameter_sets import ( + EntityCallParameterSet, + MetricCallParameterSet, + TimeDimensionCallParameterSet, +) +from dbt_semantic_interfaces.errors import InvalidQuerySyntax +from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( + ParameterSetFactory, +) +from dbt_semantic_interfaces.parsing.where_filter.where_filter_objects import ( + WhereFilterDimension, + WhereFilterEntity, + WhereFilterMetric, + WhereFilterTimeDimension, +) +from dbt_semantic_interfaces.type_enums import DatePart, TimeGranularity + + +class WhereFilterEntityFactory: + """Executes in the Jinja sandbox to produce parameter sets and append them to a list.""" + + def __init__(self) -> None: # noqa + self.entity_call_parameter_sets: List[EntityCallParameterSet] = [] + + def create(self, entity_name: str, entity_path: Sequence[str] = ()) -> WhereFilterEntity: + """Gets called by Jinja when rendering {{ Entity(...) }}.""" + self.entity_call_parameter_sets.append(ParameterSetFactory.create_entity(entity_name, entity_path)) + return WhereFilterEntity(element_name=entity_name, entity_links=entity_path) + + +class WhereFilterMetricFactory: + """Executes in the Jinja sandbox to produce parameter sets and append them to a list.""" + + def __init__(self) -> None: # noqa: D + self.metric_call_parameter_sets: List[MetricCallParameterSet] = [] + + def create(self, metric_name: str, group_by: Sequence[str] = ()) -> WhereFilterMetric: + """Create a WhereFilterMetric. + + Note that group_by is required, but uses a default arg here so that we can return a readable error to the user + if they leave it out. + """ + self.metric_call_parameter_sets.append( + ParameterSetFactory.create_metric(metric_name=metric_name, group_by=group_by) + ) + return WhereFilterMetric(element_name=metric_name, group_by=group_by) + + +class WhereFilterDimensionFactory: + """Creates a WhereFilterDimension. + + Each call to `create` adds a WhereFilterDimension to `created`. + """ + + def __init__(self) -> None: # noqa + self.created: List[WhereFilterDimension] = [] + + def create(self, dimension_name: str, entity_path: Sequence[str] = ()) -> WhereFilterDimension: + """Gets called by Jinja when rendering {{ Dimension(...) }}.""" + dimension = WhereFilterDimension(dimension_name, entity_path) + self.created.append(dimension) + return dimension + + +class WhereFilterTimeDimensionFactory: + """Executes in the Jinja sandbox to produce parameter sets and append them to a list.""" + + def __init__(self) -> None: # noqa + self.time_dimension_call_parameter_sets: List[TimeDimensionCallParameterSet] = [] + + def create( + self, + time_dimension_name: str, + time_granularity_name: Optional[str] = None, + entity_path: Sequence[str] = (), + descending: Optional[bool] = None, + date_part_name: Optional[str] = None, + ) -> WhereFilterTimeDimension: + """Gets called by Jinja when rendering {{ TimeDimension(...) }}.""" + if descending is not None: + raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec") + self.time_dimension_call_parameter_sets.append( + ParameterSetFactory.create_time_dimension( + time_dimension_name=time_dimension_name, + time_granularity_name=time_granularity_name, + entity_path=entity_path, + date_part_name=date_part_name, + ) + ) + return WhereFilterTimeDimension( + element_name=time_dimension_name, + time_granularity=TimeGranularity(time_granularity_name.lower()) if time_granularity_name else None, + entity_path=entity_path, + date_part_name=DatePart(date_part_name.lower()) if date_part_name else None, + ) diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_objects.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_objects.py new file mode 100644 index 00000000..4fc0fac2 --- /dev/null +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_objects.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional, Sequence + +from dbt_semantic_interfaces.type_enums import DatePart, TimeGranularity + + +@dataclass +class WhereFilterEntity: + """A dimension that is passed in through the where filter parameter using Jinja syntax.""" + + element_name: str + entity_links: Sequence[str] = () + + +@dataclass +class WhereFilterMetric: + """A metric that is passed in through the where filter parameter using Jinja syntax.""" + + element_name: str + group_by: Sequence[str] + + +@dataclass +class WhereFilterDimension: + """A dimension that is passed in through the where filter parameter using Jinja syntax.""" + + name: str + entity_path: Sequence[str] = () # Default is new here - consistent with TimeDimension + # Behavior change: allows passing these params on init (why shouldn't we allow that?) + # don't love the names, though. Copy MFS jinja object? + time_granularity_name: Optional[str] = None + date_part_name: Optional[str] = None + + def grain(self, time_granularity: str) -> WhereFilterDimension: # noqa: D + if self.time_granularity_name: + raise RuntimeError("Grain was already set in the Dimension object parameters.") + self.time_granularity_name = time_granularity + return self + + def date_part(self, date_part: str) -> WhereFilterDimension: # noqa: D + if self.date_part_name: + raise RuntimeError("Date part was already set in the Dimension object parameters.") + self.date_part_name = date_part + return self + + +@dataclass +class WhereFilterTimeDimension: + """A time dimension that is passed in through the where filter parameter using Jinja syntax.""" + + element_name: str + time_granularity: Optional[TimeGranularity] = None # not str? + entity_path: Sequence[str] = () + # Can we change the name below? Breaking change bo one is using date part anyway, right? And it's not documented? + date_part_name: Optional[DatePart] = None + + def grain(self, time_granularity: str) -> WhereFilterTimeDimension: # noqa: D + if self.time_granularity: + raise RuntimeError("Grain was already set in the Dimension object parameters.") + self.time_granularity = TimeGranularity(time_granularity.lower()) + return self + + def date_part(self, date_part: str) -> WhereFilterTimeDimension: # noqa: D + if self.date_part_name: + raise RuntimeError("Date part was already set in the Dimension object parameters.") + self.date_part_name = DatePart(date_part.lower()) + return self diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py index df81590d..e2e01388 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py @@ -11,14 +11,10 @@ from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( ParameterSetFactory, ) -from dbt_semantic_interfaces.parsing.where_filter.where_filter_dimension import ( +from dbt_semantic_interfaces.parsing.where_filter.where_filter_factories import ( WhereFilterDimensionFactory, -) -from dbt_semantic_interfaces.parsing.where_filter.where_filter_entity import ( WhereFilterEntityFactory, WhereFilterMetricFactory, -) -from dbt_semantic_interfaces.parsing.where_filter.where_filter_time_dimension import ( WhereFilterTimeDimensionFactory, ) diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_stubs.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_stubs.py deleted file mode 100644 index 90c57e75..00000000 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_stubs.py +++ /dev/null @@ -1,61 +0,0 @@ -from dataclasses import dataclass -from typing import Optional, Sequence - -from typing_extensions import override - -from dbt_semantic_interfaces.errors import InvalidQuerySyntax -from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint -from dbt_semantic_interfaces.protocols.query_interface import ( - QueryInterfaceEntity, - QueryInterfaceMetric, - QueryInterfaceTimeDimension, -) -from dbt_semantic_interfaces.type_enums import DatePart, TimeGranularity - - -@dataclass -class EntityStub(ProtocolHint[QueryInterfaceEntity]): - """An Entity implementation that just satisfies the protocol. - - QueryInterfaceEntity currently has no methods and the parameter set is created in the factory. - So, there is nothing to do here. - """ - - element_name: str - entity_links: Sequence[str] = () - - @override - def _implements_protocol(self) -> QueryInterfaceEntity: - return self - - -class MetricStub(ProtocolHint[QueryInterfaceMetric]): - """A Metric implementation that just satisfies the protocol. - - QueryInterfaceMetric currently has no methods and the parameter set is created in the factory. - """ - - @override - def _implements_protocol(self) -> QueryInterfaceMetric: - return self - - def descending(self, _is_descending: bool) -> QueryInterfaceMetric: # noqa: D - raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec") - - -@dataclass -class TimeDimensionStub(ProtocolHint[QueryInterfaceTimeDimension]): - """A TimeDimension implementation that just satisfies the protocol. - - QueryInterfaceTimeDimension currently has no methods and the parameter set is created in the factory. - So, there is nothing to do here. - """ - - element_name: str - time_granularity: Optional[TimeGranularity] = None - entity_path: Sequence[str] = () - date_part: Optional[DatePart] = None - - @override - def _implements_protocol(self) -> QueryInterfaceTimeDimension: - return self diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_time_dimension.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_time_dimension.py deleted file mode 100644 index 3b948c2e..00000000 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_time_dimension.py +++ /dev/null @@ -1,56 +0,0 @@ -from __future__ import annotations - -from typing import List, Optional, Sequence - -from typing_extensions import override - -from dbt_semantic_interfaces.call_parameter_sets import TimeDimensionCallParameterSet -from dbt_semantic_interfaces.errors import InvalidQuerySyntax -from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( - ParameterSetFactory, -) -from dbt_semantic_interfaces.parsing.where_filter.where_filter_stubs import ( - TimeDimensionStub, -) -from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint -from dbt_semantic_interfaces.protocols.query_interface import ( - QueryInterfaceTimeDimensionFactory, -) -from dbt_semantic_interfaces.type_enums import DatePart, TimeGranularity - - -class WhereFilterTimeDimensionFactory(ProtocolHint[QueryInterfaceTimeDimensionFactory]): - """Executes in the Jinja sandbox to produce parameter sets and append them to a list.""" - - @override - def _implements_protocol(self) -> QueryInterfaceTimeDimensionFactory: - return self - - def __init__(self) -> None: # noqa - self.time_dimension_call_parameter_sets: List[TimeDimensionCallParameterSet] = [] - - def create( - self, - time_dimension_name: str, - time_granularity_name: Optional[str] = None, - entity_path: Sequence[str] = (), - descending: Optional[bool] = None, - date_part_name: Optional[str] = None, - ) -> TimeDimensionStub: - """Gets called by Jinja when rendering {{ TimeDimension(...) }}.""" - if descending is not None: - raise InvalidQuerySyntax("descending is invalid in the where parameter and filter spec") - self.time_dimension_call_parameter_sets.append( - ParameterSetFactory.create_time_dimension( - time_dimension_name=time_dimension_name, - time_granularity_name=time_granularity_name, - entity_path=entity_path, - date_part_name=date_part_name, - ) - ) - return TimeDimensionStub( - element_name=time_dimension_name, - time_granularity=TimeGranularity(time_granularity_name.lower()) if time_granularity_name else None, - entity_path=entity_path, - date_part=DatePart(date_part_name.lower()) if date_part_name else None, - ) diff --git a/dbt_semantic_interfaces/protocols/query_interface.py b/dbt_semantic_interfaces/protocols/query_interface.py deleted file mode 100644 index 70225450..00000000 --- a/dbt_semantic_interfaces/protocols/query_interface.py +++ /dev/null @@ -1,99 +0,0 @@ -from __future__ import annotations - -from abc import abstractmethod -from typing import Optional, Protocol, Sequence - - -class QueryInterfaceMetric(Protocol): - """Represents the interface for Metric in the query interface.""" - - @abstractmethod - def descending(self, _is_descending: bool) -> QueryInterfaceMetric: - """Set the sort order for order-by.""" - pass - - -class QueryInterfaceDimension(Protocol): - """Represents the interface for Dimension in the query interface.""" - - @abstractmethod - def grain(self, _grain: str) -> QueryInterfaceDimension: - """The time granularity.""" - pass - - @abstractmethod - def descending(self, _is_descending: bool) -> QueryInterfaceDimension: - """Set the sort order for order-by.""" - pass - - @abstractmethod - def date_part(self, _date_part: str) -> QueryInterfaceDimension: - """Date part to extract from the dimension.""" - pass - - -class QueryInterfaceDimensionFactory(Protocol): - """Creates a Dimension for the query interface. - - Represented as the Dimension constructor in the Jinja sandbox. - """ - - @abstractmethod - def create(self, name: str, entity_path: Sequence[str] = ()) -> QueryInterfaceDimension: - """Create a QueryInterfaceDimension.""" - pass - - -class QueryInterfaceTimeDimension(Protocol): - """Represents the interface for TimeDimension in the query interface.""" - - pass - - -class QueryInterfaceTimeDimensionFactory(Protocol): - """Creates a TimeDimension for the query interface. - - Represented as the TimeDimension constructor in the Jinja sandbox. - """ - - @abstractmethod - def create( - self, - time_dimension_name: str, - time_granularity_name: str, - entity_path: Sequence[str] = (), - descending: Optional[bool] = None, - date_part_name: Optional[str] = None, - ) -> QueryInterfaceTimeDimension: - """Create a TimeDimension.""" - pass - - -class QueryInterfaceEntity(Protocol): - """Represents the interface for Entity in the query interface.""" - - pass - - -class QueryInterfaceEntityFactory(Protocol): - """Creates an Entity for the query interface. - - Represented as the Entity constructor in the Jinja sandbox. - """ - - @abstractmethod - def create(self, entity_name: str, entity_path: Sequence[str] = ()) -> QueryInterfaceEntity: - """Create an Entity.""" - pass - - -class QueryInterfaceMetricFactory(Protocol): - """Creates an Metric for the query interface. - - Represented as the Metric constructor in the Jinja sandbox. - """ - - @abstractmethod - def create(self, metric_name: str, group_by: Sequence[str] = ()) -> QueryInterfaceMetric: - """Create a Metric.""" - pass From 728cc91dc49db6ca2259fd7b13eb56c60847528e Mon Sep 17 00:00:00 2001 From: Courtney Holcomb Date: Thu, 6 Jun 2024 15:29:18 -0700 Subject: [PATCH 4/4] WIP --- .../parsing/where_filter/where_filter_factories.py | 4 ++++ .../simple_semantic_manifest/metrics.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_factories.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_factories.py index a4b0a59c..ae937176 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_factories.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_factories.py @@ -20,6 +20,10 @@ from dbt_semantic_interfaces.type_enums import DatePart, TimeGranularity +# Rename these factories: RenderedWhereFilterEntityFactory (MF) vs. ParsedWhereFilterParser (DSI) +# Add protocols back. Rename them to: WhereFilterEntity (JinjaWhereFilterEntity? Can we reuse them in the JDBC interface & saved queries?), +# JinjaEntityFactory (do we even need a protocol for this? I guess to make sure the create() method is aligned?) +# Can we use an ABC instead of a protocol? Something that lets me add functional methods to the base class to avoid duplication. class WhereFilterEntityFactory: """Executes in the Jinja sandbox to produce parameter sets and append them to a list.""" diff --git a/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/metrics.yaml b/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/metrics.yaml index 9b834a34..c9a4408d 100644 --- a/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/metrics.yaml +++ b/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/metrics.yaml @@ -544,7 +544,7 @@ metric: type_params: measure: name: listings - filter: "{{ Metric('bookings', group_by=['listing']) }} > 2" + filter: "{{ Metric('bookings', sup=['listing']) }} > 2" --- metric: name: "active_listings"