Skip to content

Commit 83ba551

Browse files
Chore: Refine environment statements cli output (#4153)
1 parent a02d8b3 commit 83ba551

6 files changed

Lines changed: 138 additions & 48 deletions

File tree

sqlmesh/core/console.py

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,14 @@ def update_env_migration_progress(self, num_tasks: int) -> None:
281281
def stop_env_migration_progress(self, success: bool = True) -> None:
282282
"""Stop the environment migration progress."""
283283

284+
@abc.abstractmethod
285+
def show_environment_difference_summary(
286+
self,
287+
context_diff: ContextDiff,
288+
no_diff: bool = True,
289+
) -> None:
290+
"""Displays a summary of differences for the environment."""
291+
284292
@abc.abstractmethod
285293
def show_model_difference_summary(
286294
self,
@@ -551,13 +559,19 @@ def update_state_import_progress(
551559
def stop_state_import(self, success: bool, input_file: Path) -> None:
552560
pass
553561

562+
def show_environment_difference_summary(
563+
self,
564+
context_diff: ContextDiff,
565+
no_diff: bool = True,
566+
) -> None:
567+
pass
568+
554569
def show_model_difference_summary(
555570
self,
556571
context_diff: ContextDiff,
557572
environment_naming_info: EnvironmentNamingInfo,
558573
default_catalog: t.Optional[str],
559574
no_diff: bool = True,
560-
ignored_snapshot_ids: t.Optional[t.Set[SnapshotId]] = None,
561575
) -> None:
562576
pass
563577

@@ -1324,20 +1338,16 @@ def stop_state_import(self, success: bool, input_file: Path) -> None:
13241338
else:
13251339
self.log_error("State import failed!")
13261340

1327-
def show_model_difference_summary(
1341+
def show_environment_difference_summary(
13281342
self,
13291343
context_diff: ContextDiff,
1330-
environment_naming_info: EnvironmentNamingInfo,
1331-
default_catalog: t.Optional[str],
13321344
no_diff: bool = True,
13331345
) -> None:
1334-
"""Shows a summary of the differences.
1346+
"""Shows a summary of the environment differences.
13351347
13361348
Args:
13371349
context_diff: The context diff to use to print the summary
1338-
environment_naming_info: The environment naming info to reference when printing model names
1339-
default_catalog: The default catalog to reference when deciding to remove catalog from display names
1340-
no_diff: Hide the actual SQL differences.
1350+
no_diff: Hide the actual environment statement differences.
13411351
"""
13421352
if context_diff.is_new_environment:
13431353
msg = (
@@ -1372,11 +1382,28 @@ def show_model_difference_summary(
13721382
if context_diff.has_requirement_changes:
13731383
self._print(f"[bold]Requirements:\n{context_diff.requirements_diff()}")
13741384

1375-
if context_diff.has_environment_statements_changes:
1385+
if context_diff.has_environment_statements_changes and not no_diff:
13761386
self._print("[bold]Environment statements:\n")
1377-
for type, diff in context_diff.environment_statements_diff():
1387+
for type, diff in context_diff.environment_statements_diff(
1388+
include_python_env=not context_diff.is_new_environment
1389+
):
13781390
self._print(Syntax(diff, type, line_numbers=False))
13791391

1392+
def show_model_difference_summary(
1393+
self,
1394+
context_diff: ContextDiff,
1395+
environment_naming_info: EnvironmentNamingInfo,
1396+
default_catalog: t.Optional[str],
1397+
no_diff: bool = True,
1398+
) -> None:
1399+
"""Shows a summary of the model differences.
1400+
1401+
Args:
1402+
context_diff: The context diff to use to print the summary
1403+
environment_naming_info: The environment naming info to reference when printing model names
1404+
default_catalog: The default catalog to reference when deciding to remove catalog from display names
1405+
no_diff: Hide the actual SQL differences.
1406+
"""
13801407
self._show_summary_tree_for(
13811408
context_diff,
13821409
"Models",
@@ -1552,12 +1579,18 @@ def _prompt_categorize(
15521579
"""Get the user's change category for the directly modified models."""
15531580
plan = plan_builder.build()
15541581

1555-
self.show_model_difference_summary(
1582+
self.show_environment_difference_summary(
15561583
plan.context_diff,
1557-
plan.environment_naming_info,
1558-
default_catalog=default_catalog,
1584+
no_diff=no_diff,
15591585
)
15601586

1587+
if plan.context_diff.has_changes:
1588+
self.show_model_difference_summary(
1589+
plan.context_diff,
1590+
plan.environment_naming_info,
1591+
default_catalog=default_catalog,
1592+
)
1593+
15611594
if not no_diff:
15621595
self._show_categorized_snapshots(plan, default_catalog)
15631596

@@ -2497,20 +2530,16 @@ class MarkdownConsole(CaptureTerminalConsole):
24972530
def __init__(self, **kwargs: t.Any) -> None:
24982531
super().__init__(**{**kwargs, "console": RichConsole(no_color=True)})
24992532

2500-
def show_model_difference_summary(
2533+
def show_environment_difference_summary(
25012534
self,
25022535
context_diff: ContextDiff,
2503-
environment_naming_info: EnvironmentNamingInfo,
2504-
default_catalog: t.Optional[str],
25052536
no_diff: bool = True,
25062537
) -> None:
2507-
"""Shows a summary of the differences.
2538+
"""Shows a summary of the environment differences.
25082539
25092540
Args:
25102541
context_diff: The context diff to use to print the summary.
2511-
environment_naming_info: The environment naming info to reference when printing model names
2512-
default_catalog: The default catalog to reference when deciding to remove catalog from display names
2513-
no_diff: Hide the actual SQL differences.
2542+
no_diff: Hide the actual environment statements differences.
25142543
"""
25152544
if context_diff.is_new_environment:
25162545
self._print(
@@ -2528,11 +2557,28 @@ def show_model_difference_summary(
25282557
if context_diff.has_requirement_changes:
25292558
self._print(f"Requirements:\n{context_diff.requirements_diff()}")
25302559

2531-
if context_diff.has_environment_statements_changes:
2560+
if context_diff.has_environment_statements_changes and not no_diff:
25322561
self._print("[bold]Environment statements:\n")
2533-
for _, diff in context_diff.environment_statements_diff():
2562+
for _, diff in context_diff.environment_statements_diff(
2563+
include_python_env=not context_diff.is_new_environment
2564+
):
25342565
self._print(diff)
25352566

2567+
def show_model_difference_summary(
2568+
self,
2569+
context_diff: ContextDiff,
2570+
environment_naming_info: EnvironmentNamingInfo,
2571+
default_catalog: t.Optional[str],
2572+
no_diff: bool = True,
2573+
) -> None:
2574+
"""Shows a summary of the model differences.
2575+
2576+
Args:
2577+
context_diff: The context diff to use to print the summary.
2578+
environment_naming_info: The environment naming info to reference when printing model names
2579+
default_catalog: The default catalog to reference when deciding to remove catalog from display names
2580+
no_diff: Hide the actual SQL differences.
2581+
"""
25362582
added_snapshots = {context_diff.snapshots[s_id] for s_id in context_diff.added}
25372583
added_snapshot_models = {s for s in added_snapshots if s.is_model}
25382584
if added_snapshot_models:
@@ -3035,23 +3081,32 @@ def update_env_migration_progress(self, num_tasks: int) -> None:
30353081
def stop_env_migration_progress(self, success: bool = True) -> None:
30363082
self._write(f"Stopping environment migration with success={success}")
30373083

3038-
def show_model_difference_summary(
3084+
def show_environment_difference_summary(
30393085
self,
30403086
context_diff: ContextDiff,
3041-
environment_naming_info: EnvironmentNamingInfo,
3042-
default_catalog: t.Optional[str],
30433087
no_diff: bool = True,
30443088
) -> None:
3045-
self._write("Model Difference Summary:")
3089+
self._write("Environment Difference Summary:")
30463090

30473091
if context_diff.has_requirement_changes:
30483092
self._write(f"Requirements:\n{context_diff.requirements_diff()}")
30493093

3050-
if context_diff.has_environment_statements_changes:
3094+
if context_diff.has_environment_statements_changes and not no_diff:
30513095
self._write("Environment statements:\n")
3052-
for _, diff in context_diff.environment_statements_diff():
3096+
for _, diff in context_diff.environment_statements_diff(
3097+
include_python_env=not context_diff.is_new_environment
3098+
):
30533099
self._write(diff)
30543100

3101+
def show_model_difference_summary(
3102+
self,
3103+
context_diff: ContextDiff,
3104+
environment_naming_info: EnvironmentNamingInfo,
3105+
default_catalog: t.Optional[str],
3106+
no_diff: bool = True,
3107+
) -> None:
3108+
self._write("Model Difference Summary:")
3109+
30553110
for added in context_diff.new_snapshots:
30563111
self._write(f" Added: {added}")
30573112
for removed in context_diff.removed_snapshots:

sqlmesh/core/context.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,17 +1533,22 @@ def diff(self, environment: t.Optional[str] = None, detailed: bool = False) -> b
15331533
environment = environment or self.config.default_target_environment
15341534
environment = Environment.sanitize_name(environment)
15351535
context_diff = self._context_diff(environment)
1536-
self.console.show_model_difference_summary(
1536+
self.console.show_environment_difference_summary(
15371537
context_diff,
1538-
EnvironmentNamingInfo.from_environment_catalog_mapping(
1539-
self.config.environment_catalog_mapping,
1540-
name=environment,
1541-
suffix_target=self.config.environment_suffix_target,
1542-
normalize_name=context_diff.normalize_environment_name,
1543-
),
1544-
self.default_catalog,
15451538
no_diff=not detailed,
15461539
)
1540+
if context_diff.has_changes:
1541+
self.console.show_model_difference_summary(
1542+
context_diff,
1543+
EnvironmentNamingInfo.from_environment_catalog_mapping(
1544+
self.config.environment_catalog_mapping,
1545+
name=environment,
1546+
suffix_target=self.config.environment_suffix_target,
1547+
normalize_name=context_diff.normalize_environment_name,
1548+
),
1549+
self.default_catalog,
1550+
no_diff=not detailed,
1551+
)
15471552
return context_diff.has_changes
15481553

15491554
@python_api_analytics

sqlmesh/core/context_diff.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import sys
1616
import typing as t
17-
from difflib import ndiff
17+
from difflib import ndiff, unified_diff
1818
from functools import cached_property
1919
from sqlmesh.core import constants as c
2020
from sqlmesh.core.console import get_console
@@ -341,16 +341,19 @@ def requirements_diff(self) -> str:
341341
)
342342
)
343343

344-
def environment_statements_diff(self) -> t.List[t.Tuple[str, str]]:
344+
def environment_statements_diff(
345+
self, include_python_env: bool = False
346+
) -> t.List[t.Tuple[str, str]]:
345347
def extract_statements(statements: t.List[EnvironmentStatements], attr: str) -> t.List[str]:
346348
return [
347-
expr
349+
string
348350
for statement in statements
349351
for expr in (
350352
sorted_python_env_payloads(statement.python_env)
351353
if attr == "python_env"
352354
else getattr(statement, attr)
353355
)
356+
for string in expr.split("\n")
354357
]
355358

356359
def compute_diff(attribute: str) -> t.Optional[t.Tuple[str, str]]:
@@ -360,21 +363,24 @@ def compute_diff(attribute: str) -> t.Optional[t.Tuple[str, str]]:
360363
if previous == current:
361364
return None
362365

363-
diff_lines = list(ndiff(previous, current))
364366
diff_text = attribute if not attribute == "python_env" else "dependencies"
365367
diff_text += ":\n"
368+
if attribute == "python_env":
369+
diff = list(unified_diff(previous, current))
370+
diff_text += "\n".join(diff[2:] if len(diff) > 1 else diff)
371+
return "python", diff_text + "\n"
366372

373+
diff_lines = list(ndiff(previous, current))
367374
if any(line.startswith(("-", "+")) for line in diff_lines):
368375
diff_text += " " + "\n ".join(diff_lines) + "\n"
369-
370-
return "python" if attribute == "python_env" else "sql", diff_text
376+
return "sql", diff_text
371377

372378
return [
373379
diff
374380
for attribute in [
375381
RuntimeStage.BEFORE_ALL.value,
376382
RuntimeStage.AFTER_ALL.value,
377-
"python_env",
383+
*(["python_env"] if include_python_env else []),
378384
]
379385
if (diff := compute_diff(attribute)) is not None
380386
]

sqlmesh/integrations/github/cicd/controller.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -457,12 +457,17 @@ def get_plan_summary(self, plan: Plan) -> str:
457457
try:
458458
# Clear out any output that might exist from prior steps
459459
self._console.clear_captured_outputs()
460-
self._console.show_model_difference_summary(
460+
self._console.show_environment_difference_summary(
461461
context_diff=plan.context_diff,
462-
environment_naming_info=plan.environment_naming_info,
463-
default_catalog=self._context.default_catalog,
464462
no_diff=False,
465463
)
464+
if plan.context_diff.has_changes:
465+
self._console.show_model_difference_summary(
466+
context_diff=plan.context_diff,
467+
environment_naming_info=plan.environment_naming_info,
468+
default_catalog=self._context.default_catalog,
469+
no_diff=False,
470+
)
466471
difference_summary = self._console.consume_captured_output()
467472
self._console._show_missing_dates(plan, self._context.default_catalog)
468473
missing_dates = self._console.consume_captured_output()

tests/core/test_context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ def test_diff(sushi_context: Context, mocker: MockerFixture):
287287

288288
sushi_context.upsert_model("sushi.customers", query=parse_one("select 1 as customer_id"))
289289
sushi_context.diff("test")
290+
assert mock_console.show_environment_difference_summary.called
290291
assert mock_console.show_model_difference_summary.called
291292
assert success
292293

tests/core/test_plan.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3023,15 +3023,33 @@ def test_plan_environment_statements_diff(make_snapshot):
30233023
terminal_console._print(diff)
30243024
output = console_output.getvalue()
30253025
stripped = strip_ansi_codes(output)
3026+
3027+
expected_output = (
3028+
"before_all:\n"
3029+
" + CREATE OR REPLACE TABLE table_1 AS SELECT 1\n"
3030+
" + @test_macro()\n\n"
3031+
"after_all:\n"
3032+
" + CREATE OR REPLACE TABLE table_2 AS SELECT 2"
3033+
)
3034+
assert stripped == expected_output
3035+
console_output.close()
3036+
3037+
# Validate with python env included
3038+
console_output, terminal_console = create_test_console()
3039+
for _, diff in context_diff.environment_statements_diff(include_python_env=True):
3040+
terminal_console._print(diff)
3041+
output = console_output.getvalue()
3042+
stripped = strip_ansi_codes(output)
30263043
expected_output = (
30273044
"before_all:\n"
30283045
" + CREATE OR REPLACE TABLE table_1 AS SELECT 1\n"
30293046
" + @test_macro()\n\n"
30303047
"after_all:\n"
30313048
" + CREATE OR REPLACE TABLE table_2 AS SELECT 2\n\n"
30323049
"dependencies:\n"
3033-
" + def test_macro(evaluator):\n"
3034-
" return 'one'"
3050+
"@@ -0,0 +1,2 @@\n\n"
3051+
"+def test_macro(evaluator):\n"
3052+
"+ return 'one'"
30353053
)
30363054
assert stripped == expected_output
30373055
console_output.close()

0 commit comments

Comments
 (0)