4646 UnparsedGroup ,
4747 UnparsedMeasure ,
4848 UnparsedMetric ,
49+ UnparsedMetricBase ,
4950 UnparsedMetricInput ,
5051 UnparsedMetricInputMeasure ,
5152 UnparsedMetricTypeParams ,
53+ UnparsedMetricV2 ,
5254 UnparsedNonAdditiveDimension ,
5355 UnparsedQueryParams ,
5456 UnparsedSavedQuery ,
7274
7375
7476def parse_where_filter (
75- where : Optional [Union [List [str ], str ]]
77+ where : Optional [Union [List [str ], str ]],
7678) -> Optional [WhereFilterIntersection ]:
7779 if where is None :
7880 return None
@@ -306,11 +308,19 @@ def _get_metric_inputs(
306308
307309 return metric_inputs
308310
309- def _get_optional_conversion_type_params (
311+ def _get_optional_v1_conversion_type_params (
310312 self , unparsed : Optional [UnparsedConversionTypeParams ]
311313 ) -> Optional [ConversionTypeParams ]:
312314 if unparsed is None :
313315 return None
316+ if unparsed .base_measure is None :
317+ raise ValidationError (
318+ "base_measure is required for conversion metrics that use type_params."
319+ )
320+ if unparsed .conversion_measure is None :
321+ raise ValidationError (
322+ "conversion_measure is required for conversion metrics that use type_params."
323+ )
314324 return ConversionTypeParams (
315325 base_measure = self ._get_input_measure (unparsed .base_measure ),
316326 conversion_measure = self ._get_input_measure (unparsed .conversion_measure ),
@@ -320,7 +330,30 @@ def _get_optional_conversion_type_params(
320330 constant_properties = unparsed .constant_properties ,
321331 )
322332
323- def _get_optional_cumulative_type_params (
333+ def _get_optional_v2_conversion_type_params (
334+ self ,
335+ unparsed_metric : UnparsedMetricV2 ,
336+ ) -> Optional [ConversionTypeParams ]:
337+ if MetricType (unparsed_metric .type ) is not MetricType .CONVERSION :
338+ return None
339+ # TODO: shoudl we also make a best effort to validate this in the UNPARSED class?
340+ # It'd violate DRY, but it is probably the more appropriate place to do the validation.
341+ if unparsed_metric .base_metric is None :
342+ raise ValidationError ("base_metric is required for cumulative metrics." )
343+ if unparsed_metric .conversion_metric is None :
344+ raise ValidationError ("conversion_metric is required for cumulative metrics." )
345+ if unparsed_metric .entity is None :
346+ raise ValidationError ("entity is required for conversion metrics." )
347+ return ConversionTypeParams (
348+ base_metric = self ._get_metric_input (unparsed_metric .base_metric ),
349+ conversion_metric = self ._get_metric_input (unparsed_metric .conversion_metric ),
350+ entity = unparsed_metric .entity ,
351+ calculation = ConversionCalculationType (unparsed_metric .calculation ),
352+ window = self ._get_optional_time_window (unparsed_metric .window ),
353+ constant_properties = unparsed_metric .constant_properties ,
354+ )
355+
356+ def _get_optional_v1_cumulative_type_params (
324357 self , unparsed_metric : UnparsedMetric
325358 ) -> Optional [CumulativeTypeParams ]:
326359 unparsed_type_params = unparsed_metric .type_params
@@ -353,36 +386,71 @@ def _get_optional_cumulative_type_params(
353386
354387 return None
355388
356- def _get_metric_type_params (self , unparsed_metric : UnparsedMetric ) -> MetricTypeParams :
357- type_params = unparsed_metric .type_params
358-
359- grain_to_date : Optional [TimeGranularity ] = None
360- if type_params .grain_to_date is not None :
361- # This should've been changed to a string (to support custom grain), but since this
362- # is a legacy field waiting to be deprecated, we will not support custom grain here
363- # in order to force customers off of using this field. The field to use should be
364- # `cumulative_type_params.grain_to_date`
365- grain_to_date = TimeGranularity (type_params .grain_to_date )
366-
367- return MetricTypeParams (
368- measure = self ._get_optional_input_measure (type_params .measure ),
369- numerator = self ._get_optional_metric_input (type_params .numerator ),
370- denominator = self ._get_optional_metric_input (type_params .denominator ),
371- expr = str (type_params .expr ) if type_params .expr is not None else None ,
372- window = self ._get_optional_time_window (type_params .window ),
373- grain_to_date = grain_to_date ,
374- metrics = self ._get_metric_inputs (type_params .metrics ),
375- conversion_type_params = self ._get_optional_conversion_type_params (
376- type_params .conversion_type_params
377- ),
378- cumulative_type_params = self ._get_optional_cumulative_type_params (
379- unparsed_metric = unparsed_metric ,
380- ),
381- # input measures are calculated via metric processing post parsing
382- # input_measures=?,
389+ def _get_optional_v2_cumulative_type_params (
390+ self ,
391+ unparsed_metric : UnparsedMetricV2 ,
392+ ) -> Optional [CumulativeTypeParams ]:
393+ if MetricType (unparsed_metric .type ) is not MetricType .CUMULATIVE :
394+ return None
395+ return CumulativeTypeParams (
396+ window = self ._get_optional_time_window (unparsed_metric .window ),
397+ grain_to_date = unparsed_metric .grain_to_date ,
398+ period_agg = self ._get_period_agg (unparsed_metric .period_agg ),
383399 )
384400
385- def parse_metric (self , unparsed : UnparsedMetric , generated_from : Optional [str ] = None ) -> None :
401+ def _get_metric_type_params (self , unparsed_metric : UnparsedMetricBase ) -> MetricTypeParams :
402+ if isinstance (unparsed_metric , UnparsedMetric ):
403+ type_params = unparsed_metric .type_params
404+
405+ grain_to_date : Optional [TimeGranularity ] = None
406+ if type_params .grain_to_date is not None :
407+ # This should've been changed to a string (to support custom grain), but since this
408+ # is a legacy field waiting to be deprecated, we will not support custom grain here
409+ # in order to force customers off of using this field. The field to use should be
410+ # `cumulative_type_params.grain_to_date`
411+ grain_to_date = TimeGranularity (type_params .grain_to_date )
412+
413+ return MetricTypeParams (
414+ measure = self ._get_optional_input_measure (type_params .measure ),
415+ numerator = self ._get_optional_metric_input (type_params .numerator ),
416+ denominator = self ._get_optional_metric_input (type_params .denominator ),
417+ expr = str (type_params .expr ) if type_params .expr is not None else None ,
418+ window = self ._get_optional_time_window (type_params .window ),
419+ grain_to_date = grain_to_date ,
420+ metrics = self ._get_metric_inputs (type_params .metrics ),
421+ conversion_type_params = self ._get_optional_v1_conversion_type_params (
422+ type_params .conversion_type_params
423+ ),
424+ cumulative_type_params = self ._get_optional_v1_cumulative_type_params (
425+ unparsed_metric = unparsed_metric ,
426+ ),
427+ # input measures are calculated via metric processing post parsing
428+ # input_measures=?,
429+ )
430+ elif isinstance (unparsed_metric , UnparsedMetricV2 ):
431+ # if unparsed_metric.type.lower() == MetricType.CONVERSION.value:
432+ return MetricTypeParams (
433+ numerator = self ._get_optional_metric_input (unparsed_metric .numerator ),
434+ denominator = self ._get_optional_metric_input (unparsed_metric .denominator ),
435+ expr = str (unparsed_metric .expr ) if unparsed_metric .expr is not None else None ,
436+ window = self ._get_optional_time_window (unparsed_metric .window ),
437+ metrics = self ._get_metric_inputs (unparsed_metric .input_metrics ),
438+ conversion_type_params = self ._get_optional_v2_conversion_type_params (
439+ unparsed_metric = unparsed_metric ,
440+ ),
441+ cumulative_type_params = self ._get_optional_v2_cumulative_type_params (
442+ unparsed_metric = unparsed_metric ,
443+ ),
444+ )
445+ else :
446+ raise DbtInternalError (
447+ f"Tried to parse type params for a { type (unparsed_metric )} , but expected "
448+ "an UnparsedMetric or UnparsedMetricV2" ,
449+ )
450+
451+ def parse_metric (
452+ self , unparsed : UnparsedMetricBase , generated_from : Optional [str ] = None
453+ ) -> None :
386454 package_name = self .project .project_name
387455 unique_id = f"{ NodeType .Metric } .{ package_name } .{ unparsed .name } "
388456 path = self .yaml .path .relative_path
@@ -411,10 +479,23 @@ def parse_metric(self, unparsed: UnparsedMetric, generated_from: Optional[str] =
411479 f"Calculated a { type (config )} for a metric, but expected a MetricConfig"
412480 )
413481
414- # If we have meta in the config, copy to node level, for backwards
415- # compatibility with earlier node-only config.
416- if "meta" in config and config ["meta" ]:
417- unparsed .meta = config ["meta" ]
482+ if isinstance (unparsed , UnparsedMetric ):
483+ # If we have meta in the config, copy to node level, for backwards
484+ # compatibility with earlier node-only config.
485+ if "meta" in config and config ["meta" ]:
486+ unparsed .meta = config ["meta" ]
487+ meta = unparsed .meta
488+ tags = unparsed .tags
489+ elif isinstance (unparsed , UnparsedMetricV2 ):
490+ # V2 Metrics do not have a top-level meta field; this should be part of
491+ # the config.
492+ meta = {}
493+ tags = []
494+ else :
495+ raise DbtInternalError (
496+ f"Tried to parse a { type (unparsed )} into a metric, but expected "
497+ "an UnparsedMetric or UnparsedMetricV2" ,
498+ )
418499
419500 parsed = Metric (
420501 resource_type = NodeType .Metric ,
@@ -425,13 +506,13 @@ def parse_metric(self, unparsed: UnparsedMetric, generated_from: Optional[str] =
425506 fqn = fqn ,
426507 name = unparsed .name ,
427508 description = unparsed .description ,
428- label = unparsed .label ,
509+ label = unparsed .label or unparsed . name ,
429510 type = MetricType (unparsed .type ),
430511 type_params = self ._get_metric_type_params (unparsed ),
431512 time_granularity = unparsed .time_granularity ,
432513 filter = parse_where_filter (unparsed .filter ),
433- meta = unparsed . meta ,
434- tags = unparsed . tags ,
514+ meta = meta ,
515+ tags = tags ,
435516 config = config ,
436517 unrendered_config = unrendered_config ,
437518 group = config .group ,
@@ -445,7 +526,7 @@ def parse_metric(self, unparsed: UnparsedMetric, generated_from: Optional[str] =
445526 self .manifest .add_disabled (self .yaml .file , parsed )
446527
447528 def _generate_metric_config (
448- self , target : UnparsedMetric , fqn : List [str ], package_name : str , rendered : bool
529+ self , target : UnparsedMetricBase , fqn : List [str ], package_name : str , rendered : bool
449530 ):
450531 generator : BaseContextConfigGenerator
451532 if rendered :
@@ -470,12 +551,20 @@ def _generate_metric_config(
470551
471552 def parse (self ) -> None :
472553 for data in self .get_key_dicts ():
473- try :
474- UnparsedMetric .validate (data )
475- unparsed = UnparsedMetric .from_dict (data )
476-
477- except (ValidationError , JSONValidationError ) as exc :
478- raise YamlParseDictError (self .yaml .path , self .key , data , exc )
554+ # The main differentiator of old-style yaml and new-style is "type_params",
555+ # so if that is missing, we'll assume you're using the newer yaml.
556+ if "type_params" in data :
557+ try :
558+ UnparsedMetric .validate (data )
559+ unparsed = UnparsedMetric .from_dict (data )
560+ except (ValidationError , JSONValidationError ) as exc :
561+ raise YamlParseDictError (self .yaml .path , self .key , data , exc )
562+ else :
563+ try :
564+ UnparsedMetricV2 .validate (data )
565+ unparsed = UnparsedMetricV2 .from_dict (data )
566+ except (ValidationError , JSONValidationError ) as exc :
567+ raise YamlParseDictError (self .yaml .path , self .key , data , exc )
479568 self .parse_metric (unparsed )
480569
481570
0 commit comments