Skip to content

Commit 717094e

Browse files
Add new-syle YAML (Parsed) Metric
1 parent f5cdb1d commit 717094e

File tree

7 files changed

+425
-72
lines changed

7 files changed

+425
-72
lines changed

core/dbt/artifacts/resources/v1/metric.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,11 @@ class ConstantPropertyInput(dbtClassMixin):
8383

8484
@dataclass
8585
class ConversionTypeParams(dbtClassMixin):
86-
base_measure: MetricInputMeasure
87-
conversion_measure: MetricInputMeasure
8886
entity: str
87+
base_measure: Optional[MetricInputMeasure] = None
88+
conversion_measure: Optional[MetricInputMeasure] = None
89+
base_metric: Optional[MetricInput] = None
90+
conversion_metric: Optional[MetricInput] = None
8991
calculation: ConversionCalculationType = ConversionCalculationType.CONVERSION_RATE
9092
window: Optional[MetricTimeWindow] = None
9193
constant_properties: Optional[List[ConstantPropertyInput]] = None
@@ -106,13 +108,15 @@ class MetricAggregationParams(dbtClassMixin):
106108
agg_params: Optional[MeasureAggregationParameters] = None
107109
agg_time_dimension: Optional[str] = None
108110
non_additive_dimension: Optional[NonAdditiveDimension] = None
109-
expr: Optional[str] = None
110111

111112

112113
@dataclass
113114
class MetricTypeParams(dbtClassMixin):
115+
# Only used in old-style YAML (pre-November 2025)
114116
measure: Optional[MetricInputMeasure] = None
117+
# Only used in old-style YAML (pre-November 2025)
115118
input_measures: List[MetricInputMeasure] = field(default_factory=list)
119+
116120
numerator: Optional[MetricInput] = None
117121
denominator: Optional[MetricInput] = None
118122
expr: Optional[str] = None
@@ -123,7 +127,12 @@ class MetricTypeParams(dbtClassMixin):
123127
metrics: Optional[List[MetricInput]] = None
124128
conversion_type_params: Optional[ConversionTypeParams] = None
125129
cumulative_type_params: Optional[CumulativeTypeParams] = None
130+
131+
# Only used in v2 YAML
126132
metric_aggregation_params: Optional[MetricAggregationParams] = None
133+
fill_nulls_with: Optional[int] = None
134+
join_to_timespine: Optional[bool] = None
135+
is_private: Optional[bool] = None # populated by "hidden" field in YAML
127136

128137

129138
@dataclass
@@ -148,8 +157,6 @@ class Metric(GraphResource):
148157
metadata: Optional[SourceFileMetadata] = None
149158
time_granularity: Optional[str] = None
150159
resource_type: Literal[NodeType.Metric]
151-
meta: Dict[str, Any] = field(default_factory=dict, metadata=MergeBehavior.Update.meta())
152-
tags: List[str] = field(default_factory=list)
153160
config: MetricConfig = field(default_factory=MetricConfig)
154161
unrendered_config: Dict[str, Any] = field(default_factory=dict)
155162
sources: List[List[str]] = field(default_factory=list)
@@ -159,14 +166,18 @@ class Metric(GraphResource):
159166
created_at: float = field(default_factory=lambda: time.time())
160167
group: Optional[str] = None
161168

169+
# These fields are only used in v1 metrics.
170+
meta: Dict[str, Any] = field(default_factory=dict, metadata=MergeBehavior.Update.meta())
171+
tags: List[str] = field(default_factory=list)
172+
173+
@property
174+
def input_metrics(self) -> List[MetricInput]:
175+
return self.type_params.metrics or []
176+
162177
@property
163178
def input_measures(self) -> List[MetricInputMeasure]:
164179
return self.type_params.input_measures
165180

166181
@property
167182
def measure_references(self) -> List[MeasureReference]:
168183
return [x.measure_reference() for x in self.input_measures]
169-
170-
@property
171-
def input_metrics(self) -> List[MetricInput]:
172-
return self.type_params.metrics or []

core/dbt/contracts/graph/unparsed.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import datetime
22
import re
33
from dataclasses import dataclass, field
4+
from enum import Enum
45
from pathlib import Path
56
from typing import Any, Dict, List, Literal, Optional, Sequence, Union
67

8+
from typing_extensions import override
9+
710
# trigger the PathEncoder
811
import dbt_common.helper_types # noqa:F401
912
from dbt import deprecations
@@ -580,15 +583,34 @@ class UnparsedMetricInput(dbtClassMixin):
580583

581584
@dataclass
582585
class UnparsedConversionTypeParams(dbtClassMixin):
583-
base_measure: Union[UnparsedMetricInputMeasure, str]
584-
conversion_measure: Union[UnparsedMetricInputMeasure, str]
585586
entity: str
587+
588+
# *_measure params are for old-style YAML.
589+
base_measure: Optional[Union[UnparsedMetricInputMeasure, str]] = None
590+
conversion_measure: Optional[Union[UnparsedMetricInputMeasure, str]] = None
591+
# *_metric params are for v2-style metrics.
592+
base_metric: Optional[Union[UnparsedMetricInput, str]] = None
593+
conversion_metric: Optional[Union[UnparsedMetricInput, str]] = None
594+
586595
calculation: str = (
587596
ConversionCalculationType.CONVERSION_RATE.value
588597
) # ConversionCalculationType Enum
589598
window: Optional[str] = None
590599
constant_properties: Optional[List[ConstantPropertyInput]] = None
591600

