-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add default_granularity
to metric spec
#291
Changes from 4 commits
a7688aa
bbfd12e
b066c5b
3005c7d
7e4de5b
23ee45b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
kind: Features | ||
body: Add default_granularity to metric spec. | ||
time: 2024-06-14T14:05:15.355931-07:00 | ||
custom: | ||
Author: courtneyholcomb | ||
Issue: "290" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Any, Dict, List, Optional, Sequence | ||
from typing import Any, Dict, List, Optional, Sequence, Set | ||
|
||
from typing_extensions import override | ||
|
||
|
@@ -16,7 +16,12 @@ | |
PydanticWhereFilterIntersection, | ||
) | ||
from dbt_semantic_interfaces.implementations.metadata import PydanticMetadata | ||
from dbt_semantic_interfaces.protocols import MetricConfig, ProtocolHint | ||
from dbt_semantic_interfaces.protocols import ( | ||
Metric, | ||
MetricConfig, | ||
MetricInputMeasure, | ||
ProtocolHint, | ||
) | ||
from dbt_semantic_interfaces.references import MeasureReference, MetricReference | ||
from dbt_semantic_interfaces.type_enums import ( | ||
ConversionCalculationType, | ||
|
@@ -191,9 +196,13 @@ def _implements_protocol(self) -> MetricConfig: # noqa: D | |
meta: Dict[str, Any] = Field(default_factory=dict) | ||
|
||
|
||
class PydanticMetric(HashableBaseModel, ModelWithMetadataParsing): | ||
class PydanticMetric(HashableBaseModel, ModelWithMetadataParsing, ProtocolHint[Metric]): | ||
"""Describes a metric.""" | ||
|
||
@override | ||
def _implements_protocol(self) -> Metric: # noqa: D | ||
return self | ||
|
||
name: str | ||
description: Optional[str] | ||
type: MetricType | ||
|
@@ -202,6 +211,7 @@ class PydanticMetric(HashableBaseModel, ModelWithMetadataParsing): | |
metadata: Optional[PydanticMetadata] | ||
label: Optional[str] = None | ||
config: Optional[PydanticMetricConfig] | ||
default_granularity: Optional[TimeGranularity] = None | ||
|
||
@property | ||
def input_measures(self) -> Sequence[PydanticMetricInputMeasure]: | ||
|
@@ -228,3 +238,31 @@ def input_metrics(self) -> Sequence[PydanticMetricInput]: | |
return (self.type_params.numerator, self.type_params.denominator) | ||
else: | ||
assert_values_exhausted(self.type) | ||
|
||
@staticmethod | ||
def all_input_measures_for_metric( | ||
metric: Metric, metric_index: Dict[MetricReference, Metric] | ||
) -> Set[MetricInputMeasure]: | ||
"""Gets all input measures for the metric, including those defined on input metrics (recursively).""" | ||
measures = set() | ||
if metric.type is MetricType.SIMPLE or metric.type is MetricType.CUMULATIVE: | ||
assert ( | ||
metric.type_params.measure is not None | ||
), f"Metric {metric.name} should have a measure defined, but it does not." | ||
measures.add(metric.type_params.measure) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not blocking, but heads up that this could end up with duplicate measures. Ie., If you don't want duplicate measures showing up in this, you could return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call!! |
||
elif metric.type is MetricType.DERIVED or metric.type is MetricType.RATIO: | ||
for input_metric in metric.input_metrics: | ||
nested_metric = metric_index.get(MetricReference(input_metric.name)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could just use the |
||
assert nested_metric, f"Could not find metric {input_metric.name} in semantic manifest." | ||
measures.update( | ||
PydanticMetric.all_input_measures_for_metric(metric=nested_metric, metric_index=metric_index) | ||
) | ||
elif metric.type is MetricType.CONVERSION: | ||
conversion_type_params = metric.type_params.conversion_type_params | ||
assert conversion_type_params, "Conversion metric should have conversion_type_params." | ||
measures.add(conversion_type_params.base_measure) | ||
measures.add(conversion_type_params.conversion_measure) | ||
else: | ||
assert_values_exhausted(metric.type) | ||
|
||
return measures |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
from typing import Set | ||
|
||
from typing_extensions import override | ||
|
||
from dbt_semantic_interfaces.implementations.semantic_manifest import ( | ||
PydanticSemanticManifest, | ||
) | ||
from dbt_semantic_interfaces.protocols import ProtocolHint | ||
from dbt_semantic_interfaces.references import ( | ||
DimensionReference, | ||
TimeDimensionReference, | ||
) | ||
from dbt_semantic_interfaces.transformations.transform_rule import ( | ||
SemanticManifestTransformRule, | ||
) | ||
from dbt_semantic_interfaces.type_enums.time_granularity import TimeGranularity | ||
|
||
|
||
class SetDefaultGrainRule(ProtocolHint[SemanticManifestTransformRule[PydanticSemanticManifest]]): | ||
"""If default_granularity is not set for a metric, set it to DAY if available, else the smallest available grain.""" | ||
|
||
@override | ||
def _implements_protocol(self) -> SemanticManifestTransformRule[PydanticSemanticManifest]: # noqa: D | ||
return self | ||
|
||
@staticmethod | ||
def transform_model(semantic_manifest: PydanticSemanticManifest) -> PydanticSemanticManifest: | ||
"""For each metric, set default_granularity to DAY or smallest granularity supported by all agg_time_dims.""" | ||
for metric in semantic_manifest.metrics: | ||
if metric.default_granularity: | ||
continue | ||
|
||
default_granularity = TimeGranularity.DAY | ||
seen_agg_time_dimensions: Set[TimeDimensionReference] = set() | ||
for semantic_model in semantic_manifest.semantic_models: | ||
for measure_ref in set(metric.measure_references).intersection(semantic_model.measure_references): | ||
agg_time_dimension_ref = semantic_model.checked_agg_time_dimension_for_measure(measure_ref) | ||
if agg_time_dimension_ref in seen_agg_time_dimensions: | ||
continue | ||
seen_agg_time_dimensions.add(agg_time_dimension_ref) | ||
dimension = semantic_model.get_dimension(DimensionReference(agg_time_dimension_ref.element_name)) | ||
if ( | ||
dimension.type_params | ||
and dimension.type_params.time_granularity.to_int() > default_granularity.to_int() | ||
): | ||
default_granularity = dimension.type_params.time_granularity | ||
|
||
return semantic_manifest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this method be in the protocol?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think not. I don't think we want to require implementing this logic any place that implements a metric.