Skip to content

Commit cdb9b1c

Browse files
Ensure source node .freshness is equal to node's .config.freshness (#11719) (#11723)
* Ensure source node `.freshness` is equal to node's `.config.freshness` * Default source config freshness to empty spec if no freshenss spec is given * Update contract tests for source nodes (cherry picked from commit 2e6d4f4) Co-authored-by: Quigley Malcolm <[email protected]>
1 parent 4c91396 commit cdb9b1c

File tree

6 files changed

+61
-5
lines changed

6 files changed

+61
-5
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: Ensure source node `.freshness` is equal to node's `.config.freshness`
3+
time: 2025-06-09T17:52:39.978403-05:00
4+
custom:
5+
Author: QMalcolm
6+
Issue: "11717"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
class SourceConfig(BaseConfig):
2121
enabled: bool = True
2222
event_time: Any = None
23-
freshness: Optional[FreshnessThreshold] = None
23+
freshness: Optional[FreshnessThreshold] = field(default_factory=FreshnessThreshold)
2424

2525

2626
@dataclass

core/dbt/parser/sources.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,7 @@ def parse_source(self, target: UnpatchedSourceDefinition) -> SourceDefinition:
206206
loader=source.loader,
207207
loaded_at_field=loaded_at_field,
208208
loaded_at_query=loaded_at_query,
209-
# The setting to an empty freshness object is to maintain what we were previously doing if no freshenss was specified
210-
freshness=config.freshness or FreshnessThreshold(),
209+
freshness=config.freshness,
211210
quoting=quoting,
212211
resource_type=NodeType.Source,
213212
fqn=target.fqn,

tests/functional/artifacts/expected_manifest.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,11 @@ def expected_seeded_manifest(project, model_database=None, quote_model=False):
818818
"config": {
819819
"enabled": True,
820820
"event_time": None,
821-
"freshness": None,
821+
"freshness": {
822+
"error_after": {"count": None, "period": None},
823+
"warn_after": {"count": None, "period": None},
824+
"filter": None,
825+
},
822826
},
823827
"quoting": {
824828
"database": None,
@@ -1358,7 +1362,11 @@ def expected_references_manifest(project):
13581362
"config": {
13591363
"enabled": True,
13601364
"event_time": None,
1361-
"freshness": None,
1365+
"freshness": {
1366+
"error_after": {"count": None, "period": None},
1367+
"warn_after": {"count": None, "period": None},
1368+
"filter": None,
1369+
},
13621370
},
13631371
"quoting": {
13641372
"database": False,

tests/unit/contracts/graph/test_nodes_parsed.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1900,6 +1900,10 @@ def basic_parsed_source_definition_dict():
19001900
"tags": [],
19011901
"config": {
19021902
"enabled": True,
1903+
"freshness": {
1904+
"warn_after": {},
1905+
"error_after": {},
1906+
},
19031907
},
19041908
"unrendered_config": {},
19051909
"doc_blocks": [],
@@ -1931,6 +1935,10 @@ def complex_parsed_source_definition_dict():
19311935
"tags": ["my_tag"],
19321936
"config": {
19331937
"enabled": True,
1938+
"freshness": {
1939+
"warn_after": {},
1940+
"error_after": {},
1941+
},
19341942
},
19351943
"freshness": {"warn_after": {"period": "hour", "count": 1}, "error_after": {}},
19361944
"loaded_at_field": "loaded_at",

tests/unit/parser/test_parser.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,22 @@ def assertEqualNodes(node_one, node_two):
453453
- name: my_table
454454
loaded_at_query: "select 1 as id"
455455
"""
456+
457+
SOURCE_FRESHNESS_AT_TABLE_AND_CONFIG = """
458+
sources:
459+
- name: my_source
460+
loaded_at_field: test
461+
tables:
462+
- name: my_table
463+
freshness:
464+
warn_after: {count: 1, period: hour}
465+
error_after: {count: 1, period: day}
466+
config:
467+
freshness:
468+
warn_after: {count: 2, period: hour}
469+
error_after: {count: 2, period: day}
470+
"""
471+
456472
SOURCE_FIELD_AT_CUSTOM_FRESHNESS_BOTH_AT_TABLE = """
457473
sources:
458474
- name: my_source
@@ -476,6 +492,12 @@ def assertEqualNodes(node_one, node_two):
476492
class SchemaParserTest(BaseParserTest):
477493
def setUp(self):
478494
super().setUp()
495+
# Reset `warn_error` to False so we don't raise warnigns about top level freshness as errors
496+
set_from_args(
497+
Namespace(warn_error=False, state_modified_compare_more_unrendered_values=False),
498+
None,
499+
)
500+
479501
self.parser = SchemaParser(
480502
project=self.snowplow_project_config,
481503
manifest=self.manifest,
@@ -557,6 +579,19 @@ def test_parse_source_field_at_custom_freshness_both_at_table_fails(self, _):
557579
with self.assertRaises(ParsingError):
558580
self.source_patcher.parse_source(unpatched_src_default)
559581

582+
@mock.patch("dbt.parser.sources.get_adapter")
583+
def test_parse_source_resulting_node_freshness_matches_config_freshness(self, _):
584+
block = self.file_block_for(SOURCE_FRESHNESS_AT_TABLE_AND_CONFIG, "test_one.yml")
585+
dct = yaml_from_file(block.file, validate=True)
586+
self.parser.parse_file(block, dct)
587+
unpatched_src_default = self.parser.manifest.sources["source.snowplow.my_source.my_table"]
588+
src_default = self.source_patcher.parse_source(unpatched_src_default)
589+
assert src_default.freshness == src_default.config.freshness
590+
assert src_default.freshness.warn_after.count == 2
591+
assert src_default.freshness.warn_after.period == "hour"
592+
assert src_default.freshness.error_after.count == 2
593+
assert src_default.freshness.error_after.period == "day"
594+
560595
@mock.patch("dbt.parser.sources.get_adapter")
561596
def test_parse_source_field_at_custom_freshness_both_at_source_fails(self, _):
562597
block = self.file_block_for(

0 commit comments

Comments
 (0)