Skip to content

Commit b3d059e

Browse files
Fix legacy time spine deprecation warning logic (#12018)
1 parent b783c97 commit b3d059e

File tree

5 files changed

+101
-24
lines changed

5 files changed

+101
-24
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: Fixes
2+
body: Fixes a bug in the logic for legacy time spine deprecation warnings.
3+
time: 2025-09-15T15:22:27.791819-07:00
4+
custom:
5+
Author: courtneyholcomb
6+
Issue: "11690"

core/dbt/contracts/graph/semantic_manifest.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ def write_json_to_file(self, file_path: str):
127127
def _get_pydantic_semantic_manifest(self) -> PydanticSemanticManifest:
128128
pydantic_time_spines: List[PydanticTimeSpine] = []
129129
minimum_time_spine_granularity: Optional[TimeGranularity] = None
130-
has_legacy_time_spine_with_config: bool = False
131130
for node in self.manifest.nodes.values():
132131
if not (isinstance(node, ModelNode) and node.time_spine):
133132
continue
@@ -167,8 +166,6 @@ def _get_pydantic_semantic_manifest(self) -> PydanticSemanticManifest:
167166
],
168167
)
169168
pydantic_time_spines.append(pydantic_time_spine)
170-
if pydantic_time_spine.node_relation.relation_name == LEGACY_TIME_SPINE_MODEL_NAME:
171-
has_legacy_time_spine_with_config = True
172169
if (
173170
not minimum_time_spine_granularity
174171
or standard_granularity_column.granularity.to_int()
@@ -196,18 +193,18 @@ def _get_pydantic_semantic_manifest(self) -> PydanticSemanticManifest:
196193
PydanticSavedQuery.parse_obj(saved_query.to_dict())
197194
)
198195

196+
legacy_time_spine_model: Optional[ModelNode] = None
199197
if self.manifest.semantic_models:
200-
if not has_legacy_time_spine_with_config:
201-
legacy_time_spine_model = self.manifest.ref_lookup.find(
202-
LEGACY_TIME_SPINE_MODEL_NAME, None, None, self.manifest
203-
)
204-
if legacy_time_spine_model:
205-
if (
206-
not minimum_time_spine_granularity
207-
or LEGACY_TIME_SPINE_GRANULARITY.to_int()
208-
< minimum_time_spine_granularity.to_int()
209-
):
210-
minimum_time_spine_granularity = LEGACY_TIME_SPINE_GRANULARITY
198+
legacy_time_spine_model = self.manifest.ref_lookup.find(
199+
LEGACY_TIME_SPINE_MODEL_NAME, None, None, self.manifest
200+
)
201+
if legacy_time_spine_model:
202+
if (
203+
not minimum_time_spine_granularity
204+
or LEGACY_TIME_SPINE_GRANULARITY.to_int()
205+
< minimum_time_spine_granularity.to_int()
206+
):
207+
minimum_time_spine_granularity = LEGACY_TIME_SPINE_GRANULARITY
211208

212209
# If no time spines have been configured at DAY or smaller AND legacy time spine model does not exist, error.
213210
if (
@@ -221,8 +218,8 @@ def _get_pydantic_semantic_manifest(self) -> PydanticSemanticManifest:
221218
"(https://docs.getdbt.com/docs/build/metricflow-time-spine)."
222219
)
223220

