@@ -85,7 +85,9 @@ def assert_model_evaluation(
8585 return timestamp , plan_or_run_result
8686
8787
88- def create_freshness_model (name : str , schema : str , query : str , path : pathlib .Path ):
88+ def create_model (
89+ name : str , schema : str , query : str , path : pathlib .Path , signals : str = "freshness()"
90+ ):
8991 """
9092 Create a freshness model with the given name, path, and query.
9193 """
@@ -99,7 +101,7 @@ def create_freshness_model(name: str, schema: str, query: str, path: pathlib.Pat
99101 start '2024-01-01',
100102 kind FULL,
101103 signals (
102- freshness() ,
104+ { signals } ,
103105 )
104106 );
105107
@@ -167,7 +169,7 @@ def test_external_model_freshness(ctx: TestContext, tmp_path: pathlib.Path, mock
167169 )
168170
169171 # Create model that depends on external models
170- model_name , model_path = create_freshness_model (
172+ model_name , model_path = create_model (
171173 "new_model" ,
172174 schema ,
173175 f"SELECT col1 * col2 AS col FROM { external_table1 } , { external_table2 } " ,
@@ -237,31 +239,31 @@ def test_mixed_model_freshness(ctx: TestContext, tmp_path: pathlib.Path):
237239 context , schema , (external_table ,) = initialize_context (ctx , tmp_path , num_external_models = 1 )
238240
239241 # Create parent model that depends on the external model
240- parent_model_name , _ = create_freshness_model (
242+ parent_model_name , _ = create_model (
241243 "parent_model" ,
242244 schema ,
243245 f"SELECT col1 AS new_col FROM { external_table } " ,
244246 tmp_path ,
245247 )
246248
247249 # First child model depends only on the parent model
248- create_freshness_model (
250+ create_model (
249251 "child_model1" ,
250252 schema ,
251253 f"SELECT new_col FROM { parent_model_name } " ,
252254 tmp_path ,
253255 )
254256
255257 # Second child model depends on the parent model and the external table
256- create_freshness_model (
258+ create_model (
257259 "child_model2" ,
258260 schema ,
259261 f"SELECT col1 + new_col FROM { parent_model_name } , { external_table } " ,
260262 tmp_path ,
261263 )
262264
263265 # Third model does not depend on any models, so it should only be evaluated once
264- create_freshness_model (
266+ create_model (
265267 "child_model3" ,
266268 schema ,
267269 f"SELECT 1 AS col" ,
@@ -287,7 +289,7 @@ def test_mixed_model_freshness(ctx: TestContext, tmp_path: pathlib.Path):
287289 )
288290
289291 # Case 3: Mixed models are still evaluated if breaking changes are introduced
290- create_freshness_model (
292+ create_model (
291293 "child_model2" ,
292294 schema ,
293295 f"SELECT col1 * new_col FROM { parent_model_name } , { external_table } " ,
@@ -317,7 +319,7 @@ def test_missing_external_model_freshness(ctx: TestContext, tmp_path: pathlib.Pa
317319 context , schema , (external_table ,) = initialize_context (ctx , tmp_path )
318320
319321 # Create model that depends on the external model
320- create_freshness_model (
322+ create_model (
321323 "new_model" ,
322324 schema ,
323325 f"SELECT * FROM { external_table } " ,
@@ -334,3 +336,81 @@ def test_missing_external_model_freshness(ctx: TestContext, tmp_path: pathlib.Pa
334336 with time_machine .travel (now () + timedelta (days = 1 )):
335337 with pytest .raises (SignalEvalError ):
336338 context .run ()
339+
340+
341+ @use_terminal_console
342+ def test_check_ready_intervals (ctx : TestContext , tmp_path : pathlib .Path ):
343+ """
344+ Scenario: Ensure that freshness evaluates the "ready" intervals of the parent snapshots i.e their
345+ missing intervals plus their signals applied.
346+
347+ """
348+
349+ def _write_user_signal (signal : str , tmp_path : pathlib .Path ):
350+ signal_code = f"""
351+ import typing as t
352+ from sqlmesh import signal
353+
354+ @signal()
355+ { signal }
356+ """
357+
358+ test_signals = tmp_path / "signals/test_signals.py"
359+ test_signals .parent .mkdir (parents = True , exist_ok = True )
360+ test_signals .write_text (signal_code )
361+
362+ context , schema , _ = initialize_context (ctx , tmp_path , num_external_models = 0 )
363+
364+ _write_user_signal (
365+ """
366+ def my_signal(batch):
367+ return True
368+ """ ,
369+ tmp_path ,
370+ )
371+
372+ # Parent model depends on a custom signal
373+ parent_model , _ = create_model (
374+ "parent_model" ,
375+ schema ,
376+ f"SELECT 1 AS col" ,
377+ tmp_path ,
378+ signals = "my_signal()" ,
379+ )
380+
381+ # Create a new model that depends on the parent model
382+ create_model (
383+ "child_model" ,
384+ schema ,
385+ f"SELECT * FROM { parent_model } " ,
386+ tmp_path ,
387+ )
388+
389+ # Case 1: Both models are evaluated when introduced in a plan and subsequent runs,
390+ # given that `my_signal()` always returns True.
391+ context .load ()
392+ context .plan (auto_apply = True , no_prompts = True )
393+
394+ assert_model_evaluation (
395+ lambda : context .run (),
396+ day_delta = 2 ,
397+ model_evaluations = 2 ,
398+ )
399+
400+ # Case 2: By changing the signal to return False, both models should not be evaluated.
401+ _write_user_signal (
402+ """
403+ def my_signal(batch):
404+ return False
405+ """ ,
406+ tmp_path ,
407+ )
408+
409+ context .load ()
410+ context .plan (auto_apply = True , no_prompts = True )
411+
412+ assert_model_evaluation (
413+ lambda : context .run (),
414+ day_delta = 3 ,
415+ was_evaluated = False ,
416+ )
0 commit comments