Skip to content

Commit 7271e8f

Browse files
authored
Feat: print CLI messages when no physical layer or model evals occurred (#4113)
1 parent e3e32bf commit 7271e8f

6 files changed

Lines changed: 56 additions & 45 deletions

File tree

sqlmesh/core/console.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1303,7 +1303,7 @@ def show_model_difference_summary(
13031303
):
13041304
self._print(
13051305
Tree(
1306-
f"[bold]Differences from the `{context_diff.create_from if context_diff.is_new_environment else context_diff.environment}` environment:\n"
1306+
f"\n[bold]Differences from the `{context_diff.create_from if context_diff.is_new_environment else context_diff.environment}` environment:\n"
13071307
)
13081308
)
13091309

sqlmesh/core/plan/evaluator.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
SnapshotInfoLike,
3838
SnapshotTableInfo,
3939
)
40+
from sqlmesh.utils import CompletionStatus
4041
from sqlmesh.core.state_sync import StateSync
4142
from sqlmesh.core.state_sync.base import PromotionResult
4243
from sqlmesh.core.user import User
@@ -133,10 +134,14 @@ def evaluate(
133134
execution_time=plan.execution_time,
134135
)
135136

136-
self._push(plan, snapshots, deployability_index_for_creation)
137+
push_completion_status = self._push(plan, snapshots, deployability_index_for_creation)
138+
if push_completion_status.is_nothing_to_do:
139+
self.console.log_status_update(
140+
"\n[green]SKIP: No physical layer updates to perform[/green]\n"
141+
)
137142
update_intervals_for_new_snapshots(plan.new_snapshots, self.state_sync)
138143
self._restate(plan, snapshots_by_name)
139-
self._backfill(
144+
first_bf_completion_status = self._backfill(
140145
plan,
141146
snapshots_by_name,
142147
before_promote_snapshots,
@@ -146,20 +151,22 @@ def evaluate(
146151
promotion_result = self._promote(
147152
plan, snapshots, before_promote_snapshots, deployability_index_for_creation
148153
)
149-
self._backfill(
154+
second_bf_completion_status = self._backfill(
150155
plan,
151156
snapshots_by_name,
152157
after_promote_snapshots,
153158
deployability_index_for_evaluation,
154159
circuit_breaker=circuit_breaker,
155160
)
161+
if (
162+
first_bf_completion_status.is_nothing_to_do
163+
and second_bf_completion_status.is_nothing_to_do
164+
):
165+
self.console.log_status_update("[green]SKIP: No model batches to execute[/green]\n")
156166
self._update_views(
157167
plan, snapshots, promotion_result, deployability_index_for_evaluation
158168
)
159169

160-
if not plan.requires_backfill:
161-
self.console.log_success("Virtual Update executed successfully")
162-
163170
execute_environment_statements(
164171
adapter=self.snapshot_evaluator.adapter,
165172
environment_statements=plan.environment_statements or [],
@@ -187,7 +194,7 @@ def _backfill(
187194
selected_snapshots: t.Set[str],
188195
deployability_index: DeployabilityIndex,
189196
circuit_breaker: t.Optional[t.Callable[[], bool]] = None,
190-
) -> None:
197+
) -> CompletionStatus:
191198
"""Backfill missing intervals for snapshots that are part of the given plan.
192199
193200
Args:
@@ -212,10 +219,10 @@ def _backfill(
212219
)
213220
)
214221
self.state_sync.add_snapshots_intervals(intervals_to_add)
215-
return
222+
return CompletionStatus.NOTHING_TO_DO
216223

217224
if not plan.requires_backfill or not selected_snapshots:
218-
return
225+
return CompletionStatus.NOTHING_TO_DO
219226

220227
scheduler = self.create_scheduler(snapshots_by_name.values())
221228
completion_status = scheduler.run(
@@ -236,12 +243,14 @@ def _backfill(
236243
if completion_status.is_failure:
237244
raise PlanError("Plan application failed.")
238245

246+
return completion_status
247+
239248
def _push(
240249
self,
241250
plan: EvaluatablePlan,
242251
snapshots: t.Dict[SnapshotId, Snapshot],
243252
deployability_index: t.Optional[DeployabilityIndex] = None,
244-
) -> None:
253+
) -> CompletionStatus:
245254
"""Push the snapshots to the state sync.
246255
247256
As a part of plan pushing, snapshot tables are created.
@@ -268,10 +277,10 @@ def _should_create(s: Snapshot) -> bool:
268277

269278
snapshots_to_create = [s for s in snapshots.values() if _should_create(s)]
270279

271-
completed = False
280+
completion_status = None
272281
progress_stopped = False
273282
try:
274-
self.snapshot_evaluator.create(
283+
completion_status = self.snapshot_evaluator.create(
275284
snapshots_to_create,
276285
snapshots,
277286
allow_destructive_snapshots=plan.allow_destructive_models,
@@ -281,7 +290,6 @@ def _should_create(s: Snapshot) -> bool:
281290
),
282291
on_complete=self.console.update_creation_progress,
283292
)
284-
completed = True
285293
except NodeExecutionFailedError as ex:
286294
self.console.stop_creation_progress(success=False)
287295
progress_stopped = True
@@ -292,13 +300,16 @@ def _should_create(s: Snapshot) -> bool:
292300
raise PlanError("Plan application failed.")
293301
finally:
294302
if not progress_stopped:
295-
self.console.stop_creation_progress(success=completed)
303+
self.console.stop_creation_progress(
304+
success=completion_status is not None and completion_status.is_success
305+
)
296306

297307
self.state_sync.push_snapshots(plan.new_snapshots)
298308

299309
analytics.collector.on_snapshots_created(
300310
new_snapshots=plan.new_snapshots, plan_id=plan.plan_id
301311
)
312+
return completion_status
302313

303314
def _promote(
304315
self,

sqlmesh/core/scheduler.py

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from __future__ import annotations
2-
from enum import Enum
32
import logging
43
import typing as t
54
from sqlglot import exp
@@ -31,6 +30,7 @@
3130
parent_snapshots_by_name,
3231
)
3332
from sqlmesh.core.state_sync import StateSync
33+
from sqlmesh.utils import CompletionStatus
3434
from sqlmesh.utils.concurrency import concurrent_apply_to_dag, NodeExecutionFailedError
3535
from sqlmesh.utils.dag import DAG
3636
from sqlmesh.utils.date import (
@@ -48,24 +48,6 @@
4848
SchedulingUnit = t.Tuple[str, t.Tuple[Interval, int]]
4949

5050

51-
class CompletionStatus(Enum):
52-
SUCCESS = "success"
53-
FAILURE = "failure"
54-
NOTHING_TO_DO = "nothing_to_do"
55-
56-
@property
57-
def is_success(self) -> bool:
58-
return self == CompletionStatus.SUCCESS
59-
60-
@property
61-
def is_failure(self) -> bool:
62-
return self == CompletionStatus.FAILURE
63-
64-
@property
65-
def is_nothing_to_do(self) -> bool:
66-
return self == CompletionStatus.NOTHING_TO_DO
67-
68-
6951
class Scheduler:
7052
"""Schedules and manages the evaluation of snapshots.
7153

sqlmesh/core/snapshot/evaluator.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
ViewKind,
5252
CustomKind,
5353
)
54-
54+
from sqlmesh.utils import CompletionStatus
5555
from sqlmesh.core.schema_diff import has_drop_alteration, get_dropped_column_names
5656
from sqlmesh.core.snapshot import (
5757
DeployabilityIndex,
@@ -289,7 +289,7 @@ def create(
289289
on_start: t.Optional[t.Callable] = None,
290290
on_complete: t.Optional[t.Callable[[SnapshotInfoLike], None]] = None,
291291
allow_destructive_snapshots: t.Optional[t.Set[str]] = None,
292-
) -> None:
292+
) -> CompletionStatus:
293293
"""Creates a physical snapshot schema and table for the given collection of snapshots.
294294
295295
Args:
@@ -299,6 +299,9 @@ def create(
299299
on_start: A callback to initialize the snapshot creation progress bar.
300300
on_complete: A callback to call on each successfully created snapshot.
301301
allow_destructive_snapshots: Set of snapshots that are allowed to have destructive schema changes.
302+
303+
Returns:
304+
CompletionStatus: The status of the creation operation (success, failure, nothing to do).
302305
"""
303306
snapshots_with_table_names = defaultdict(set)
304307
tables_by_gateway_and_schema: t.Dict[t.Union[str, None], t.Dict[exp.Table, set[str]]] = (
@@ -366,7 +369,7 @@ def _get_data_objects(
366369
target_deployability_flags[snapshot.name].sort()
367370

368371
if not snapshots_to_create:
369-
return
372+
return CompletionStatus.NOTHING_TO_DO
370373
if on_start:
371374
on_start(len(snapshots_to_create))
372375

@@ -381,6 +384,7 @@ def _get_data_objects(
381384
on_complete=on_complete,
382385
allow_destructive_snapshots=allow_destructive_snapshots,
383386
)
387+
return CompletionStatus.SUCCESS
384388

385389
def _create_snapshots(
386390
self,

sqlmesh/utils/__init__.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from collections import defaultdict
1717
from contextlib import contextmanager
1818
from copy import deepcopy
19-
from enum import IntEnum
19+
from enum import IntEnum, Enum
2020
from functools import lru_cache, reduce, wraps
2121
from pathlib import Path
2222

@@ -346,3 +346,21 @@ class Verbosity(IntEnum):
346346
DEFAULT = 0
347347
VERBOSE = 1
348348
VERY_VERBOSE = 2
349+
350+
351+
class CompletionStatus(Enum):
352+
SUCCESS = "success"
353+
FAILURE = "failure"
354+
NOTHING_TO_DO = "nothing_to_do"
355+
356+
@property
357+
def is_success(self) -> bool:
358+
return self == CompletionStatus.SUCCESS
359+
360+
@property
361+
def is_failure(self) -> bool:
362+
return self == CompletionStatus.FAILURE
363+
364+
@property
365+
def is_nothing_to_do(self) -> bool:
366+
return self == CompletionStatus.NOTHING_TO_DO

tests/cli/test_cli.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,6 @@ def assert_plan_success(result, new_env="prod", from_env="prod") -> None:
154154
assert_backfill_success(result)
155155

156156

157-
def assert_virtual_update(result) -> None:
158-
assert "Virtual Update executed" in result.output
159-
160-
161157
def test_version(runner, tmp_path):
162158
from sqlmesh import __version__ as SQLMESH_VERSION
163159

@@ -275,7 +271,7 @@ def test_plan_skip_backfill(runner, tmp_path, flag):
275271
input="y\n",
276272
)
277273
assert result.exit_code == 0
278-
assert_virtual_update(result)
274+
assert_virtual_layer_updated(result)
279275
assert "Model batches executed" not in result.output
280276

281277

@@ -404,8 +400,9 @@ def test_plan_dev_create_from_virtual(runner, tmp_path):
404400
)
405401
assert result.exit_code == 0
406402
assert_new_env(result, "dev2", "dev", initialize=False)
403+
assert "SKIP: No physical layer updates to perform" in result.output
404+
assert "SKIP: No model batches to execute" in result.output
407405
assert_virtual_layer_updated(result)
408-
assert_virtual_update(result)
409406

410407

411408
def test_plan_dev_create_from(runner, tmp_path):
@@ -542,7 +539,6 @@ def test_plan_dev_no_changes(runner, tmp_path):
542539
assert result.exit_code == 0
543540
assert_new_env(result, "dev", initialize=False)
544541
assert_virtual_layer_updated(result)
545-
assert_virtual_update(result)
546542

547543

548544
def test_plan_nonbreaking(runner, tmp_path):

0 commit comments

Comments
 (0)