From 3fd75e8a86a2529c4023a52ecf265b3b9205be38 Mon Sep 17 00:00:00 2001 From: Courtney Holcomb Date: Mon, 8 Jul 2024 18:31:24 -0700 Subject: [PATCH] Allow passing granularity in where filter `TimeDimension` name (#1316) Allow users to use dunder syntax to pass granularity in where filter `TimeDimension`. Ex: `{{ TimeDimension('metric_time__month') }} > 2020-01-01` This behavior is currently implemented everywhere else that similar Jinja parsing happens, making this one site inconsistent. Fix that to avoid user confusion. --- .../requirements.txt | 2 +- .../specs/where_filter_time_dimension.py | 11 +++- .../model/test_where_filter_spec.py | 64 +++++++++++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/metricflow-semantics/extra-hatch-configuration/requirements.txt b/metricflow-semantics/extra-hatch-configuration/requirements.txt index 6131ae86f3..0c67346e52 100644 --- a/metricflow-semantics/extra-hatch-configuration/requirements.txt +++ b/metricflow-semantics/extra-hatch-configuration/requirements.txt @@ -1,5 +1,5 @@ # dbt Cloud depends on metricflow-semantics (dependency set in dbt-mantle), so DSI must always point to a production version here. -dbt-semantic-interfaces>=0.6.1, <2.0.0 +dbt-semantic-interfaces>=0.6.5, <2.0.0 graphviz>=0.18.2, <0.21 python-dateutil>=2.9.0, <2.10.0 rapidfuzz>=3.0, <4.0 diff --git a/metricflow-semantics/metricflow_semantics/specs/where_filter_time_dimension.py b/metricflow-semantics/metricflow_semantics/specs/where_filter_time_dimension.py index a896b73b03..122bd26df3 100644 --- a/metricflow-semantics/metricflow_semantics/specs/where_filter_time_dimension.py +++ b/metricflow-semantics/metricflow_semantics/specs/where_filter_time_dimension.py @@ -91,6 +91,15 @@ def create( ) structured_name = DunderedNameFormatter.parse_name(time_dimension_name.lower()) + grain_parsed_from_name = structured_name.time_granularity + grain_from_param = TimeGranularity(time_granularity_name) if time_granularity_name else None + if grain_parsed_from_name and grain_from_param and grain_parsed_from_name != grain_from_param: + raise InvalidQuerySyntax( + f"Received different granularities in `time_dimension_name` parameter ('{time_dimension_name}') " + f"and `time_granularity_name` parameter ('{time_granularity_name}')." + ) + + TimeGranularity(time_granularity_name.lower()) if time_granularity_name else None return WhereFilterTimeDimension( column_association_resolver=self._column_association_resolver, resolved_spec_lookup=self._resolved_spec_lookup, @@ -99,6 +108,6 @@ def create( element_name=structured_name.element_name, entity_links=tuple(EntityReference(entity_link_name.lower()) for entity_link_name in entity_path) + structured_name.entity_links, - time_grain=TimeGranularity(time_granularity_name.lower()) if time_granularity_name else None, + time_grain=grain_from_param or grain_parsed_from_name, date_part=DatePart(date_part_name.lower()) if date_part_name else None, ) diff --git a/metricflow-semantics/tests_metricflow_semantics/model/test_where_filter_spec.py b/metricflow-semantics/tests_metricflow_semantics/model/test_where_filter_spec.py index e327580762..47152cae1e 100644 --- a/metricflow-semantics/tests_metricflow_semantics/model/test_where_filter_spec.py +++ b/metricflow-semantics/tests_metricflow_semantics/model/test_where_filter_spec.py @@ -275,6 +275,70 @@ def test_time_dimension_in_filter( # noqa: D103 ) +def test_time_dimension_with_grain_in_name( # noqa: D103 + column_association_resolver: ColumnAssociationResolver, +) -> None: + where_filter_specs = WhereSpecFactory( + column_association_resolver=column_association_resolver, + spec_resolution_lookup=create_spec_lookup( + call_parameter_set=TimeDimensionCallParameterSet( + entity_path=(EntityReference("listing"),), + time_dimension_reference=TimeDimensionReference("created_at"), + time_granularity=TimeGranularity.MONTH, + ), + resolved_spec=TimeDimensionSpec( + element_name="created_at", + entity_links=(EntityReference("listing"),), + time_granularity=TimeGranularity.MONTH, + ), + resolved_linkable_element_set=LinkableElementSet( + path_key_to_linkable_dimensions={ + ElementPathKey( + element_name="created_at", + element_type=LinkableElementType.TIME_DIMENSION, + entity_links=(EntityReference("listing"),), + time_granularity=TimeGranularity.MONTH, + date_part=None, + ): ( + LinkableDimension( + defined_in_semantic_model=SemanticModelReference("listings_source"), + dimension_type=DimensionType.CATEGORICAL, + element_name="created_at", + entity_links=(EntityReference("listing"),), + join_path=SemanticModelJoinPath( + left_semantic_model_reference=SemanticModelReference("bookings_source"), + ), + properties=frozenset(), + time_granularity=TimeGranularity.MONTH, + date_part=None, + ), + ) + } + ), + ), + ).create_from_where_filter_intersection( + filter_location=EXAMPLE_FILTER_LOCATION, + filter_intersection=create_where_filter_intersection( + "{{ TimeDimension('listing__created_at__month') }} = '2020-01-01'" + ), + ) + assert len(where_filter_specs) == 1 + where_filter_spec = where_filter_specs[0] + assert where_filter_spec.where_sql == "listing__created_at__month = '2020-01-01'" + assert LinkableSpecSet.create_from_specs(where_filter_spec.linkable_specs) == LinkableSpecSet( + dimension_specs=(), + time_dimension_specs=( + TimeDimensionSpec( + element_name="created_at", + entity_links=(EntityReference(element_name="listing"),), + time_granularity=TimeGranularity.MONTH, + ), + ), + entity_specs=(), + group_by_metric_specs=(), + ) + + def test_date_part_in_filter( # noqa: D103 column_association_resolver: ColumnAssociationResolver, ) -> None: