From b7ce0aa3d83be469d066c4007f66c802e9fb48d9 Mon Sep 17 00:00:00 2001 From: William Deng <33618746+WilliamDee@users.noreply.github.com> Date: Tue, 12 Nov 2024 02:19:33 -0500 Subject: [PATCH 1/2] Update support for custom granularity in other types (#365) ### Description Support custom grain by switching the typing to be not on `TimeGranularity` - `Metric.time_granularity` - `TimeGranularity -> str` - `Metric.type_params.cumulative_type_params.grain_to_date` - `TimeGranularity -> str` - `MetricInput.offset_to_grain` - `TimeGranularity -> str` - `MetricTimeWindow.granularity` - `TimeGranularity -> str` affects the following - `Metric.type_params.conversion_type_params.window` - `Metric.type_params.cumulative_type_params.window` - `MetricInput.offset_window` #### Changes to `MetricTimeWindow` Because `MetricTimeWindow.granularity` is now a string, we allow it to be not as strict when ensuring it's a valid granularity during initial yaml parsing (since we don't have access to the custom granularities). Then during validations, we will properly validate that it's a valid grain, and during the transformation step we will remove trailing 's'. ### Checklist - [x] I have read [the contributing guide](https://github.com/dbt-labs/dbt-semantic-interfaces/blob/main/CONTRIBUTING.md) and understand what's expected of me - [x] I have signed the [CLA](https://docs.getdbt.com/docs/contributor-license-agreements) - [x] This PR includes tests, or tests are not required/relevant for this PR - [x] I have run `changie new` to [create a changelog entry](https://github.com/dbt-labs/dbt-semantic-interfaces/blob/main/CONTRIBUTING.md#adding-a-changelog-entry) Resolves SL-2826 --- .../Breaking Changes-20241108-215906.yaml | 6 + .../implementations/metric.py | 45 ++- .../default_explicit_schema.json | 50 +--- dbt_semantic_interfaces/parsing/schemas.py | 4 +- dbt_semantic_interfaces/protocols/metric.py | 18 +- dbt_semantic_interfaces/test_utils.py | 4 +- .../transformations/cumulative_type_params.py | 2 +- .../transformations/pydantic_rule_set.py | 4 + .../remove_plural_from_window_granularity.py | 89 ++++++ .../validations/metrics.py | 37 ++- tests/parsing/test_metric_parsing.py | 32 +-- .../test_metric_parsing_with_custom_grain.py | 262 ++++++++++++++++++ tests/validations/test_metrics.py | 123 ++++++-- 13 files changed, 552 insertions(+), 124 deletions(-) create mode 100644 .changes/unreleased/Breaking Changes-20241108-215906.yaml create mode 100644 dbt_semantic_interfaces/transformations/remove_plural_from_window_granularity.py create mode 100644 tests/parsing/test_metric_parsing_with_custom_grain.py diff --git a/.changes/unreleased/Breaking Changes-20241108-215906.yaml b/.changes/unreleased/Breaking Changes-20241108-215906.yaml new file mode 100644 index 00000000..bd9906ff --- /dev/null +++ b/.changes/unreleased/Breaking Changes-20241108-215906.yaml @@ -0,0 +1,6 @@ +kind: Breaking Changes +body: Adding support for custom grain in windows/grain_to_dates +time: 2024-11-08T21:59:06.162076-05:00 +custom: + Author: WilliamDee + Issue: None diff --git a/dbt_semantic_interfaces/implementations/metric.py b/dbt_semantic_interfaces/implementations/metric.py index 02933e5a..204337a7 100644 --- a/dbt_semantic_interfaces/implementations/metric.py +++ b/dbt_semantic_interfaces/implementations/metric.py @@ -71,7 +71,7 @@ class PydanticMetricTimeWindow(PydanticCustomInputParser, HashableBaseModel): """Describes the window of time the metric should be accumulated over, e.g., '1 day', '2 weeks', etc.""" count: int - granularity: TimeGranularity + granularity: str @classmethod def _from_yaml_value(cls, input: PydanticParseableValueType) -> PydanticMetricTimeWindow: @@ -80,21 +80,31 @@ def _from_yaml_value(cls, input: PydanticParseableValueType) -> PydanticMetricTi The MetricTimeWindow is always expected to be provided as a string in user-defined YAML configs. """ if isinstance(input, str): - return PydanticMetricTimeWindow.parse(input) + return PydanticMetricTimeWindow.parse(window=input.lower(), custom_granularity_names=(), strict=False) else: raise ValueError( f"MetricTimeWindow inputs from model configs are expected to always be of type string, but got " f"type {type(input)} with value: {input}" ) + @property + def is_standard_granularity(self) -> bool: + """Returns whether the window uses standard TimeGranularity.""" + return self.granularity.casefold() in {item.value.casefold() for item in TimeGranularity} + + @property + def window_string(self) -> str: + """Returns the string value of the time window.""" + return f"{self.count} {self.granularity}" + @staticmethod - def parse(window: str) -> PydanticMetricTimeWindow: + def parse(window: str, custom_granularity_names: Sequence[str], strict: bool = True) -> PydanticMetricTimeWindow: """Returns window values if parsing succeeds, None otherwise. - Output of the form: (