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
@@ -42,7 +43,16 @@ class signal(registry_decorator):
4243
4344
4445@signal ()
45- def freshness (batch : DatetimeRanges , snapshot : Snapshot , context : ExecutionContext ) -> bool :
46+ def freshness (
47+ batch : DatetimeRanges ,
48+ snapshot : Snapshot ,
49+ context : ExecutionContext ,
50+ ) -> bool :
51+ """
52+ Implements model freshness as a signal, i.e it considers this model to be fresh if:
53+ - Any upstream SQLMesh model has available intervals to compute i.e is fresh
54+ - Any upstream external model has been altered since the last time the model was evaluated
55+ """
4656 adapter = context .engine_adapter
4757 if context .is_restatement or not adapter .SUPPORTS_METADATA_TABLE_LAST_MODIFIED_TS :
4858 return True
@@ -54,24 +64,35 @@ def freshness(batch: DatetimeRanges, snapshot: Snapshot, context: ExecutionConte
5464 if deployability_index .is_deployable (snapshot )
5565 else snapshot .dev_last_altered_ts
5666 )
67+
5768 if not last_altered_ts :
5869 return True
5970
6071 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- ):
64- # The mismatch can happen if e.g an external model is not registered in the project
72+
73+ upstream_parent_snapshots = {p for p in parent_snapshots if not p .is_external }
74+ external_parents = snapshot .node .depends_on - {p .name for p in upstream_parent_snapshots }
75+
76+ if context .parent_intervals :
77+ # At least one upstream sqlmesh model has intervals to compute (i.e is fresh),
78+ # so the current model is considered fresh too
6579 return True
6680
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 ]
81+ if external_parents :
82+ external_last_altered_timestamps = adapter .get_table_last_modified_ts (
83+ list (external_parents )
84+ )
85+
86+ if len (external_last_altered_timestamps ) != len (external_parents ):
87+ raise MissingSourceError (
88+ f"Expected { len (external_parents )} sources to be present, but got { len (external_last_altered_timestamps )} ."
89+ )
90+
91+ # Finding new data means that the upstream depedencies have been altered
92+ # since the last time the model was evaluated
93+ return any (
94+ external_last_altered_ts > last_altered_ts
95+ for external_last_altered_ts in external_last_altered_timestamps
7396 )
74- )
7597
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
98+ return False
0 commit comments