601+
@override
602+
@classmethod
603+
def validate(cls, data):
604+
super().validate(data)
605+
if data.get("base_measure") is None and data.get("base_metric") is None:
606+
raise ValidationError(
607+
"Conversion metrics must define a base_measure or base_metric parameter."
608+
)
609+
if data.get("conversion_measure") is None and data.get("conversion_metric") is None:
610+
raise ValidationError(
611+
"Conversion metrics must define a conversion_measure or conversion_metric parameter."
612+
)
613+
592614

593615
@dataclass
594616
class UnparsedCumulativeTypeParams(dbtClassMixin):
@@ -674,30 +696,37 @@ class UnparsedMetricV2(UnparsedMetricBase):
674696

675697
join_to_timespine: Optional[bool] = None
676698
fill_nulls_with: Optional[int] = None
677-
expr: Optional[Union[str, int]] = None
699+
expr: Optional[Union[str, bool]] = None
678700

679701
non_additive_dimension: Optional[UnparsedNonAdditiveDimensionV2] = None
680702
agg_time_dimension: Optional[str] = None
681703

682704
# For cumulative metrics
683705
window: Optional[str] = None
684706
grain_to_date: Optional[str] = None
685-
period_agg: Optional[str] = None
707+
period_agg: str = PeriodAggregation.FIRST.value
686708
input_metric: Optional[Union[str, Dict[str, Any]]] = None
687709

688710
# For ratio metrics
689-
numerator: Optional[Union[str, Dict[str, Any]]] = None
690-
denominator: Optional[Union[str, Dict[str, Any]]] = None
711+
numerator: Optional[Union[UnparsedMetricInput, str]] = None
712+
denominator: Optional[Union[UnparsedMetricInput, str]] = None
691713

692714
# For derived metrics
693-
input_metrics: Optional[List[Dict[str, Any]]] = None
715+
input_metrics: Optional[List[Union[UnparsedMetricInput, str]]] = None
694716

695717
# For conversion metrics
696718
entity: Optional[str] = None
697719
calculation: Optional[str] = None
698-
base_metric: Optional[Union[str, Dict[str, Any]]] = None
699-
conversion_metric: Optional[Union[str, Dict[str, Any]]] = None
700-
constant_properties: Optional[List[Dict[str, Any]]] = None
720+
base_metric: Optional[Union[UnparsedMetricInput, str]] = None
721+
conversion_metric: Optional[Union[UnparsedMetricInput, str]] = None
722+
constant_properties: Optional[List[ConstantPropertyInput]] = None
723+
724+
@classmethod
725+
@override
726+
def validate(cls, data):
727+
super().validate(data)
728+
if data["type"] == "simple" and data.get("agg") is None:
729+
raise ValidationError("Simple metrics must have an agg param.")
701730

702731

703732
@dataclass
@@ -748,6 +777,11 @@ class UnparsedNonAdditiveDimension(dbtClassMixin):
748777
window_groupings: List[str] = field(default_factory=list)
749778

750779

780+
class PercentileType(str, Enum):
781+
DISCRETE = "discrete"
782+
CONTINUOUS = "continuous"
783+
784+
751785
@dataclass
752786
class UnparsedMeasure(dbtClassMixin):
753787
name: str

core/dbt/parser/manifest.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,7 +1867,7 @@ def _process_metric_depends_on(
18671867
) -> None:
18681868
"""For a given metric, set the `depends_on` property"""
18691869

1870-
assert len(metric.type_params.input_measures) > 0
1870+
# assert len(metric.type_params.input_measures) > 0 or metric.type_params.metric_aggregation_params is not None, f"{metric} should have a measure or agg type defined, but it does not."
18711871
for input_measure in metric.type_params.input_measures:
18721872
target_semantic_model = manifest.resolve_semantic_model_for_measure(
18731873
target_measure_name=input_measure.name,
@@ -1903,10 +1903,16 @@ def _process_metric_node(
19031903
return
19041904

19051905
if metric.type is MetricType.SIMPLE or metric.type is MetricType.CUMULATIVE:
1906-
assert (
1907-
metric.type_params.measure is not None
1908-
), f"{metric} should have a measure defined, but it does not."
1909-
metric.add_input_measure(metric.type_params.measure)
1906+
if (
1907+
metric.type_params.measure is None
1908+
and metric.type_params.metric_aggregation_params is None
1909+
):
1910+
raise dbt.exceptions.ParsingError(
1911+
f"{metric} should have a measure or agg type defined, but it does not.",
1912+
node=metric,
1913+
)
1914+
if metric.type_params.measure is not None:
1915+
metric.add_input_measure(metric.type_params.measure)
19101916
_process_metric_depends_on(
19111917
manifest=manifest, current_project=current_project, metric=metric
19121918
)
@@ -1915,8 +1921,11 @@ def _process_metric_node(
19151921
assert (
19161922
conversion_type_params
19171923
), f"{metric.name} is a conversion metric and must have conversion_type_params defined."
1918-
metric.add_input_measure(conversion_type_params.base_measure)
1919-
metric.add_input_measure(conversion_type_params.conversion_measure)
1924+
if conversion_type_params.base_measure is not None:
1925+
metric.add_input_measure(conversion_type_params.base_measure)
1926+
if conversion_type_params.conversion_measure is not None:
1927+
metric.add_input_measure(conversion_type_params.conversion_measure)
1928+
# TODO : process input metrics' input measures recursively, like in derived metrics.
19201929
_process_metric_depends_on(
19211930
manifest=manifest, current_project=current_project, metric=metric
19221931
)

0 commit comments

Comments
 (0)