Skip to content

Commit bca5c4d

Browse files
authored
Support configuring model.config.freshness.build_after.update_on without period or count (#12027)
1 parent b3d059e commit bca5c4d

File tree

3 files changed

+112
-3
lines changed

3 files changed

+112
-3
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: Features
2+
body: Support configuring model.config.freshness.build_after.updates_on without period or count
3+
time: 2025-09-17T16:49:41.528385-04:00
4+
custom:
5+
Author: michelleark
6+
Issue: "12019"

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
from dbt_common.contracts.config.base import MergeBehavior
1414
from dbt_common.contracts.constraints import ModelLevelConstraint
1515
from dbt_common.contracts.util import Mergeable
16-
from dbt_common.dataclass_schema import ExtensibleDbtClassMixin, dbtClassMixin
16+
from dbt_common.dataclass_schema import (
17+
ExtensibleDbtClassMixin,
18+
ValidationError,
19+
dbtClassMixin,
20+
)
1721

1822

1923
class ModelFreshnessUpdatesOnOptions(enum.Enum):
@@ -23,8 +27,8 @@ class ModelFreshnessUpdatesOnOptions(enum.Enum):
2327

2428
@dataclass
2529
class ModelBuildAfter(ExtensibleDbtClassMixin):
26-
count: int
27-
period: TimePeriod
30+
count: Optional[int] = None
31+
period: Optional[TimePeriod] = None
2832
updates_on: ModelFreshnessUpdatesOnOptions = ModelFreshnessUpdatesOnOptions.any
2933

3034

@@ -75,6 +79,25 @@ class ModelConfig(NodeConfig):
7579
)
7680
freshness: Optional[ModelFreshness] = None
7781

82+
def __post_init__(self):
83+
super().__post_init__()
84+
if (
85+
self.freshness
86+
and self.freshness.build_after.period
87+
and not self.freshness.build_after.count
88+
):
89+
raise ValidationError(
90+
"`freshness.build_after` must have a value for `count` if a `period` is provided"
91+
)
92+
elif (
93+
self.freshness
94+
and self.freshness.build_after.count
95+
and not self.freshness.build_after.period
96+
):
97+
raise ValidationError(
98+
"`freshness.build_after` must have a value for `period` if a `count` is provided"
99+
)
100+
78101
@classmethod
79102
def __pre_deserialize__(cls, data):
80103
data = super().__pre_deserialize__(data)

tests/functional/model_config/test_freshness_config.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
from dbt.tests.util import run_dbt
4+
from dbt_common.dataclass_schema import ValidationError
45

56
# Seed data for source tables
67
seeds__source_table_csv = """id,_loaded_at
@@ -77,6 +78,39 @@
7778
"""
7879

7980

81+
models__model_freshness_schema_yml_build_after_only = """
82+
models:
83+
- name: model_a
84+
description: Model with only model freshness defined
85+
config:
86+
freshness:
87+
build_after:
88+
updates_on: all
89+
"""
90+
91+
92+
models__model_freshness_schema_yml_build_period_requires_count = """
93+
models:
94+
- name: model_a
95+
description: Model with only model freshness defined
96+
config:
97+
freshness:
98+
build_after:
99+
period: day
100+
"""
101+
102+
103+
models__model_freshness_schema_yml_build_count_requires_period = """
104+
models:
105+
- name: model_a
106+
description: Model with only model freshness defined
107+
config:
108+
freshness:
109+
build_after:
110+
count: 1
111+
"""
112+
113+
80114
class TestModelFreshnessConfig:
81115

82116
@pytest.fixture(scope="class")
@@ -94,3 +128,49 @@ def test_model_freshness_configs(self, project):
94128
run_dbt(["parse"])
95129
compile_results = run_dbt(["compile"])
96130
assert len(compile_results) == 5 # All 4 models compiled successfully
131+
132+
133+
class TestModelFreshnessConfigParseBuildAfterOnly:
134+
@pytest.fixture(scope="class")
135+
def models(self):
136+
return {
137+
"schema.yml": models__model_freshness_schema_yml_build_after_only,
138+
"model_a.sql": models__no_freshness_sql,
139+
}
140+
141+
def test_model_freshness_configs(self, project):
142+
run_dbt(["parse"])
143+
144+
145+
class TestModelFreshnessConfigParseBuildPeriodRequiresCount:
146+
@pytest.fixture(scope="class")
147+
def models(self):
148+
return {
149+
"schema.yml": models__model_freshness_schema_yml_build_period_requires_count,
150+
"model_a.sql": models__no_freshness_sql,
151+
}
152+
153+
def test_model_freshness_configs(self, project):
154+
with pytest.raises(ValidationError) as excinfo:
155+
run_dbt(["parse"])
156+
expected_msg = (
157+
"`freshness.build_after` must have a value for `count` if a `period` is provided"
158+
)
159+
assert expected_msg in str(excinfo.value)
160+
161+
162+
class TestModelFreshnessConfigParseBuildCountRequiresPeriod:
163+
@pytest.fixture(scope="class")
164+
def models(self):
165+
return {
166+
"schema.yml": models__model_freshness_schema_yml_build_count_requires_period,
167+
"model_a.sql": models__no_freshness_sql,
168+
}
169+
170+
def test_model_freshness_configs(self, project):
171+
with pytest.raises(ValidationError) as excinfo:
172+
run_dbt(["parse"])
173+
expected_msg = (
174+
"`freshness.build_after` must have a value for `period` if a `count` is provided"
175+
)
176+
assert expected_msg in str(excinfo.value)

0 commit comments

Comments
 (0)