From 7faca7078511fc720a3137844c81cf8d238b799c Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Fri, 15 Sep 2023 17:53:29 -0500 Subject: [PATCH 01/16] added support for Dimension(...).grain --- .../call_parameter_sets.py | 6 +- .../implementations/filters/where_filter.py | 2 +- .../where_filter/parameter_set_factory.py | 85 +++++++++++ .../parsing/where_filter/query_interface.py | 65 +++++++++ .../where_filter/where_filter_dimension.py | 74 ++++++++++ .../where_filter/where_filter_entity.py | 32 ++++ .../where_filter/where_filter_parser.py | 48 ++++++ .../where_filter_time_dimension.py | 38 +++++ .../parsing/where_filter_parser.py | 138 ------------------ 9 files changed, 346 insertions(+), 142 deletions(-) create mode 100644 dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py create mode 100644 dbt_semantic_interfaces/parsing/where_filter/query_interface.py create mode 100644 dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py create mode 100644 dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py create mode 100644 dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py create mode 100644 dbt_semantic_interfaces/parsing/where_filter/where_filter_time_dimension.py delete mode 100644 dbt_semantic_interfaces/parsing/where_filter_parser.py diff --git a/dbt_semantic_interfaces/call_parameter_sets.py b/dbt_semantic_interfaces/call_parameter_sets.py index 193bc892..e9cae4f6 100644 --- a/dbt_semantic_interfaces/call_parameter_sets.py +++ b/dbt_semantic_interfaces/call_parameter_sets.py @@ -13,7 +13,7 @@ @dataclass(frozen=True) class DimensionCallParameterSet: - """When 'dimension(...)' is used in the Jinja template of the where filter, the parameters to that call.""" + """When 'Dimension(...)' is used in the Jinja template of the where filter, the parameters to that call.""" entity_path: Tuple[EntityReference, ...] dimension_reference: DimensionReference @@ -21,7 +21,7 @@ class DimensionCallParameterSet: @dataclass(frozen=True) class TimeDimensionCallParameterSet: - """When 'time_dimension(...)' is used in the Jinja template of the where filter, the parameters to that call.""" + """When 'TimeDimension(...)' is used in the Jinja template of the where filter, the parameters to that call.""" entity_path: Tuple[EntityReference, ...] time_dimension_reference: TimeDimensionReference @@ -30,7 +30,7 @@ class TimeDimensionCallParameterSet: @dataclass(frozen=True) class EntityCallParameterSet: - """When 'entity(...)' is used in the Jinja template of the where filter, the parameters to that call.""" + """When 'Entity(...)' is used in the Jinja template of the where filter, the parameters to that call.""" entity_path: Tuple[EntityReference, ...] entity_reference: EntityReference diff --git a/dbt_semantic_interfaces/implementations/filters/where_filter.py b/dbt_semantic_interfaces/implementations/filters/where_filter.py index 70c15055..b8fd7bc3 100644 --- a/dbt_semantic_interfaces/implementations/filters/where_filter.py +++ b/dbt_semantic_interfaces/implementations/filters/where_filter.py @@ -6,7 +6,7 @@ PydanticCustomInputParser, PydanticParseableValueType, ) -from dbt_semantic_interfaces.parsing.where_filter_parser import WhereFilterParser +from dbt_semantic_interfaces.parsing.where_filter.where_filter_parser import WhereFilterParser class PydanticWhereFilter(PydanticCustomInputParser, HashableBaseModel): diff --git a/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py b/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py new file mode 100644 index 00000000..950c2f0a --- /dev/null +++ b/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py @@ -0,0 +1,85 @@ +from typing import Sequence + +from dbt_semantic_interfaces.call_parameter_sets import ( + DimensionCallParameterSet, + EntityCallParameterSet, + ParseWhereFilterException, + TimeDimensionCallParameterSet, +) +from dbt_semantic_interfaces.naming.dundered import DunderedNameFormatter +from dbt_semantic_interfaces.naming.keywords import METRIC_TIME_ELEMENT_NAME, is_metric_time_name +from dbt_semantic_interfaces.references import DimensionReference, EntityReference, TimeDimensionReference +from dbt_semantic_interfaces.type_enums import TimeGranularity + + +class ParameterSetFactory: + @staticmethod + def _exception_message_for_incorrect_format(element_name: str) -> str: + return ( + f"Name is in an incorrect format: '{element_name}'. It should be of the form: " + f"__" + ) + + @staticmethod + def create_time_dimension( + time_dimension_name: str, time_granularity_name: str, entity_path: Sequence[str] = () + ) -> TimeDimensionCallParameterSet: + """Gets called by Jinja when rendering {{ TimeDimension(...) }}.""" + group_by_item_name = DunderedNameFormatter.parse_name(time_dimension_name) + + # metric_time is the only time dimension that does not have an associated primary entity, so the + # GroupByItemName would not have any entity links. + if is_metric_time_name(group_by_item_name.element_name): + if len(group_by_item_name.entity_links) != 0 or group_by_item_name.time_granularity is not None: + raise ParseWhereFilterException( + f"Name is in an incorrect format: {time_dimension_name} " + f"When referencing {METRIC_TIME_ELEMENT_NAME}, the name should not have any dunders." + ) + else: + if len(group_by_item_name.entity_links) != 1 or group_by_item_name.time_granularity is not None: + raise ParseWhereFilterException( + ParameterSetFactory._exception_message_for_incorrect_format(time_dimension_name) + ) + + return TimeDimensionCallParameterSet( + time_dimension_reference=TimeDimensionReference(element_name=group_by_item_name.element_name), + entity_path=( + tuple(EntityReference(element_name=arg) for arg in entity_path) + group_by_item_name.entity_links + ), + time_granularity=TimeGranularity(time_granularity_name), + ) + + @staticmethod + def create_dimension(dimension_name: str, entity_path: Sequence[str] = ()) -> DimensionCallParameterSet: + """Gets called by Jinja when rendering {{ Dimension(...) }}.""" + group_by_item_name = DunderedNameFormatter.parse_name(dimension_name) + if is_metric_time_name(group_by_item_name.element_name): + raise ParseWhereFilterException( + f"{METRIC_TIME_ELEMENT_NAME} is a time dimension, so it should be referenced using " + f"TimeDimension(...)" + ) + + if len(group_by_item_name.entity_links) != 1: + raise ParseWhereFilterException(ParameterSetFactory._exception_message_for_incorrect_format(dimension_name)) + + return DimensionCallParameterSet( + dimension_reference=DimensionReference(element_name=group_by_item_name.element_name), + entity_path=( + tuple(EntityReference(element_name=arg) for arg in entity_path) + group_by_item_name.entity_links + ), + ) + + @staticmethod + def create_entity(entity_name: str, entity_path: Sequence[str] = ()) -> EntityCallParameterSet: + """Gets called by Jinja when rendering {{ Entity(...) }}.""" + group_by_item_name = DunderedNameFormatter.parse_name(entity_name) + if len(group_by_item_name.entity_links) > 0 or group_by_item_name.time_granularity is not None: + ParameterSetFactory._exception_message_for_incorrect_format( + f"Name is in an incorrect format: {entity_name} " + f"When referencing entities, the name should not have any dunders." + ) + + return EntityCallParameterSet( + entity_path=tuple(EntityReference(element_name=arg) for arg in entity_path), + entity_reference=EntityReference(element_name=entity_name), + ) diff --git a/dbt_semantic_interfaces/parsing/where_filter/query_interface.py b/dbt_semantic_interfaces/parsing/where_filter/query_interface.py new file mode 100644 index 00000000..b0fe26cf --- /dev/null +++ b/dbt_semantic_interfaces/parsing/where_filter/query_interface.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from typing import Protocol, Sequence + + +class QueryInterfaceDimension(Protocol): + """Represents the interface for Dimension in the query interface.""" + + def grain(self, _grain: str) -> QueryInterfaceDimension: + """The time granularity.""" + raise NotImplementedError + + def alias(self, _alias: str) -> QueryInterfaceDimension: + """Renaming the column.""" + raise NotImplementedError + + +class QueryInterfaceDimensionFactory(Protocol): + """Creates a Dimension for the query interface. + + Represented as the Dimension constructor in the Jinja sandbox. + """ + + def create(self, name: str, entity_path: Sequence[str] = ()) -> QueryInterfaceDimension: + """Create a QueryInterfaceDimension.""" + raise NotImplementedError + + +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. + """ + + def create( + self, + time_dimension_name: str, + time_granularity_name: str, + entity_path: Sequence[str] = (), + ) -> QueryInterfaceTimeDimension: + """Create a TimeDimension.""" + raise NotImplementedError + + +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. + """ + + def create(self, entity_name: str, entity_path: Sequence[str] = ()) -> QueryInterfaceEntity: + """Create an Entity.""" + raise NotImplementedError diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py new file mode 100644 index 00000000..0172009b --- /dev/null +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from typing import List, Optional, Sequence + +from dbt_semantic_interfaces.call_parameter_sets import ( + DimensionCallParameterSet, + ParseWhereFilterException, + TimeDimensionCallParameterSet, +) +from dbt_semantic_interfaces.naming.dundered import DunderedNameFormatter +from dbt_semantic_interfaces.naming.keywords import METRIC_TIME_ELEMENT_NAME, is_metric_time_name +from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ParameterSetFactory +from dbt_semantic_interfaces.parsing.where_filter.query_interface import ( + QueryInterfaceDimension, + QueryInterfaceDimensionFactory, +) +from dbt_semantic_interfaces.parsing.where_filter.where_filter_error import WhereFilterError +from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint +from dbt_semantic_interfaces.references import DimensionReference, EntityReference +from typing_extensions import override + +from dbt_semantic_interfaces.type_enums.time_granularity import TimeGranularity + + +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], + time_dimension_call_parameter_sets: List[TimeDimensionCallParameterSet], + ): + self.name = name + self.entity_path = entity_path + self._time_dimension_call_parameter_sets = time_dimension_call_parameter_sets + self.time_granularity: Optional[TimeGranularity] = None + + def grain(self, time_granularity: str) -> QueryInterfaceDimension: + """The time granularity.""" + self.time_granularity = TimeGranularity(time_granularity) + self._time_dimension_call_parameter_sets.append( + ParameterSetFactory.create_time_dimension(self.name, self.entity_path, self.time_granularity) + ) + + def alias(self, _alias: str) -> QueryInterfaceDimension: + """Renaming the column.""" + raise NotImplementedError + + +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, time_dimension_call_parameter_sets: List[TimeDimensionCallParameterSet]): # noqa + self.dimension_call_parameter_sets: List[DimensionCallParameterSet] = [] + self.created: List[WhereFilterDimension] = [] + self._time_dimension_call_parameter_sets = time_dimension_call_parameter_sets + + 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._time_dimension_call_parameter_sets) + 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 new file mode 100644 index 00000000..2075d2e6 --- /dev/null +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from typing import List, Sequence + +from dbt_semantic_interfaces.call_parameter_sets import EntityCallParameterSet +from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ParameterSetFactory +from dbt_semantic_interfaces.parsing.where_filter.query_interface import ( + QueryInterfaceEntity, + QueryInterfaceEntityFactory, +) +from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint +from typing_extensions import override + + +class EntityStub(ProtocolHint[QueryInterfaceEntity]): + @override + def _implements_protocol(self) -> QueryInterfaceEntity: + return self + + +class WhereFilterEntityFactory(ProtocolHint[QueryInterfaceEntityFactory]): + @override + def _implements_protocol(self) -> QueryInterfaceEntityFactory: + return self + + def __init__(self): # 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() diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py new file mode 100644 index 00000000..0d0bb717 --- /dev/null +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py @@ -0,0 +1,48 @@ +from __future__ import annotations +from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ParameterSetFactory + +from dbt_semantic_interfaces.parsing.where_filter.where_filter_dimension import WhereFilterDimensionFactory +from dbt_semantic_interfaces.parsing.where_filter.where_filter_entity import WhereFilterEntityFactory +from dbt_semantic_interfaces.parsing.where_filter.where_filter_time_dimension import WhereFilterTimeDimensionFactory + +from jinja2 import StrictUndefined +from jinja2.exceptions import SecurityError, TemplateSyntaxError, UndefinedError +from jinja2.sandbox import SandboxedEnvironment + +from dbt_semantic_interfaces.call_parameter_sets import ( + FilterCallParameterSets, + ParseWhereFilterException, +) + + +class WhereFilterParser: + """Parses the template in the WhereFilter into FilterCallParameterSets.""" + + @staticmethod + def parse_call_parameter_sets(where_sql_template: str) -> FilterCallParameterSets: + """Return the result of extracting the semantic objects referenced in the where SQL template string.""" + time_dimension_factory = WhereFilterTimeDimensionFactory() + dimension_factory = WhereFilterDimensionFactory(time_dimension_factory.time_dimension_call_parameter_sets) + entity_factory = WhereFilterEntityFactory() + + try: + # the string that the sandbox renders is unused + SandboxedEnvironment(undefined=StrictUndefined).from_string(where_sql_template).render( + Dimension=dimension_factory.create, + TimeDimension=time_dimension_factory.create, + Entity=entity_factory.create, + ) + except (UndefinedError, TemplateSyntaxError, SecurityError) as e: + raise ParseWhereFilterException(f"Error while parsing Jinja template:\n{where_sql_template}") from e + + dimension_parameter_sets = [] + for dimension in dimension_factory.created: + if not dimension.time_granularity: + param_set = ParameterSetFactory.create_dimension(dimension.name, dimension.entity_path) + dimension_parameter_sets.append(param_set) + + return FilterCallParameterSets( + dimension_call_parameter_sets=tuple(dimension_parameter_sets), + time_dimension_call_parameter_sets=tuple(time_dimension_factory.time_dimension_call_parameter_sets), + entity_call_parameter_sets=tuple(entity_factory.entity_call_parameter_sets), + ) 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 new file mode 100644 index 00000000..971a5740 --- /dev/null +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_time_dimension.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from typing import List, Sequence + +from dbt_semantic_interfaces.call_parameter_sets import ( + TimeDimensionCallParameterSet, +) +from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ParameterSetFactory +from dbt_semantic_interfaces.parsing.where_filter.query_interface import ( + QueryInterfaceTimeDimension, + QueryInterfaceTimeDimensionFactory, +) +from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint +from typing_extensions import override + + +class TimeDimensionStub(ProtocolHint[QueryInterfaceTimeDimension]): + @override + def _implements_protocol(self) -> QueryInterfaceTimeDimension: + return self + + +class WhereFilterTimeDimensionFactory(ProtocolHint[QueryInterfaceTimeDimensionFactory]): + @override + def _implements_protocol(self) -> QueryInterfaceTimeDimensionFactory: + return self + + def __init__(self): # noqa + self.time_dimension_call_parameter_sets: List[TimeDimensionCallParameterSet] = [] + + def create( + self, time_dimension_name: str, time_granularity_name: str, entity_path: Sequence[str] = () + ) -> TimeDimensionStub: + """Gets called by Jinja when rendering {{ TimeDimension(...) }}.""" + self.time_dimension_call_parameter_sets.append( + ParameterSetFactory.create_time_dimension(time_dimension_name, time_granularity_name, entity_path) + ) + return TimeDimensionStub() diff --git a/dbt_semantic_interfaces/parsing/where_filter_parser.py b/dbt_semantic_interfaces/parsing/where_filter_parser.py deleted file mode 100644 index d2d83547..00000000 --- a/dbt_semantic_interfaces/parsing/where_filter_parser.py +++ /dev/null @@ -1,138 +0,0 @@ -from __future__ import annotations - -from typing import List, Sequence - -from jinja2 import StrictUndefined -from jinja2.exceptions import SecurityError, TemplateSyntaxError, UndefinedError -from jinja2.sandbox import SandboxedEnvironment - -from dbt_semantic_interfaces.call_parameter_sets import ( - DimensionCallParameterSet, - EntityCallParameterSet, - FilterCallParameterSets, - ParseWhereFilterException, - TimeDimensionCallParameterSet, -) -from dbt_semantic_interfaces.naming.dundered import DunderedNameFormatter -from dbt_semantic_interfaces.naming.keywords import ( - METRIC_TIME_ELEMENT_NAME, - is_metric_time_name, -) -from dbt_semantic_interfaces.references import ( - DimensionReference, - EntityReference, - TimeDimensionReference, -) -from dbt_semantic_interfaces.type_enums import TimeGranularity - - -class WhereFilterParser: - """Parses the template in the WhereFilter into FilterCallParameterSets.""" - - @staticmethod - def _exception_message_for_incorrect_format(element_name: str) -> str: - return ( - f"Name is in an incorrect format: '{element_name}'. It should be of the form: " - f"__" - ) - - @staticmethod - def parse_call_parameter_sets(where_sql_template: str) -> FilterCallParameterSets: - """Return the result of extracting the semantic objects referenced in the where SQL template string.""" - # To extract the parameters to the calls, we use a function to record the parameters while rendering the Jinja - # template. The rendered result is not used, but since Jinja has to render something, using this as a - # placeholder. An alternative approach would have been to use the Jinja AST API, but this seemed simpler. - _DUMMY_PLACEHOLDER = "DUMMY_PLACEHOLDER" - - dimension_call_parameter_sets: List[DimensionCallParameterSet] = [] - time_dimension_call_parameter_sets: List[TimeDimensionCallParameterSet] = [] - entity_call_parameter_sets: List[EntityCallParameterSet] = [] - - def _dimension_call(dimension_name: str, entity_path: Sequence[str] = ()) -> str: - """Gets called by Jinja when rendering {{ dimension(...) }}.""" - group_by_item_name = DunderedNameFormatter.parse_name(dimension_name) - if is_metric_time_name(group_by_item_name.element_name): - raise ParseWhereFilterException( - f"{METRIC_TIME_ELEMENT_NAME} is a time dimension, so it should be referenced using " - f"TimeDimension(...)" - ) - - if len(group_by_item_name.entity_links) != 1: - raise ParseWhereFilterException( - WhereFilterParser._exception_message_for_incorrect_format(dimension_name) - ) - - dimension_call_parameter_sets.append( - DimensionCallParameterSet( - dimension_reference=DimensionReference(element_name=group_by_item_name.element_name), - entity_path=( - tuple(EntityReference(element_name=arg) for arg in entity_path) - + group_by_item_name.entity_links - ), - ) - ) - return _DUMMY_PLACEHOLDER - - def _time_dimension_call( - time_dimension_name: str, time_granularity_name: str, entity_path: Sequence[str] = () - ) -> str: - """Gets called by Jinja when rendering {{ time_dimension(...) }}.""" - group_by_item_name = DunderedNameFormatter.parse_name(time_dimension_name) - - # metric_time is the only time dimension that does not have an associated primary entity, so the - # GroupByItemName would not have any entity links. - if is_metric_time_name(group_by_item_name.element_name): - if len(group_by_item_name.entity_links) != 0 or group_by_item_name.time_granularity is not None: - raise ParseWhereFilterException( - f"Name is in an incorrect format: {time_dimension_name} " - f"When referencing {METRIC_TIME_ELEMENT_NAME}, the name should not have any dunders." - ) - else: - if len(group_by_item_name.entity_links) != 1 or group_by_item_name.time_granularity is not None: - raise ParseWhereFilterException( - WhereFilterParser._exception_message_for_incorrect_format(time_dimension_name) - ) - - time_dimension_call_parameter_sets.append( - TimeDimensionCallParameterSet( - time_dimension_reference=TimeDimensionReference(element_name=group_by_item_name.element_name), - entity_path=( - tuple(EntityReference(element_name=arg) for arg in entity_path) - + group_by_item_name.entity_links - ), - time_granularity=TimeGranularity(time_granularity_name), - ) - ) - return _DUMMY_PLACEHOLDER - - def _entity_call(entity_name: str, entity_path: Sequence[str] = ()) -> str: - """Gets called by Jinja when rendering {{ entity(...) }}.""" - group_by_item_name = DunderedNameFormatter.parse_name(entity_name) - if len(group_by_item_name.entity_links) > 0 or group_by_item_name.time_granularity is not None: - WhereFilterParser._exception_message_for_incorrect_format( - f"Name is in an incorrect format: {entity_name} " - f"When referencing entities, the name should not have any dunders." - ) - - entity_call_parameter_sets.append( - EntityCallParameterSet( - entity_path=tuple(EntityReference(element_name=arg) for arg in entity_path), - entity_reference=EntityReference(element_name=entity_name), - ) - ) - return _DUMMY_PLACEHOLDER - - try: - SandboxedEnvironment(undefined=StrictUndefined).from_string(where_sql_template).render( - Dimension=_dimension_call, - TimeDimension=_time_dimension_call, - Entity=_entity_call, - ) - except (UndefinedError, TemplateSyntaxError, SecurityError) as e: - raise ParseWhereFilterException(f"Error while parsing Jinja template:\n{where_sql_template}") from e - - return FilterCallParameterSets( - dimension_call_parameter_sets=tuple(dimension_call_parameter_sets), - time_dimension_call_parameter_sets=tuple(time_dimension_call_parameter_sets), - entity_call_parameter_sets=tuple(entity_call_parameter_sets), - ) From 1aff3c9dba9bdcc64dc2e2b28a67b547b7e55385 Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Mon, 18 Sep 2023 10:38:38 -0500 Subject: [PATCH 02/16] added test --- .../where_filter/where_filter_dimension.py | 8 ++----- .../where_filter/test_parse_calls.py | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py index 0172009b..956c6cf7 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py @@ -4,19 +4,14 @@ from dbt_semantic_interfaces.call_parameter_sets import ( DimensionCallParameterSet, - ParseWhereFilterException, TimeDimensionCallParameterSet, ) -from dbt_semantic_interfaces.naming.dundered import DunderedNameFormatter -from dbt_semantic_interfaces.naming.keywords import METRIC_TIME_ELEMENT_NAME, is_metric_time_name from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ParameterSetFactory from dbt_semantic_interfaces.parsing.where_filter.query_interface import ( QueryInterfaceDimension, QueryInterfaceDimensionFactory, ) -from dbt_semantic_interfaces.parsing.where_filter.where_filter_error import WhereFilterError from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint -from dbt_semantic_interfaces.references import DimensionReference, EntityReference from typing_extensions import override from dbt_semantic_interfaces.type_enums.time_granularity import TimeGranularity @@ -44,8 +39,9 @@ def grain(self, time_granularity: str) -> QueryInterfaceDimension: """The time granularity.""" self.time_granularity = TimeGranularity(time_granularity) self._time_dimension_call_parameter_sets.append( - ParameterSetFactory.create_time_dimension(self.name, self.entity_path, self.time_granularity) + ParameterSetFactory.create_time_dimension(self.name, self.time_granularity, self.entity_path) ) + return self def alias(self, _alias: str) -> QueryInterfaceDimension: """Renaming the column.""" diff --git a/tests/implementations/where_filter/test_parse_calls.py b/tests/implementations/where_filter/test_parse_calls.py index 770d8e70..e1741473 100644 --- a/tests/implementations/where_filter/test_parse_calls.py +++ b/tests/implementations/where_filter/test_parse_calls.py @@ -50,6 +50,28 @@ def test_extract_dimension_call_parameter_sets() -> None: # noqa: D ) +def test_extract_dimension_with_grain_call_parameter_sets() -> None: # noqa: D + parse_result = PydanticWhereFilter( + where_sql_template=( + """ + {{ Dimension('metric_time').grain('WEEK') }} > 2023-09-18 + """ + ) + ).call_parameter_sets + + assert parse_result == FilterCallParameterSets( + dimension_call_parameter_sets=(), + time_dimension_call_parameter_sets=( + TimeDimensionCallParameterSet( + entity_path=(), + time_dimension_reference=TimeDimensionReference(element_name="metric_time"), + time_granularity=TimeGranularity.WEEK, + ), + ), + entity_call_parameter_sets=(), + ) + + def test_extract_time_dimension_call_parameter_sets() -> None: # noqa: D parse_result = PydanticWhereFilter( where_sql_template=( From 29eeceb36be01377c0e698975b80a0b35b1fb6a8 Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Mon, 18 Sep 2023 11:11:18 -0500 Subject: [PATCH 03/16] fixed formatting & linting --- .../implementations/filters/where_filter.py | 4 +++- .../where_filter/parameter_set_factory.py | 13 +++++++++++-- .../where_filter/where_filter_dimension.py | 14 ++++++-------- .../parsing/where_filter/where_filter_entity.py | 13 ++++++++++--- .../parsing/where_filter/where_filter_parser.py | 17 ++++++++++++----- .../where_filter/where_filter_time_dimension.py | 15 ++++++++++----- 6 files changed, 52 insertions(+), 24 deletions(-) diff --git a/dbt_semantic_interfaces/implementations/filters/where_filter.py b/dbt_semantic_interfaces/implementations/filters/where_filter.py index b8fd7bc3..437d7f78 100644 --- a/dbt_semantic_interfaces/implementations/filters/where_filter.py +++ b/dbt_semantic_interfaces/implementations/filters/where_filter.py @@ -6,7 +6,9 @@ PydanticCustomInputParser, PydanticParseableValueType, ) -from dbt_semantic_interfaces.parsing.where_filter.where_filter_parser import WhereFilterParser +from dbt_semantic_interfaces.parsing.where_filter.where_filter_parser import ( + WhereFilterParser, +) class PydanticWhereFilter(PydanticCustomInputParser, HashableBaseModel): 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 950c2f0a..eebe5258 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py +++ b/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py @@ -7,12 +7,21 @@ TimeDimensionCallParameterSet, ) from dbt_semantic_interfaces.naming.dundered import DunderedNameFormatter -from dbt_semantic_interfaces.naming.keywords import METRIC_TIME_ELEMENT_NAME, is_metric_time_name -from dbt_semantic_interfaces.references import DimensionReference, EntityReference, TimeDimensionReference +from dbt_semantic_interfaces.naming.keywords import ( + METRIC_TIME_ELEMENT_NAME, + is_metric_time_name, +) +from dbt_semantic_interfaces.references import ( + DimensionReference, + EntityReference, + TimeDimensionReference, +) from dbt_semantic_interfaces.type_enums import TimeGranularity class ParameterSetFactory: + """Creates parameter sets for use in the Jinja sandbox.""" + @staticmethod def _exception_message_for_incorrect_format(element_name: str) -> str: return ( diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py index 956c6cf7..feed320f 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py @@ -2,18 +2,17 @@ from typing import List, Optional, Sequence -from dbt_semantic_interfaces.call_parameter_sets import ( - DimensionCallParameterSet, - TimeDimensionCallParameterSet, +from typing_extensions import override + +from dbt_semantic_interfaces.call_parameter_sets import TimeDimensionCallParameterSet +from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( + ParameterSetFactory, ) -from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ParameterSetFactory from dbt_semantic_interfaces.parsing.where_filter.query_interface import ( QueryInterfaceDimension, QueryInterfaceDimensionFactory, ) from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint -from typing_extensions import override - from dbt_semantic_interfaces.type_enums.time_granularity import TimeGranularity @@ -39,7 +38,7 @@ def grain(self, time_granularity: str) -> QueryInterfaceDimension: """The time granularity.""" self.time_granularity = TimeGranularity(time_granularity) self._time_dimension_call_parameter_sets.append( - ParameterSetFactory.create_time_dimension(self.name, self.time_granularity, self.entity_path) + ParameterSetFactory.create_time_dimension(self.name, time_granularity, self.entity_path) ) return self @@ -59,7 +58,6 @@ def _implements_protocol(self) -> QueryInterfaceDimensionFactory: return self def __init__(self, time_dimension_call_parameter_sets: List[TimeDimensionCallParameterSet]): # noqa - self.dimension_call_parameter_sets: List[DimensionCallParameterSet] = [] self.created: List[WhereFilterDimension] = [] self._time_dimension_call_parameter_sets = time_dimension_call_parameter_sets 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 2075d2e6..ee747561 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py @@ -2,28 +2,35 @@ from typing import List, Sequence +from typing_extensions import override + from dbt_semantic_interfaces.call_parameter_sets import EntityCallParameterSet -from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ParameterSetFactory +from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( + ParameterSetFactory, +) from dbt_semantic_interfaces.parsing.where_filter.query_interface import ( QueryInterfaceEntity, QueryInterfaceEntityFactory, ) from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint -from typing_extensions import override class EntityStub(ProtocolHint[QueryInterfaceEntity]): + """An Entity implementation that does nothing.""" + @override def _implements_protocol(self) -> QueryInterfaceEntity: return self 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): # noqa + def __init__(self) -> None: # noqa self.entity_call_parameter_sets: List[EntityCallParameterSet] = [] def create(self, entity_name: str, entity_path: Sequence[str] = ()) -> EntityStub: 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 0d0bb717..aa069cc2 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py @@ -1,9 +1,4 @@ from __future__ import annotations -from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ParameterSetFactory - -from dbt_semantic_interfaces.parsing.where_filter.where_filter_dimension import WhereFilterDimensionFactory -from dbt_semantic_interfaces.parsing.where_filter.where_filter_entity import WhereFilterEntityFactory -from dbt_semantic_interfaces.parsing.where_filter.where_filter_time_dimension import WhereFilterTimeDimensionFactory from jinja2 import StrictUndefined from jinja2.exceptions import SecurityError, TemplateSyntaxError, UndefinedError @@ -13,6 +8,18 @@ FilterCallParameterSets, ParseWhereFilterException, ) +from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( + ParameterSetFactory, +) +from dbt_semantic_interfaces.parsing.where_filter.where_filter_dimension import ( + WhereFilterDimensionFactory, +) +from dbt_semantic_interfaces.parsing.where_filter.where_filter_entity import ( + WhereFilterEntityFactory, +) +from dbt_semantic_interfaces.parsing.where_filter.where_filter_time_dimension import ( + WhereFilterTimeDimensionFactory, +) class WhereFilterParser: 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 971a5740..979a0665 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 @@ -2,30 +2,35 @@ from typing import List, Sequence -from dbt_semantic_interfaces.call_parameter_sets import ( - TimeDimensionCallParameterSet, +from typing_extensions import override + +from dbt_semantic_interfaces.call_parameter_sets import TimeDimensionCallParameterSet +from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( + ParameterSetFactory, ) -from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ParameterSetFactory from dbt_semantic_interfaces.parsing.where_filter.query_interface import ( QueryInterfaceTimeDimension, QueryInterfaceTimeDimensionFactory, ) from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint -from typing_extensions import override class TimeDimensionStub(ProtocolHint[QueryInterfaceTimeDimension]): + """A TimeDimension implementation that does nothing.""" + @override def _implements_protocol(self) -> QueryInterfaceTimeDimension: return self 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): # noqa + def __init__(self) -> None: # noqa self.time_dimension_call_parameter_sets: List[TimeDimensionCallParameterSet] = [] def create( From 23edab72c8f61c24fe21e58c0101def643f0c4a1 Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Mon, 18 Sep 2023 11:22:09 -0500 Subject: [PATCH 04/16] changie --- .changes/unreleased/Features-20230918-112159.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/unreleased/Features-20230918-112159.yaml diff --git a/.changes/unreleased/Features-20230918-112159.yaml b/.changes/unreleased/Features-20230918-112159.yaml new file mode 100644 index 00000000..a4379690 --- /dev/null +++ b/.changes/unreleased/Features-20230918-112159.yaml @@ -0,0 +1,6 @@ +kind: Features +body: Support for `Dimension.grain(...)` in where/filter +time: 2023-09-18T11:21:59.459474-05:00 +custom: + Author: DevonFulcher + Issue: None From 1c7b68a51b7dce8f6b3369b0c030ca06591d03bb Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Tue, 19 Sep 2023 09:08:51 -0500 Subject: [PATCH 05/16] moved query_interface to protocols directory --- .../parsing/where_filter/where_filter_dimension.py | 2 +- .../parsing/where_filter/where_filter_entity.py | 2 +- .../parsing/where_filter/where_filter_time_dimension.py | 2 +- .../{parsing/where_filter => protocols}/query_interface.py | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename dbt_semantic_interfaces/{parsing/where_filter => protocols}/query_interface.py (100%) diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py index feed320f..81d5ac55 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py @@ -8,7 +8,7 @@ from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( ParameterSetFactory, ) -from dbt_semantic_interfaces.parsing.where_filter.query_interface import ( +from dbt_semantic_interfaces.protocols.query_interface import ( QueryInterfaceDimension, QueryInterfaceDimensionFactory, ) 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 ee747561..331e3398 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py @@ -8,7 +8,7 @@ from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( ParameterSetFactory, ) -from dbt_semantic_interfaces.parsing.where_filter.query_interface import ( +from dbt_semantic_interfaces.protocols.query_interface import ( QueryInterfaceEntity, QueryInterfaceEntityFactory, ) 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 979a0665..f8880d16 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 @@ -8,7 +8,7 @@ from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( ParameterSetFactory, ) -from dbt_semantic_interfaces.parsing.where_filter.query_interface import ( +from dbt_semantic_interfaces.protocols.query_interface import ( QueryInterfaceTimeDimension, QueryInterfaceTimeDimensionFactory, ) diff --git a/dbt_semantic_interfaces/parsing/where_filter/query_interface.py b/dbt_semantic_interfaces/protocols/query_interface.py similarity index 100% rename from dbt_semantic_interfaces/parsing/where_filter/query_interface.py rename to dbt_semantic_interfaces/protocols/query_interface.py From 584ef92146c51d1bea09d8e19394b16e3b009cb5 Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Tue, 19 Sep 2023 09:17:49 -0500 Subject: [PATCH 06/16] removed raise NotImplementedError in protocol. added abstractmethod decorator --- .../protocols/query_interface.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/dbt_semantic_interfaces/protocols/query_interface.py b/dbt_semantic_interfaces/protocols/query_interface.py index b0fe26cf..81c4bb3e 100644 --- a/dbt_semantic_interfaces/protocols/query_interface.py +++ b/dbt_semantic_interfaces/protocols/query_interface.py @@ -1,4 +1,5 @@ from __future__ import annotations +from abc import abstractmethod from typing import Protocol, Sequence @@ -6,13 +7,15 @@ class QueryInterfaceDimension(Protocol): """Represents the interface for Dimension in the query interface.""" + @abstractmethod def grain(self, _grain: str) -> QueryInterfaceDimension: """The time granularity.""" - raise NotImplementedError + pass + @abstractmethod def alias(self, _alias: str) -> QueryInterfaceDimension: """Renaming the column.""" - raise NotImplementedError + pass class QueryInterfaceDimensionFactory(Protocol): @@ -21,9 +24,10 @@ class QueryInterfaceDimensionFactory(Protocol): Represented as the Dimension constructor in the Jinja sandbox. """ + @abstractmethod def create(self, name: str, entity_path: Sequence[str] = ()) -> QueryInterfaceDimension: """Create a QueryInterfaceDimension.""" - raise NotImplementedError + pass class QueryInterfaceTimeDimension(Protocol): @@ -38,6 +42,7 @@ class QueryInterfaceTimeDimensionFactory(Protocol): Represented as the TimeDimension constructor in the Jinja sandbox. """ + @abstractmethod def create( self, time_dimension_name: str, @@ -45,7 +50,7 @@ def create( entity_path: Sequence[str] = (), ) -> QueryInterfaceTimeDimension: """Create a TimeDimension.""" - raise NotImplementedError + pass class QueryInterfaceEntity(Protocol): @@ -60,6 +65,7 @@ class QueryInterfaceEntityFactory(Protocol): Represented as the Entity constructor in the Jinja sandbox. """ + @abstractmethod def create(self, entity_name: str, entity_path: Sequence[str] = ()) -> QueryInterfaceEntity: """Create an Entity.""" - raise NotImplementedError + pass From 383558430ae9aa19d8fa2eb4b2b3e22a28270f76 Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Tue, 19 Sep 2023 09:19:30 -0500 Subject: [PATCH 07/16] removed alias --- .../parsing/where_filter/where_filter_dimension.py | 4 ---- dbt_semantic_interfaces/protocols/query_interface.py | 5 ----- 2 files changed, 9 deletions(-) diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py index 81d5ac55..740fff68 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py @@ -42,10 +42,6 @@ def grain(self, time_granularity: str) -> QueryInterfaceDimension: ) return self - def alias(self, _alias: str) -> QueryInterfaceDimension: - """Renaming the column.""" - raise NotImplementedError - class WhereFilterDimensionFactory(ProtocolHint[QueryInterfaceDimensionFactory]): """Creates a WhereFilterDimension. diff --git a/dbt_semantic_interfaces/protocols/query_interface.py b/dbt_semantic_interfaces/protocols/query_interface.py index 81c4bb3e..bacc2fa1 100644 --- a/dbt_semantic_interfaces/protocols/query_interface.py +++ b/dbt_semantic_interfaces/protocols/query_interface.py @@ -12,11 +12,6 @@ def grain(self, _grain: str) -> QueryInterfaceDimension: """The time granularity.""" pass - @abstractmethod - def alias(self, _alias: str) -> QueryInterfaceDimension: - """Renaming the column.""" - pass - class QueryInterfaceDimensionFactory(Protocol): """Creates a Dimension for the query interface. From 5309a49cc8ee86bba6e0f89ee4359f2592c5fc00 Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Tue, 19 Sep 2023 09:22:15 -0500 Subject: [PATCH 08/16] made error message more user friendly --- .../parsing/where_filter/parameter_set_factory.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 eebe5258..3d8ddadd 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py +++ b/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py @@ -42,7 +42,8 @@ def create_time_dimension( if len(group_by_item_name.entity_links) != 0 or group_by_item_name.time_granularity is not None: raise ParseWhereFilterException( f"Name is in an incorrect format: {time_dimension_name} " - f"When referencing {METRIC_TIME_ELEMENT_NAME}, the name should not have any dunders." + f"When referencing {METRIC_TIME_ELEMENT_NAME}," + "the name should not have any dunders (double underscores, or __)." ) else: if len(group_by_item_name.entity_links) != 1 or group_by_item_name.time_granularity is not None: From bcc6d8e643ea7a0cdb30bae1700d4f49355933f3 Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Tue, 19 Sep 2023 09:23:45 -0500 Subject: [PATCH 09/16] improved another error message --- .../parsing/where_filter/parameter_set_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3d8ddadd..d3eae49a 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py +++ b/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py @@ -86,7 +86,7 @@ def create_entity(entity_name: str, entity_path: Sequence[str] = ()) -> EntityCa if len(group_by_item_name.entity_links) > 0 or group_by_item_name.time_granularity is not None: ParameterSetFactory._exception_message_for_incorrect_format( f"Name is in an incorrect format: {entity_name} " - f"When referencing entities, the name should not have any dunders." + f"When referencing entities, the name should not have any dunders (double underscores, or __)." ) return EntityCallParameterSet( From e3ef46c0650d18da472005f7b836d179fbc11986 Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Wed, 20 Sep 2023 13:09:13 -0500 Subject: [PATCH 10/16] appending to time_dimension_call_parameter_sets --- .../where_filter/where_filter_dimension.py | 16 +++------------- .../parsing/where_filter/where_filter_parser.py | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py index 740fff68..f6b508af 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py @@ -4,10 +4,6 @@ from typing_extensions import override -from dbt_semantic_interfaces.call_parameter_sets import TimeDimensionCallParameterSet -from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( - ParameterSetFactory, -) from dbt_semantic_interfaces.protocols.query_interface import ( QueryInterfaceDimension, QueryInterfaceDimensionFactory, @@ -27,38 +23,32 @@ def __init__( # noqa self, name: str, entity_path: Sequence[str], - time_dimension_call_parameter_sets: List[TimeDimensionCallParameterSet], ): self.name = name self.entity_path = entity_path - self._time_dimension_call_parameter_sets = time_dimension_call_parameter_sets self.time_granularity: Optional[TimeGranularity] = None def grain(self, time_granularity: str) -> QueryInterfaceDimension: """The time granularity.""" self.time_granularity = TimeGranularity(time_granularity) - self._time_dimension_call_parameter_sets.append( - ParameterSetFactory.create_time_dimension(self.name, time_granularity, self.entity_path) - ) return self class WhereFilterDimensionFactory(ProtocolHint[QueryInterfaceDimensionFactory]): """Creates a WhereFilterDimension. - Each call to `create` adds a WhereFilterDimension to created. + Each call to `create` adds a WhereFilterDimension to `created`. """ @override def _implements_protocol(self) -> QueryInterfaceDimensionFactory: return self - def __init__(self, time_dimension_call_parameter_sets: List[TimeDimensionCallParameterSet]): # noqa + def __init__(self): # noqa self.created: List[WhereFilterDimension] = [] - self._time_dimension_call_parameter_sets = time_dimension_call_parameter_sets 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._time_dimension_call_parameter_sets) + dimension = WhereFilterDimension(dimension_name, entity_path) self.created.append(dimension) return dimension 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 aa069cc2..e1767252 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py @@ -29,7 +29,7 @@ class WhereFilterParser: def parse_call_parameter_sets(where_sql_template: str) -> FilterCallParameterSets: """Return the result of extracting the semantic objects referenced in the where SQL template string.""" time_dimension_factory = WhereFilterTimeDimensionFactory() - dimension_factory = WhereFilterDimensionFactory(time_dimension_factory.time_dimension_call_parameter_sets) + dimension_factory = WhereFilterDimensionFactory() entity_factory = WhereFilterEntityFactory() try: @@ -44,9 +44,18 @@ def parse_call_parameter_sets(where_sql_template: str) -> FilterCallParameterSet dimension_parameter_sets = [] for dimension in dimension_factory.created: - if not dimension.time_granularity: - param_set = ParameterSetFactory.create_dimension(dimension.name, dimension.entity_path) - dimension_parameter_sets.append(param_set) + if dimension.time_granularity: + time_dimension_factory.time_dimension_call_parameter_sets.append( + ParameterSetFactory.create_time_dimension( + dimension.name, + dimension.time_granularity, + dimension.entity_path, + ) + ) + else: + dimension_parameter_sets.append( + ParameterSetFactory.create_dimension(dimension.name, dimension.entity_path) + ) return FilterCallParameterSets( dimension_call_parameter_sets=tuple(dimension_parameter_sets), From 3d7f5fe6e0de5cf6a2651f29230dcaec6f8f7993 Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Wed, 20 Sep 2023 13:36:20 -0500 Subject: [PATCH 11/16] added comments --- .../parsing/where_filter/where_filter_entity.py | 6 +++++- .../parsing/where_filter/where_filter_parser.py | 10 +++++++--- .../where_filter/where_filter_time_dimension.py | 6 +++++- 3 files changed, 17 insertions(+), 5 deletions(-) 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 331e3398..b6b66784 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py @@ -16,7 +16,11 @@ class EntityStub(ProtocolHint[QueryInterfaceEntity]): - """An Entity implementation that does nothing.""" + """An Entity implementation that does nothing to satisfy 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: 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 e1767252..f8a9db9e 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py @@ -42,7 +42,11 @@ def parse_call_parameter_sets(where_sql_template: str) -> FilterCallParameterSet except (UndefinedError, TemplateSyntaxError, SecurityError) as e: raise ParseWhereFilterException(f"Error while parsing Jinja template:\n{where_sql_template}") from e - dimension_parameter_sets = [] + """ + Dimensions that are created with a grain parameter, Dimension(...).grain(...), are + added to time_dimension_call_parameter_sets otherwise they are add to dimension_call_parameter_sets + """ + dimension_call_parameter_sets = [] for dimension in dimension_factory.created: if dimension.time_granularity: time_dimension_factory.time_dimension_call_parameter_sets.append( @@ -53,12 +57,12 @@ def parse_call_parameter_sets(where_sql_template: str) -> FilterCallParameterSet ) ) else: - dimension_parameter_sets.append( + dimension_call_parameter_sets.append( ParameterSetFactory.create_dimension(dimension.name, dimension.entity_path) ) return FilterCallParameterSets( - dimension_call_parameter_sets=tuple(dimension_parameter_sets), + dimension_call_parameter_sets=tuple(dimension_call_parameter_sets), time_dimension_call_parameter_sets=tuple(time_dimension_factory.time_dimension_call_parameter_sets), entity_call_parameter_sets=tuple(entity_factory.entity_call_parameter_sets), ) 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 f8880d16..54ac8a2c 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 @@ -16,7 +16,11 @@ class TimeDimensionStub(ProtocolHint[QueryInterfaceTimeDimension]): - """A TimeDimension implementation that does nothing.""" + """A TimeDimension implementation that does nothing to satisfy 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: From 13ebd521215c91d4c4b5b602d3c40c3f4b619464 Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Wed, 20 Sep 2023 15:00:32 -0500 Subject: [PATCH 12/16] fixed error message --- .../parsing/where_filter/parameter_set_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d3eae49a..bbcfc722 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py +++ b/dbt_semantic_interfaces/parsing/where_filter/parameter_set_factory.py @@ -66,7 +66,7 @@ def create_dimension(dimension_name: str, entity_path: Sequence[str] = ()) -> Di if is_metric_time_name(group_by_item_name.element_name): raise ParseWhereFilterException( f"{METRIC_TIME_ELEMENT_NAME} is a time dimension, so it should be referenced using " - f"TimeDimension(...)" + f"TimeDimension(...) or Dimension(...).grain(...)" ) if len(group_by_item_name.entity_links) != 1: From 804937a5e1957d3f214de101c6f0e7f956ac8ede Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Wed, 20 Sep 2023 15:11:20 -0500 Subject: [PATCH 13/16] slight language change --- .../parsing/where_filter/where_filter_entity.py | 2 +- .../parsing/where_filter/where_filter_time_dimension.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 b6b66784..62ba32c9 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py @@ -16,7 +16,7 @@ class EntityStub(ProtocolHint[QueryInterfaceEntity]): - """An Entity implementation that does nothing to satisfy the protocol. + """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. 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 54ac8a2c..2873b8e5 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 @@ -16,7 +16,7 @@ class TimeDimensionStub(ProtocolHint[QueryInterfaceTimeDimension]): - """A TimeDimension implementation that does nothing to satisfy the protocol. + """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. From a374894ba383532b857e7ea7e756eaee026e757e Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Wed, 20 Sep 2023 15:22:22 -0500 Subject: [PATCH 14/16] fixed linter & formatter --- .../parsing/where_filter/where_filter_dimension.py | 11 +++++------ .../parsing/where_filter/where_filter_entity.py | 2 +- .../parsing/where_filter/where_filter_parser.py | 4 ++-- .../where_filter/where_filter_time_dimension.py | 2 +- dbt_semantic_interfaces/protocols/query_interface.py | 2 +- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py index f6b508af..4a873cca 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_dimension.py @@ -4,12 +4,11 @@ from typing_extensions import override +from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint from dbt_semantic_interfaces.protocols.query_interface import ( QueryInterfaceDimension, QueryInterfaceDimensionFactory, ) -from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint -from dbt_semantic_interfaces.type_enums.time_granularity import TimeGranularity class WhereFilterDimension(ProtocolHint[QueryInterfaceDimension]): @@ -23,14 +22,14 @@ def __init__( # noqa self, name: str, entity_path: Sequence[str], - ): + ) -> None: self.name = name self.entity_path = entity_path - self.time_granularity: Optional[TimeGranularity] = None + self.time_granularity_name: Optional[str] = None def grain(self, time_granularity: str) -> QueryInterfaceDimension: """The time granularity.""" - self.time_granularity = TimeGranularity(time_granularity) + self.time_granularity_name = time_granularity return self @@ -44,7 +43,7 @@ class WhereFilterDimensionFactory(ProtocolHint[QueryInterfaceDimensionFactory]): def _implements_protocol(self) -> QueryInterfaceDimensionFactory: return self - def __init__(self): # noqa + def __init__(self) -> None: # noqa self.created: List[WhereFilterDimension] = [] def create(self, dimension_name: str, entity_path: Sequence[str] = ()) -> WhereFilterDimension: 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 62ba32c9..83e5fe8c 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_entity.py @@ -8,11 +8,11 @@ from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( ParameterSetFactory, ) +from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint from dbt_semantic_interfaces.protocols.query_interface import ( QueryInterfaceEntity, QueryInterfaceEntityFactory, ) -from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint class EntityStub(ProtocolHint[QueryInterfaceEntity]): 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 f8a9db9e..0303572a 100644 --- a/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py +++ b/dbt_semantic_interfaces/parsing/where_filter/where_filter_parser.py @@ -48,11 +48,11 @@ def parse_call_parameter_sets(where_sql_template: str) -> FilterCallParameterSet """ dimension_call_parameter_sets = [] for dimension in dimension_factory.created: - if dimension.time_granularity: + if dimension.time_granularity_name: time_dimension_factory.time_dimension_call_parameter_sets.append( ParameterSetFactory.create_time_dimension( dimension.name, - dimension.time_granularity, + dimension.time_granularity_name, dimension.entity_path, ) ) 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 2873b8e5..3da96607 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 @@ -8,11 +8,11 @@ from dbt_semantic_interfaces.parsing.where_filter.parameter_set_factory import ( ParameterSetFactory, ) +from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint from dbt_semantic_interfaces.protocols.query_interface import ( QueryInterfaceTimeDimension, QueryInterfaceTimeDimensionFactory, ) -from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint class TimeDimensionStub(ProtocolHint[QueryInterfaceTimeDimension]): diff --git a/dbt_semantic_interfaces/protocols/query_interface.py b/dbt_semantic_interfaces/protocols/query_interface.py index bacc2fa1..8cc6db4a 100644 --- a/dbt_semantic_interfaces/protocols/query_interface.py +++ b/dbt_semantic_interfaces/protocols/query_interface.py @@ -1,6 +1,6 @@ from __future__ import annotations -from abc import abstractmethod +from abc import abstractmethod from typing import Protocol, Sequence From ef68e7445a5a9531f8246247f92adcf520a463ac Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Wed, 20 Sep 2023 15:35:20 -0500 Subject: [PATCH 15/16] fixed import --- dbt_semantic_interfaces/validations/saved_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbt_semantic_interfaces/validations/saved_query.py b/dbt_semantic_interfaces/validations/saved_query.py index 7ef85385..ce629f82 100644 --- a/dbt_semantic_interfaces/validations/saved_query.py +++ b/dbt_semantic_interfaces/validations/saved_query.py @@ -4,7 +4,7 @@ from dbt_semantic_interfaces.call_parameter_sets import FilterCallParameterSets from dbt_semantic_interfaces.naming.keywords import METRIC_TIME_ELEMENT_NAME -from dbt_semantic_interfaces.parsing.where_filter_parser import WhereFilterParser +from dbt_semantic_interfaces.parsing.where_filter.where_filter_parser import WhereFilterParser from dbt_semantic_interfaces.protocols import SemanticManifestT from dbt_semantic_interfaces.protocols.saved_query import SavedQuery from dbt_semantic_interfaces.validations.validator_helpers import ( From 03dc5472dda5a2f5d02bd2d5b725810425bb0e02 Mon Sep 17 00:00:00 2001 From: Devon Fulcher Date: Wed, 20 Sep 2023 15:39:14 -0500 Subject: [PATCH 16/16] fixed formatting --- dbt_semantic_interfaces/validations/saved_query.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dbt_semantic_interfaces/validations/saved_query.py b/dbt_semantic_interfaces/validations/saved_query.py index ce629f82..0b2ecd4c 100644 --- a/dbt_semantic_interfaces/validations/saved_query.py +++ b/dbt_semantic_interfaces/validations/saved_query.py @@ -4,7 +4,9 @@ from dbt_semantic_interfaces.call_parameter_sets import FilterCallParameterSets from dbt_semantic_interfaces.naming.keywords import METRIC_TIME_ELEMENT_NAME -from dbt_semantic_interfaces.parsing.where_filter.where_filter_parser import WhereFilterParser +from dbt_semantic_interfaces.parsing.where_filter.where_filter_parser import ( + WhereFilterParser, +) from dbt_semantic_interfaces.protocols import SemanticManifestT from dbt_semantic_interfaces.protocols.saved_query import SavedQuery from dbt_semantic_interfaces.validations.validator_helpers import (