22
33import typing as t
44from sqlmesh .utils import UniqueKeyDict , registry_decorator
5+ from sqlmesh .utils .errors import MissingSourceError
56
67if t .TYPE_CHECKING :
78 from sqlmesh .core .context import ExecutionContext
89 from sqlmesh .core .snapshot .definition import Snapshot
910 from sqlmesh .utils .date import DatetimeRanges
1011 from sqlmesh .core .snapshot .definition import DeployabilityIndex
12+ from sqlmesh .core .snapshot .definition import Intervals
1113
1214
1315class signal (registry_decorator ):
@@ -42,7 +44,17 @@ class signal(registry_decorator):
4244
4345
4446@signal ()
45- def freshness (batch : DatetimeRanges , snapshot : Snapshot , context : ExecutionContext ) -> bool :
47+ def freshness (
48+ batch : DatetimeRanges ,
49+ snapshot : Snapshot ,
50+ context : ExecutionContext ,
51+ parent_intervals : t .Optional [t .List [Intervals ]] = None ,
52+ ) -> bool :
53+ """
54+ Implements model freshness as a signal, i.e it considers this model to be fresh if:
55+ - Any upstream SQLMesh model has available intervals to compute i.e is fresh
56+ - Any upstream external model has been altered since the last time the model was evaluated
57+ """
4658 adapter = context .engine_adapter
4759 if context .is_restatement or not adapter .SUPPORTS_METADATA_TABLE_LAST_MODIFIED_TS :
4860 return True
@@ -54,24 +66,38 @@ def freshness(batch: DatetimeRanges, snapshot: Snapshot, context: ExecutionConte
5466 if deployability_index .is_deployable (snapshot )
5567 else snapshot .dev_last_altered_ts
5668 )
69+
5770 if not last_altered_ts :
5871 return True
5972
6073 parent_snapshots = {context .snapshots [p .name ] for p in snapshot .parents }
61- if len (parent_snapshots ) != len (snapshot .node .depends_on ) or not all (
62- p .is_external for p in parent_snapshots
63- ):
74+ if len (parent_snapshots ) != len (snapshot .node .depends_on ):
6475 # The mismatch can happen if e.g an external model is not registered in the project
6576 return True
6677
67- # Finding new data means that the upstream depedencies have been altered
68- # since the last time the model was evaluated
69- upstream_dep_has_new_data = any (
70- upstream_last_altered_ts > last_altered_ts
71- for upstream_last_altered_ts in adapter .get_table_last_modified_ts (
72- [p .name for p in parent_snapshots ]
78+ external_parent_snapshots = {p for p in parent_snapshots if p .is_external }
79+ upstream_parent_snapshots = parent_snapshots - external_parent_snapshots
80+
81+ if upstream_parent_snapshots and parent_intervals :
82+ # At least one upstream sqlmesh model has intervals to compute (i.e is not fresh),
83+ # so the current model should be considered fresh
84+ return True
85+
86+ if external_parent_snapshots :
87+ external_last_altered_timestamps = adapter .get_table_last_modified_ts (
88+ [sp .name for sp in external_parent_snapshots ]
89+ )
90+
91+ if len (external_last_altered_timestamps ) != len (external_parent_snapshots ):
92+ raise MissingSourceError (
93+ f"Expected { len (external_parent_snapshots )} sources to be present, but got { len (external_last_altered_timestamps )} ."
94+ )
95+
96+ # Finding new data means that the upstream depedencies have been altered
97+ # since the last time the model was evaluated
98+ return any (
99+ upstream_last_altered_ts > last_altered_ts
100+ for upstream_last_altered_ts in external_last_altered_timestamps
73101 )
74- )
75102
76- # Returning true is a no-op, returning False nullifies the batch so the model will not be evaluated.
77- return upstream_dep_has_new_data
103+ return False
0 commit comments