diff --git a/metricflow-semantics/metricflow_semantics/model/semantic_manifest_lookup.py b/metricflow-semantics/metricflow_semantics/model/semantic_manifest_lookup.py index c652aaf177..03af5406fc 100644 --- a/metricflow-semantics/metricflow_semantics/model/semantic_manifest_lookup.py +++ b/metricflow-semantics/metricflow_semantics/model/semantic_manifest_lookup.py @@ -16,10 +16,16 @@ class SemanticManifestLookup: def __init__(self, semantic_manifest: SemanticManifest) -> None: # noqa: D107 self._semantic_manifest = semantic_manifest - self._semantic_model_lookup = SemanticModelLookup(semantic_manifest) - self._metric_lookup = MetricLookup(self._semantic_manifest, self._semantic_model_lookup) self._time_spine_sources = TimeSpineSource.build_standard_time_spine_sources(semantic_manifest) self._custom_granularities = TimeSpineSource.build_custom_granularities(list(self._time_spine_sources.values())) + self._semantic_model_lookup = SemanticModelLookup( + model=semantic_manifest, custom_granularities=self._custom_granularities + ) + self._metric_lookup = MetricLookup( + semantic_manifest=self._semantic_manifest, + semantic_model_lookup=self._semantic_model_lookup, + custom_granularities=self._custom_granularities, + ) @property def semantic_manifest(self) -> SemanticManifest: # noqa: D102 diff --git a/metricflow-semantics/metricflow_semantics/model/semantics/metric_lookup.py b/metricflow-semantics/metricflow_semantics/model/semantics/metric_lookup.py index b46be1214a..0e3d501a56 100644 --- a/metricflow-semantics/metricflow_semantics/model/semantics/metric_lookup.py +++ b/metricflow-semantics/metricflow_semantics/model/semantics/metric_lookup.py @@ -20,6 +20,7 @@ from metricflow_semantics.model.semantics.semantic_model_join_evaluator import MAX_JOIN_HOPS from metricflow_semantics.model.semantics.semantic_model_lookup import SemanticModelLookup from metricflow_semantics.specs.time_dimension_spec import TimeDimensionSpec +from metricflow_semantics.time.granularity import ExpandedTimeGranularity logger = logging.getLogger(__name__) @@ -27,7 +28,12 @@ class MetricLookup: """Tracks semantic information for metrics by linking them to semantic models.""" - def __init__(self, semantic_manifest: SemanticManifest, semantic_model_lookup: SemanticModelLookup) -> None: + def __init__( + self, + semantic_manifest: SemanticManifest, + semantic_model_lookup: SemanticModelLookup, + custom_granularities: Dict[str, ExpandedTimeGranularity], + ) -> None: """Initializer. Args: @@ -36,6 +42,7 @@ def __init__(self, semantic_manifest: SemanticManifest, semantic_model_lookup: S """ self._metrics: Dict[MetricReference, Metric] = {} self._semantic_model_lookup = semantic_model_lookup + self._custom_granularities = custom_granularities for metric in semantic_manifest.metrics: self._add_metric(metric) @@ -185,6 +192,7 @@ def get_valid_agg_time_dimensions_for_metric( valid_agg_time_dimension_specs = TimeDimensionSpec.generate_possible_specs_for_time_dimension( time_dimension_reference=agg_time_dimension_reference, entity_links=agg_time_dimension_entity_links, + custom_granularities=self._custom_granularities, ) return valid_agg_time_dimension_specs diff --git a/metricflow-semantics/metricflow_semantics/model/semantics/semantic_model_lookup.py b/metricflow-semantics/metricflow_semantics/model/semantics/semantic_model_lookup.py index 59931b2090..4e524c650b 100644 --- a/metricflow-semantics/metricflow_semantics/model/semantics/semantic_model_lookup.py +++ b/metricflow-semantics/metricflow_semantics/model/semantics/semantic_model_lookup.py @@ -30,6 +30,7 @@ from metricflow_semantics.specs.measure_spec import MeasureSpec from metricflow_semantics.specs.non_additive_dimension_spec import NonAdditiveDimensionSpec from metricflow_semantics.specs.time_dimension_spec import DEFAULT_TIME_GRANULARITY, TimeDimensionSpec +from metricflow_semantics.time.granularity import ExpandedTimeGranularity logger = logging.getLogger(__name__) @@ -37,15 +38,13 @@ class SemanticModelLookup: """Tracks semantic information for semantic models held in a set of SemanticModelContainers.""" - def __init__( - self, - model: SemanticManifest, - ) -> None: + def __init__(self, model: SemanticManifest, custom_granularities: Dict[str, ExpandedTimeGranularity]) -> None: """Initializer. Args: model: the semantic manifest used for loading semantic model definitions """ + self._custom_granularities = custom_granularities self._measure_index: Dict[MeasureReference, SemanticModel] = {} self._measure_aggs: Dict[MeasureReference, AggregationType] = {} self._measure_agg_time_dimension: Dict[MeasureReference, TimeDimensionReference] = {} @@ -371,6 +370,7 @@ def get_agg_time_dimension_specs_for_measure( return TimeDimensionSpec.generate_possible_specs_for_time_dimension( time_dimension_reference=agg_time_dimension, entity_links=(entity_link,), + custom_granularities=self._custom_granularities, ) def get_defined_time_granularity(self, time_dimension_reference: TimeDimensionReference) -> TimeGranularity: diff --git a/metricflow-semantics/metricflow_semantics/query/validation_rules/metric_time_requirements.py b/metricflow-semantics/metricflow_semantics/query/validation_rules/metric_time_requirements.py index 0b3d47bc64..9d67dce22c 100644 --- a/metricflow-semantics/metricflow_semantics/query/validation_rules/metric_time_requirements.py +++ b/metricflow-semantics/metricflow_semantics/query/validation_rules/metric_time_requirements.py @@ -37,7 +37,9 @@ def __init__(self, manifest_lookup: SemanticManifestLookup) -> None: # noqa: D1 self._metric_time_specs = tuple( TimeDimensionSpec.generate_possible_specs_for_time_dimension( - time_dimension_reference=TimeDimensionReference(element_name=METRIC_TIME_ELEMENT_NAME), entity_links=() + time_dimension_reference=TimeDimensionReference(element_name=METRIC_TIME_ELEMENT_NAME), + entity_links=(), + custom_granularities=self._manifest_lookup._custom_granularities, ) ) diff --git a/metricflow-semantics/metricflow_semantics/specs/time_dimension_spec.py b/metricflow-semantics/metricflow_semantics/specs/time_dimension_spec.py index 3aec625a22..9054a00b7a 100644 --- a/metricflow-semantics/metricflow_semantics/specs/time_dimension_spec.py +++ b/metricflow-semantics/metricflow_semantics/specs/time_dimension_spec.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from enum import Enum -from typing import Any, List, Optional, Sequence, Tuple, Union +from typing import Any, Dict, List, Optional, Sequence, Tuple, Union from dbt_semantic_interfaces.naming.keywords import METRIC_TIME_ELEMENT_NAME from dbt_semantic_interfaces.references import DimensionReference, EntityReference, TimeDimensionReference @@ -193,30 +193,31 @@ def comparison_key(self, exclude_fields: Sequence[TimeDimensionSpecField] = ()) @staticmethod def generate_possible_specs_for_time_dimension( - time_dimension_reference: TimeDimensionReference, entity_links: Tuple[EntityReference, ...] + time_dimension_reference: TimeDimensionReference, + entity_links: Tuple[EntityReference, ...], + custom_granularities: Dict[str, ExpandedTimeGranularity], ) -> List[TimeDimensionSpec]: - """Generate a list of time dimension specs with all combinations of granularity & date part. - - TODO: [custom calendar] decide whether to add support for custom granularities or rename this to indicate that - it only includes standard granularities. - """ + """Generate a list of time dimension specs with all combinations of granularity & date part.""" time_dimension_specs: List[TimeDimensionSpec] = [] - for time_granularity in TimeGranularity: + granularities = [ + ExpandedTimeGranularity.from_time_granularity(time_granularity) for time_granularity in TimeGranularity + ] + list(custom_granularities.values()) + for time_granularity in granularities: time_dimension_specs.append( TimeDimensionSpec( element_name=time_dimension_reference.element_name, entity_links=entity_links, - time_granularity=ExpandedTimeGranularity.from_time_granularity(time_granularity), + time_granularity=time_granularity, date_part=None, ) ) for date_part in DatePart: - for time_granularity in date_part.compatible_granularities: + for compatible_granularity in date_part.compatible_granularities: time_dimension_specs.append( TimeDimensionSpec( element_name=time_dimension_reference.element_name, entity_links=entity_links, - time_granularity=ExpandedTimeGranularity.from_time_granularity(time_granularity), + time_granularity=ExpandedTimeGranularity.from_time_granularity(compatible_granularity), date_part=date_part, ) ) diff --git a/metricflow-semantics/tests_metricflow_semantics/model/test_semantic_model_container.py b/metricflow-semantics/tests_metricflow_semantics/model/test_semantic_model_container.py index bf872becaf..ebd38b4d5d 100644 --- a/metricflow-semantics/tests_metricflow_semantics/model/test_semantic_model_container.py +++ b/metricflow-semantics/tests_metricflow_semantics/model/test_semantic_model_container.py @@ -14,29 +14,38 @@ assert_linkable_element_set_snapshot_equal, assert_object_snapshot_equal, ) +from metricflow_semantics.time.time_spine_source import TimeSpineSource logger = logging.getLogger(__name__) +def build_semantic_model_lookup_from_manifest(semantic_manifest: SemanticManifest) -> SemanticModelLookup: # noqa: D103 + time_spine_sources = TimeSpineSource.build_standard_time_spine_sources(semantic_manifest) + custom_granularities = TimeSpineSource.build_custom_granularities(list(time_spine_sources.values())) + return SemanticModelLookup(model=semantic_manifest, custom_granularities=custom_granularities) + + @pytest.fixture def semantic_model_lookup(simple_semantic_manifest: SemanticManifest) -> SemanticModelLookup: # noqa: D103 - return SemanticModelLookup( - model=simple_semantic_manifest, - ) + return build_semantic_model_lookup_from_manifest(simple_semantic_manifest) @pytest.fixture def multi_hop_semantic_model_lookup( # noqa: D103 multi_hop_join_manifest: SemanticManifest, ) -> SemanticModelLookup: - return SemanticModelLookup(model=multi_hop_join_manifest) + return build_semantic_model_lookup_from_manifest(multi_hop_join_manifest) @pytest.fixture def metric_lookup( # noqa: D103 simple_semantic_manifest: SemanticManifest, semantic_model_lookup: SemanticModelLookup ) -> MetricLookup: - return MetricLookup(semantic_manifest=simple_semantic_manifest, semantic_model_lookup=semantic_model_lookup) + return MetricLookup( + semantic_manifest=simple_semantic_manifest, + semantic_model_lookup=semantic_model_lookup, + custom_granularities=semantic_model_lookup._custom_granularities, + ) @pytest.fixture @@ -46,6 +55,7 @@ def multi_hop_metric_lookup( # noqa: D103 return MetricLookup( semantic_manifest=multi_hop_join_manifest, semantic_model_lookup=multi_hop_semantic_model_lookup, + custom_granularities=multi_hop_semantic_model_lookup._custom_granularities, ) diff --git a/tests_metricflow/integration/test_configured_cases.py b/tests_metricflow/integration/test_configured_cases.py index 1235f9c5f1..b192c7acae 100644 --- a/tests_metricflow/integration/test_configured_cases.py +++ b/tests_metricflow/integration/test_configured_cases.py @@ -214,8 +214,7 @@ def filter_not_supported_features( @pytest.mark.parametrize( "name", - # CONFIGURED_INTEGRATION_TESTS_REPOSITORY.all_test_case_names, - ["itest_granularity.yaml/cumulative_metric_with_custom_granularity"], + CONFIGURED_INTEGRATION_TESTS_REPOSITORY.all_test_case_names, ids=lambda name: f"name={name}", ) def test_case( @@ -304,7 +303,6 @@ def test_case( ) actual = query_result.result_df - assert 0, query_result.sql expected = sql_client.query( jinja2.Template(