224-
# For backward compatibility: if legacy time spine exists, include it in the manifest.
225-
if legacy_time_spine_model:
221+
# For backward compatibility: if legacy time spine exists without config, include it in the manifest.
222+
if legacy_time_spine_model and legacy_time_spine_model.time_spine is None:
226223
legacy_time_spine = LegacyTimeSpine(
227224
location=legacy_time_spine_model.relation_name,
228225
column_name="date_day",

tests/functional/time_spines/test_time_spines.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,6 @@ def test_time_spines(self, project):
8787
semantic_manifest = SemanticManifest(manifest)
8888
assert semantic_manifest.validate()
8989
project_config = semantic_manifest._get_pydantic_semantic_manifest().project_configuration
90-
# Legacy config
91-
assert len(project_config.time_spine_table_configurations) == 1
92-
legacy_time_spine_config = project_config.time_spine_table_configurations[0]
93-
assert legacy_time_spine_config.column_name == day_column_name
94-
assert legacy_time_spine_config.location.replace('"', "").split(".")[-1] == day_model_name
95-
assert legacy_time_spine_config.grain == TimeGranularity.DAY
9690
# Current configs
9791
assert len(project_config.time_spines) == 2
9892
sl_time_spine_aliases: Set[str] = set()

tests/unit/contracts/graph/test_semantic_manifest.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
import pytest
44

55
from core.dbt.contracts.graph.manifest import Manifest
6-
from core.dbt.contracts.graph.nodes import Metric, ModelNode
76
from dbt.artifacts.resources.types import NodeType
87
from dbt.artifacts.resources.v1.metric import (
98
CumulativeTypeParams,
109
MetricTimeWindow,
1110
MetricTypeParams,
1211
)
12+
from dbt.artifacts.resources.v1.model import ModelConfig, TimeSpine
13+
from dbt.constants import LEGACY_TIME_SPINE_MODEL_NAME
14+
from dbt.contracts.files import FileHash
15+
from dbt.contracts.graph.nodes import ColumnInfo, DependsOn, Metric, ModelNode
1316
from dbt.contracts.graph.semantic_manifest import SemanticManifest
1417
from dbt_semantic_interfaces.type_enums import TimeGranularity
1518
from dbt_semantic_interfaces.type_enums.metric_type import MetricType
@@ -55,6 +58,57 @@ def test_require_yaml_configuration_for_mf_time_spines(
5558
assert sm_manifest.validate()
5659
assert patched_deprecations.warn.call_count == 1
5760

61+
def test_metricflow_time_spine_non_day_grain_deprecation_warning(
62+
self, manifest: Manifest, metricflow_time_spine_model: ModelNode
63+
):
64+
"""Test that a metricflow_time_spine with non-day grain does not trigger deprecation warning."""
65+
# Create a metricflow_time_spine model with HOUR granularity
66+
metricflow_time_spine_hour = ModelNode(
67+
name=LEGACY_TIME_SPINE_MODEL_NAME,
68+
database="dbt",
69+
schema="analytics",
70+
alias=LEGACY_TIME_SPINE_MODEL_NAME,
71+
resource_type=NodeType.Model,
72+
unique_id="model.test.metricflow_time_spine",
73+
fqn=["test", "metricflow_time_spine"],
74+
package_name="test",
75+
refs=[],
76+
sources=[],
77+
metrics=[],
78+
depends_on=DependsOn(),
79+
config=ModelConfig(),
80+
tags=[],
81+
path="metricflow_time_spine.sql",
82+
original_file_path="metricflow_time_spine.sql",
83+
meta={},
84+
language="sql",
85+
raw_code="SELECT DATEADD(hour, ROW_NUMBER() OVER (ORDER BY 1), '2020-01-01'::timestamp) as ts_hour",
86+
checksum=FileHash.empty(),
87+
relation_name="",
88+
columns={
89+
"ts_hour": ColumnInfo(
90+
name="ts_hour",
91+
description="",
92+
meta={},
93+
data_type="timestamp",
94+
constraints=[],
95+
quote=None,
96+
tags=[],
97+
granularity=TimeGranularity.HOUR,
98+
)
99+
},
100+
time_spine=TimeSpine(standard_granularity_column="ts_hour"),
101+
)
102+
103+
with patch("dbt.contracts.graph.semantic_manifest.get_flags") as patched_get_flags, patch(
104+
"dbt.contracts.graph.semantic_manifest.deprecations"
105+
) as patched_deprecations:
106+
patched_get_flags.return_value.require_yaml_configuration_for_mf_time_spines = False
107+
manifest.nodes[metricflow_time_spine_hour.unique_id] = metricflow_time_spine_hour
108+
sm_manifest = SemanticManifest(manifest)
109+
assert sm_manifest.validate()
110+
assert patched_deprecations.warn.call_count == 0
111+
58112
@pytest.mark.parametrize(
59113
"metric_type_params, num_warns, should_error, flag_value",
60114
[

tests/unit/utils/manifest.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
)
2020
from dbt.artifacts.resources.types import ModelLanguage
2121
from dbt.artifacts.resources.v1.model import ModelConfig
22+
from dbt.artifacts.resources.v1.semantic_model import (
23+
Defaults,
24+
Dimension,
25+
DimensionTypeParams,
26+
Measure,
27+
)
2228
from dbt.contracts.files import AnySourceFile, FileHash
2329
from dbt.contracts.graph.manifest import Manifest, ManifestMetadata
2430
from dbt.contracts.graph.nodes import (
@@ -44,7 +50,12 @@
4450
)
4551
from dbt.contracts.graph.unparsed import UnitTestInputFixture, UnitTestOutputFixture
4652
from dbt.node_types import NodeType
47-
from dbt_semantic_interfaces.type_enums import MetricType
53+
from dbt_semantic_interfaces.type_enums import (
54+
AggregationType,
55+
DimensionType,
56+
MetricType,
57+
TimeGranularity,
58+
)
4859

4960

5061
def make_model(
@@ -485,6 +496,21 @@ def make_semantic_model(
485496
unique_id=f"semantic_model.{pkg}.{name}",
486497
original_file_path=path,
487498
fqn=[pkg, "semantic_models", name],
499+
defaults=Defaults(agg_time_dimension="created_at"),
500+
dimensions=[
501+
Dimension(
502+
name="created_at",
503+
type=DimensionType.TIME,
504+
type_params=DimensionTypeParams(time_granularity=TimeGranularity.DAY),
505+
)
506+
],
507+
measures=[
508+
Measure(
509+
name="a_measure",
510+
agg=AggregationType.COUNT,
511+
expr="1",
512+
)
513+
],
488514
)
489515

490516

0 commit comments

Comments
 (0)