sqlmesh 0.199.1.dev1__py3-none-any.whl → 0.199.1.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlmesh/_version.py +2 -2
- sqlmesh/core/console.py +34 -4
- sqlmesh/integrations/github/cicd/command.py +8 -1
- sqlmesh/integrations/github/cicd/controller.py +280 -58
- {sqlmesh-0.199.1.dev1.dist-info → sqlmesh-0.199.1.dev2.dist-info}/METADATA +1 -1
- {sqlmesh-0.199.1.dev1.dist-info → sqlmesh-0.199.1.dev2.dist-info}/RECORD +10 -10
- {sqlmesh-0.199.1.dev1.dist-info → sqlmesh-0.199.1.dev2.dist-info}/WHEEL +0 -0
- {sqlmesh-0.199.1.dev1.dist-info → sqlmesh-0.199.1.dev2.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.199.1.dev1.dist-info → sqlmesh-0.199.1.dev2.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.199.1.dev1.dist-info → sqlmesh-0.199.1.dev2.dist-info}/top_level.txt +0 -0
sqlmesh/_version.py
CHANGED
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '0.199.1.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 199, 1, '
|
|
20
|
+
__version__ = version = '0.199.1.dev2'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 199, 1, 'dev2')
|
sqlmesh/core/console.py
CHANGED
|
@@ -3224,8 +3224,9 @@ class MarkdownConsole(CaptureTerminalConsole):
|
|
|
3224
3224
|
)
|
|
3225
3225
|
else:
|
|
3226
3226
|
for snapshot_table_info in models:
|
|
3227
|
+
category_str = SNAPSHOT_CHANGE_CATEGORY_STR[snapshot_table_info.change_category]
|
|
3227
3228
|
self._print(
|
|
3228
|
-
f"- `{snapshot_table_info.display_name(environment_naming_info, default_catalog if self.verbosity < Verbosity.VERY_VERBOSE else None, dialect=self.dialect)}`"
|
|
3229
|
+
f"- `{snapshot_table_info.display_name(environment_naming_info, default_catalog if self.verbosity < Verbosity.VERY_VERBOSE else None, dialect=self.dialect)}` ({category_str})"
|
|
3229
3230
|
)
|
|
3230
3231
|
|
|
3231
3232
|
def _print_modified_models(
|
|
@@ -3237,7 +3238,7 @@ class MarkdownConsole(CaptureTerminalConsole):
|
|
|
3237
3238
|
no_diff: bool = True,
|
|
3238
3239
|
) -> None:
|
|
3239
3240
|
directly_modified = []
|
|
3240
|
-
indirectly_modified = []
|
|
3241
|
+
indirectly_modified: t.List[Snapshot] = []
|
|
3241
3242
|
metadata_modified = []
|
|
3242
3243
|
for snapshot in modified_snapshots:
|
|
3243
3244
|
if context_diff.directly_modified(snapshot.name):
|
|
@@ -3249,11 +3250,40 @@ class MarkdownConsole(CaptureTerminalConsole):
|
|
|
3249
3250
|
if directly_modified:
|
|
3250
3251
|
self._print("\n**Directly Modified:**")
|
|
3251
3252
|
for snapshot in sorted(directly_modified):
|
|
3253
|
+
category_str = SNAPSHOT_CHANGE_CATEGORY_STR[snapshot.change_category]
|
|
3252
3254
|
self._print(
|
|
3253
|
-
f"
|
|
3255
|
+
f"* `{snapshot.display_name(environment_naming_info, default_catalog if self.verbosity < Verbosity.VERY_VERBOSE else None, dialect=self.dialect)}` ({category_str})"
|
|
3256
|
+
)
|
|
3257
|
+
|
|
3258
|
+
indirectly_modified_children = sorted(
|
|
3259
|
+
[s for s in indirectly_modified if snapshot.snapshot_id in s.parents]
|
|
3254
3260
|
)
|
|
3261
|
+
|
|
3255
3262
|
if not no_diff:
|
|
3256
|
-
|
|
3263
|
+
diff_text = context_diff.text_diff(snapshot.name)
|
|
3264
|
+
# sometimes there is no text_diff, like on a seed model where the data has been updated
|
|
3265
|
+
if diff_text:
|
|
3266
|
+
diff_text = f"\n```diff\n{diff_text}\n```"
|
|
3267
|
+
# these are part of a Markdown list, so indent them by 2 spaces to relate them to the current list item
|
|
3268
|
+
diff_text_indented = "\n".join(
|
|
3269
|
+
[f" {line}" for line in diff_text.splitlines()]
|
|
3270
|
+
)
|
|
3271
|
+
self._print(diff_text_indented)
|
|
3272
|
+
else:
|
|
3273
|
+
if indirectly_modified_children:
|
|
3274
|
+
self._print("\n")
|
|
3275
|
+
|
|
3276
|
+
if indirectly_modified_children:
|
|
3277
|
+
self._print(" Indirectly Modified Children:")
|
|
3278
|
+
for child_snapshot in indirectly_modified_children:
|
|
3279
|
+
child_category_str = SNAPSHOT_CHANGE_CATEGORY_STR[
|
|
3280
|
+
child_snapshot.change_category
|
|
3281
|
+
]
|
|
3282
|
+
self._print(
|
|
3283
|
+
f" - `{child_snapshot.display_name(environment_naming_info, default_catalog if self.verbosity < Verbosity.VERY_VERBOSE else None, dialect=self.dialect)}` ({child_category_str})"
|
|
3284
|
+
)
|
|
3285
|
+
self._print("\n")
|
|
3286
|
+
|
|
3257
3287
|
if indirectly_modified:
|
|
3258
3288
|
self._print("\n**Indirectly Modified:**")
|
|
3259
3289
|
self._print_models_with_threshold(
|
|
@@ -115,8 +115,12 @@ def _update_pr_environment(controller: GithubController) -> bool:
|
|
|
115
115
|
conclusion = controller.update_pr_environment_check(status=GithubCheckStatus.COMPLETED)
|
|
116
116
|
return conclusion is not None and conclusion.is_success
|
|
117
117
|
except Exception as e:
|
|
118
|
+
logger.exception("Error occurred when updating PR environment")
|
|
118
119
|
conclusion = controller.update_pr_environment_check(
|
|
119
|
-
status=GithubCheckStatus.COMPLETED,
|
|
120
|
+
status=GithubCheckStatus.COMPLETED,
|
|
121
|
+
exception=e,
|
|
122
|
+
plan=controller.pr_plan_or_none,
|
|
123
|
+
plan_flags=controller.pr_plan_flags,
|
|
120
124
|
)
|
|
121
125
|
return (
|
|
122
126
|
conclusion is not None
|
|
@@ -147,6 +151,7 @@ def _gen_prod_plan(controller: GithubController) -> bool:
|
|
|
147
151
|
)
|
|
148
152
|
return bool(plan_summary)
|
|
149
153
|
except Exception as e:
|
|
154
|
+
logger.exception("Error occurred generating prod plan")
|
|
150
155
|
controller.update_prod_plan_preview_check(
|
|
151
156
|
status=GithubCheckStatus.COMPLETED,
|
|
152
157
|
conclusion=GithubCheckConclusion.FAILURE,
|
|
@@ -211,6 +216,8 @@ def deploy_production(ctx: click.Context) -> None:
|
|
|
211
216
|
|
|
212
217
|
|
|
213
218
|
def _run_all(controller: GithubController) -> None:
|
|
219
|
+
click.echo(f"SQLMesh Version: {controller.version_info}")
|
|
220
|
+
|
|
214
221
|
has_required_approval = False
|
|
215
222
|
is_auto_deploying_prod = (
|
|
216
223
|
controller.deploy_command_enabled or controller.do_required_approval_check
|
|
@@ -9,11 +9,11 @@ import re
|
|
|
9
9
|
import traceback
|
|
10
10
|
import typing as t
|
|
11
11
|
from enum import Enum
|
|
12
|
-
from typing import List
|
|
13
12
|
from pathlib import Path
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from functools import cached_property
|
|
14
15
|
|
|
15
16
|
import requests
|
|
16
|
-
from hyperscript import Element, h
|
|
17
17
|
from sqlglot.helper import seq_get
|
|
18
18
|
|
|
19
19
|
from sqlmesh.core import constants as c
|
|
@@ -21,13 +21,13 @@ from sqlmesh.core.console import SNAPSHOT_CHANGE_CATEGORY_STR, get_console, Mark
|
|
|
21
21
|
from sqlmesh.core.context import Context
|
|
22
22
|
from sqlmesh.core.test.result import ModelTextTestResult
|
|
23
23
|
from sqlmesh.core.environment import Environment
|
|
24
|
-
from sqlmesh.core.plan import Plan, PlanBuilder
|
|
24
|
+
from sqlmesh.core.plan import Plan, PlanBuilder, SnapshotIntervals
|
|
25
|
+
from sqlmesh.core.plan.definition import UserProvidedFlags
|
|
25
26
|
from sqlmesh.core.snapshot.definition import (
|
|
26
27
|
Snapshot,
|
|
27
28
|
SnapshotChangeCategory,
|
|
28
29
|
SnapshotId,
|
|
29
30
|
SnapshotTableInfo,
|
|
30
|
-
format_intervals,
|
|
31
31
|
)
|
|
32
32
|
from sqlglot.errors import SqlglotError
|
|
33
33
|
from sqlmesh.core.user import User
|
|
@@ -415,6 +415,14 @@ class GithubController:
|
|
|
415
415
|
except:
|
|
416
416
|
return None
|
|
417
417
|
|
|
418
|
+
@property
|
|
419
|
+
def pr_plan_flags(self) -> t.Optional[t.Dict[str, UserProvidedFlags]]:
|
|
420
|
+
if pr_plan := self.pr_plan_or_none:
|
|
421
|
+
return pr_plan.user_provided_flags
|
|
422
|
+
if pr_plan_builder := self._pr_plan_builder:
|
|
423
|
+
return pr_plan_builder._user_provided_flags
|
|
424
|
+
return None
|
|
425
|
+
|
|
418
426
|
@property
|
|
419
427
|
def prod_plan(self) -> Plan:
|
|
420
428
|
if not self._prod_plan_builder:
|
|
@@ -434,7 +442,9 @@ class GithubController:
|
|
|
434
442
|
if not self._prod_plan_with_gaps_builder:
|
|
435
443
|
self._prod_plan_with_gaps_builder = self._context.plan_builder(
|
|
436
444
|
c.PROD,
|
|
445
|
+
# this is required to highlight any data gaps between this PR environment and prod (since PR environments may only contain a subset of data)
|
|
437
446
|
no_gaps=False,
|
|
447
|
+
# this works because the snapshots were already categorized when applying self.pr_plan so there are no uncategorized local snapshots to trigger a plan error
|
|
438
448
|
no_auto_categorization=True,
|
|
439
449
|
skip_tests=True,
|
|
440
450
|
skip_linter=True,
|
|
@@ -495,9 +505,17 @@ class GithubController:
|
|
|
495
505
|
difference_summary = self._console.consume_captured_output()
|
|
496
506
|
self._console._show_missing_dates(plan, self._context.default_catalog)
|
|
497
507
|
missing_dates = self._console.consume_captured_output()
|
|
508
|
+
|
|
509
|
+
plan_flags_section = (
|
|
510
|
+
f"\n\n{self._generate_plan_flags_section(plan.user_provided_flags)}"
|
|
511
|
+
if plan.user_provided_flags
|
|
512
|
+
else ""
|
|
513
|
+
)
|
|
514
|
+
|
|
498
515
|
if not difference_summary and not missing_dates:
|
|
499
|
-
return "No changes to apply."
|
|
500
|
-
|
|
516
|
+
return f"No changes to apply.{plan_flags_section}"
|
|
517
|
+
|
|
518
|
+
return f"{difference_summary}\n{missing_dates}{plan_flags_section}"
|
|
501
519
|
except PlanError as e:
|
|
502
520
|
return f"Plan failed to generate. Check for pending or unresolved changes. Error: {e}"
|
|
503
521
|
|
|
@@ -831,6 +849,9 @@ class GithubController:
|
|
|
831
849
|
status: GithubCheckStatus,
|
|
832
850
|
exception: t.Optional[Exception] = None,
|
|
833
851
|
plan: t.Optional[Plan] = None,
|
|
852
|
+
plan_flags: t.Optional[
|
|
853
|
+
t.Dict[str, UserProvidedFlags]
|
|
854
|
+
] = None, # note: the plan flags are passed separately in case the plan fails to build
|
|
834
855
|
) -> t.Optional[GithubCheckConclusion]:
|
|
835
856
|
"""
|
|
836
857
|
Updates the status of the merge commit for the PR environment.
|
|
@@ -852,58 +873,16 @@ class GithubController:
|
|
|
852
873
|
conclusion: GithubCheckConclusion, exception: t.Optional[Exception]
|
|
853
874
|
) -> t.Tuple[GithubCheckConclusion, str, t.Optional[str]]:
|
|
854
875
|
if conclusion.is_success:
|
|
855
|
-
|
|
876
|
+
prod_plan = self.prod_plan_with_gaps
|
|
877
|
+
|
|
878
|
+
if not prod_plan.has_changes:
|
|
856
879
|
summary = "No models were modified in this PR.\n"
|
|
857
880
|
else:
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
h("th", "Dates Loaded"),
|
|
864
|
-
],
|
|
865
|
-
]
|
|
866
|
-
body_rows: List[Element | List[Element]] = []
|
|
867
|
-
for modified_snapshot in self.modified_snapshots.values():
|
|
868
|
-
# We don't want to display indirect non-breaking since to users these are effectively no-op changes
|
|
869
|
-
if modified_snapshot.is_indirect_non_breaking:
|
|
870
|
-
continue
|
|
871
|
-
if modified_snapshot.snapshot_id in self.removed_snapshots:
|
|
872
|
-
# This will be an FQN since we don't have access to node name from a snapshot table info
|
|
873
|
-
# which is what a removed snapshot is
|
|
874
|
-
model_name = modified_snapshot.name
|
|
875
|
-
change_category = SNAPSHOT_CHANGE_CATEGORY_STR[
|
|
876
|
-
SnapshotChangeCategory.BREAKING
|
|
877
|
-
]
|
|
878
|
-
interval_output = "REMOVED"
|
|
879
|
-
else:
|
|
880
|
-
assert isinstance(modified_snapshot, Snapshot)
|
|
881
|
-
model_name = modified_snapshot.node.name
|
|
882
|
-
change_category = (
|
|
883
|
-
"Uncategorized"
|
|
884
|
-
if not modified_snapshot.change_category
|
|
885
|
-
else SNAPSHOT_CHANGE_CATEGORY_STR[modified_snapshot.change_category]
|
|
886
|
-
)
|
|
887
|
-
intervals = (
|
|
888
|
-
modified_snapshot.dev_intervals
|
|
889
|
-
if modified_snapshot.is_forward_only
|
|
890
|
-
else modified_snapshot.intervals
|
|
891
|
-
)
|
|
892
|
-
interval_output = (
|
|
893
|
-
format_intervals(intervals, modified_snapshot.node.interval_unit)
|
|
894
|
-
if intervals
|
|
895
|
-
else "N/A"
|
|
896
|
-
)
|
|
897
|
-
body_rows.append(
|
|
898
|
-
[
|
|
899
|
-
h("td", model_name, autoescape=False),
|
|
900
|
-
h("td", change_category),
|
|
901
|
-
h("td", interval_output),
|
|
902
|
-
]
|
|
903
|
-
)
|
|
904
|
-
table_header = h("thead", [h("tr", row) for row in header_rows])
|
|
905
|
-
table_body = h("tbody", [h("tr", row) for row in body_rows])
|
|
906
|
-
summary = str(h("table", [table_header, table_body]))
|
|
881
|
+
intro = self._generate_pr_environment_summary_intro()
|
|
882
|
+
summary = intro + self._generate_pr_environment_summary_list(prod_plan)
|
|
883
|
+
if prod_plan.user_provided_flags:
|
|
884
|
+
summary += self._generate_plan_flags_section(prod_plan.user_provided_flags)
|
|
885
|
+
|
|
907
886
|
vde_title = (
|
|
908
887
|
"- :eyes: To **review** this PR's changes, use virtual data environment:"
|
|
909
888
|
)
|
|
@@ -943,7 +922,11 @@ class GithubController:
|
|
|
943
922
|
"If you would like the bot to automatically categorize changes, check the [documentation](https://sqlmesh.readthedocs.io/en/stable/integrations/github/) for more information."
|
|
944
923
|
)
|
|
945
924
|
elif isinstance(exception, PlanError):
|
|
946
|
-
failure_msg = f"Plan application failed.\n
|
|
925
|
+
failure_msg = f"Plan application failed.\n"
|
|
926
|
+
if exception.args and (msg := exception.args[0]) and isinstance(msg, str):
|
|
927
|
+
failure_msg += f"\n{msg}\n"
|
|
928
|
+
if self._console.captured_output:
|
|
929
|
+
failure_msg += f"\n{self._console.captured_output}"
|
|
947
930
|
elif isinstance(exception, (SQLMeshError, SqlglotError, ValueError)):
|
|
948
931
|
# this logic is taken from the global error handler attached to the CLI, which uses `click.echo()` to output the message
|
|
949
932
|
# so cant be re-used here because it bypasses the Console
|
|
@@ -959,13 +942,17 @@ class GithubController:
|
|
|
959
942
|
|
|
960
943
|
conclusion_to_summary = {
|
|
961
944
|
GithubCheckConclusion.SKIPPED: f":next_track_button: Skipped creating or updating PR Environment `{self.pr_environment_name}`. {skip_reason}",
|
|
962
|
-
GithubCheckConclusion.FAILURE: f":x: Failed to create or update PR Environment `{self.pr_environment_name}
|
|
945
|
+
GithubCheckConclusion.FAILURE: f":x: Failed to create or update PR Environment `{self.pr_environment_name}` :x:\n\n{failure_msg}",
|
|
963
946
|
GithubCheckConclusion.CANCELLED: f":stop_sign: Cancelled creating or updating PR Environment `{self.pr_environment_name}`",
|
|
964
947
|
GithubCheckConclusion.ACTION_REQUIRED: f":warning: Action Required to create or update PR Environment `{self.pr_environment_name}` :warning:\n\n{failure_msg}",
|
|
965
948
|
}
|
|
966
949
|
summary = conclusion_to_summary.get(
|
|
967
950
|
conclusion, f":interrobang: Got an unexpected conclusion: {conclusion.value}"
|
|
968
951
|
)
|
|
952
|
+
if plan_flags:
|
|
953
|
+
plan_flags_section = self._generate_plan_flags_section(plan_flags)
|
|
954
|
+
summary += f"\n\n{plan_flags_section}"
|
|
955
|
+
|
|
969
956
|
self._append_output("pr_environment_name", self.pr_environment_name)
|
|
970
957
|
return conclusion, check_title, summary
|
|
971
958
|
|
|
@@ -1006,6 +993,12 @@ class GithubController:
|
|
|
1006
993
|
title = conclusion_to_title.get(
|
|
1007
994
|
conclusion, f"Got an unexpected conclusion: {conclusion.value}"
|
|
1008
995
|
)
|
|
996
|
+
if conclusion == GithubCheckConclusion.SUCCESS and summary:
|
|
997
|
+
summary = (
|
|
998
|
+
f"This is a preview that shows the differences between this PR environment `{self.pr_environment_name}` and `prod`.\n\n"
|
|
999
|
+
"These are the changes that would be deployed.\n\n"
|
|
1000
|
+
) + summary
|
|
1001
|
+
|
|
1009
1002
|
return conclusion, title, summary
|
|
1010
1003
|
|
|
1011
1004
|
self._update_check_handler(
|
|
@@ -1115,3 +1108,232 @@ class GithubController:
|
|
|
1115
1108
|
@property
|
|
1116
1109
|
def running_in_github_actions(self) -> bool:
|
|
1117
1110
|
return os.environ.get("GITHUB_ACTIONS", None) == "true"
|
|
1111
|
+
|
|
1112
|
+
@property
|
|
1113
|
+
def version_info(self) -> str:
|
|
1114
|
+
from sqlmesh.cli.main import _sqlmesh_version
|
|
1115
|
+
|
|
1116
|
+
return _sqlmesh_version()
|
|
1117
|
+
|
|
1118
|
+
def _generate_plan_flags_section(
|
|
1119
|
+
self, user_provided_flags: t.Dict[str, UserProvidedFlags]
|
|
1120
|
+
) -> str:
|
|
1121
|
+
# collapsed section syntax:
|
|
1122
|
+
# https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections#creating-a-collapsed-section
|
|
1123
|
+
section = "<details>\n\n<summary>Plan flags</summary>\n\n"
|
|
1124
|
+
for flag_name, flag_value in user_provided_flags.items():
|
|
1125
|
+
section += f"- `{flag_name}` = `{flag_value}`\n"
|
|
1126
|
+
section += "\n</details>"
|
|
1127
|
+
|
|
1128
|
+
return section
|
|
1129
|
+
|
|
1130
|
+
def _generate_pr_environment_summary_intro(self) -> str:
|
|
1131
|
+
note = ""
|
|
1132
|
+
subset_reasons = []
|
|
1133
|
+
|
|
1134
|
+
if self.bot_config.skip_pr_backfill:
|
|
1135
|
+
subset_reasons.append("`skip_pr_backfill` is enabled")
|
|
1136
|
+
|
|
1137
|
+
if default_pr_start := self.bot_config.default_pr_start:
|
|
1138
|
+
subset_reasons.append(f"`default_pr_start` is set to `{default_pr_start}`")
|
|
1139
|
+
|
|
1140
|
+
if subset_reasons:
|
|
1141
|
+
note = (
|
|
1142
|
+
"> [!IMPORTANT]\n"
|
|
1143
|
+
f"> This PR environment may only contain a subset of data because:\n"
|
|
1144
|
+
+ "\n".join(f"> - {r}" for r in subset_reasons)
|
|
1145
|
+
+ "\n"
|
|
1146
|
+
"> \n"
|
|
1147
|
+
"> This means that deploying to `prod` may not be a simple virtual update if there is still some data to load.\n"
|
|
1148
|
+
"> See `Dates not loaded in PR` below or the `Prod Plan Preview` check for more information.\n\n"
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
return (
|
|
1152
|
+
f"Here is a summary of data that has been loaded into the PR environment `{self.pr_environment_name}` and could be deployed to `prod`.\n\n"
|
|
1153
|
+
+ note
|
|
1154
|
+
)
|
|
1155
|
+
|
|
1156
|
+
def _generate_pr_environment_summary_list(self, plan: Plan) -> str:
|
|
1157
|
+
added_snapshot_ids = set(plan.context_diff.added)
|
|
1158
|
+
modified_snapshot_ids = set(
|
|
1159
|
+
s.snapshot_id for s, _ in plan.context_diff.modified_snapshots.values()
|
|
1160
|
+
)
|
|
1161
|
+
removed_snapshot_ids = set(plan.context_diff.removed_snapshots.keys())
|
|
1162
|
+
|
|
1163
|
+
# note: we sort these to get a deterministic order for the output tests
|
|
1164
|
+
table_records = sorted(
|
|
1165
|
+
[
|
|
1166
|
+
SnapshotSummaryRecord(snapshot_id=snapshot_id, plan=plan)
|
|
1167
|
+
for snapshot_id in (
|
|
1168
|
+
added_snapshot_ids | modified_snapshot_ids | removed_snapshot_ids
|
|
1169
|
+
)
|
|
1170
|
+
],
|
|
1171
|
+
key=lambda r: r.display_name,
|
|
1172
|
+
)
|
|
1173
|
+
|
|
1174
|
+
sections = [
|
|
1175
|
+
("### Added", [r for r in table_records if r.is_added]),
|
|
1176
|
+
("### Removed", [r for r in table_records if r.is_removed]),
|
|
1177
|
+
("### Directly Modified", [r for r in table_records if r.is_directly_modified]),
|
|
1178
|
+
("### Indirectly Modified", [r for r in table_records if r.is_indirectly_modified]),
|
|
1179
|
+
(
|
|
1180
|
+
"### Metadata Updated",
|
|
1181
|
+
[r for r in table_records if r.is_metadata_updated and not r.is_modified],
|
|
1182
|
+
),
|
|
1183
|
+
]
|
|
1184
|
+
|
|
1185
|
+
summary = ""
|
|
1186
|
+
for title, records in sections:
|
|
1187
|
+
if records:
|
|
1188
|
+
summary += f"\n{title}\n"
|
|
1189
|
+
|
|
1190
|
+
for record in records:
|
|
1191
|
+
summary += f"{record.as_markdown_list_item}\n"
|
|
1192
|
+
|
|
1193
|
+
return summary
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
@dataclass
|
|
1197
|
+
class SnapshotSummaryRecord:
|
|
1198
|
+
snapshot_id: SnapshotId
|
|
1199
|
+
plan: Plan
|
|
1200
|
+
|
|
1201
|
+
@property
|
|
1202
|
+
def snapshot(self) -> Snapshot:
|
|
1203
|
+
if self.is_removed:
|
|
1204
|
+
raise ValueError("Removed snapshots only have SnapshotTableInfo available")
|
|
1205
|
+
return self.plan.snapshots[self.snapshot_id]
|
|
1206
|
+
|
|
1207
|
+
@cached_property
|
|
1208
|
+
def snapshot_table_info(self) -> SnapshotTableInfo:
|
|
1209
|
+
if self.is_removed:
|
|
1210
|
+
return self.plan.modified_snapshots[self.snapshot_id].table_info
|
|
1211
|
+
return self.plan.snapshots[self.snapshot_id].table_info
|
|
1212
|
+
|
|
1213
|
+
@property
|
|
1214
|
+
def display_name(self) -> str:
|
|
1215
|
+
dialect = None if self.is_removed else self.snapshot.node.dialect
|
|
1216
|
+
return self.snapshot_table_info.display_name(
|
|
1217
|
+
self.plan.environment_naming_info, default_catalog=None, dialect=dialect
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
@property
|
|
1221
|
+
def change_category(self) -> str:
|
|
1222
|
+
if self.is_removed:
|
|
1223
|
+
return SNAPSHOT_CHANGE_CATEGORY_STR[SnapshotChangeCategory.BREAKING]
|
|
1224
|
+
|
|
1225
|
+
if change_category := self.snapshot.change_category:
|
|
1226
|
+
return SNAPSHOT_CHANGE_CATEGORY_STR[change_category]
|
|
1227
|
+
|
|
1228
|
+
return "Uncategorized"
|
|
1229
|
+
|
|
1230
|
+
@property
|
|
1231
|
+
def is_added(self) -> bool:
|
|
1232
|
+
return self.snapshot_id in self.plan.context_diff.added
|
|
1233
|
+
|
|
1234
|
+
@property
|
|
1235
|
+
def is_removed(self) -> bool:
|
|
1236
|
+
return self.snapshot_id in self.plan.context_diff.removed_snapshots
|
|
1237
|
+
|
|
1238
|
+
@property
|
|
1239
|
+
def is_dev_preview(self) -> bool:
|
|
1240
|
+
return not self.plan.deployability_index.is_deployable(self.snapshot_id)
|
|
1241
|
+
|
|
1242
|
+
@property
|
|
1243
|
+
def is_directly_modified(self) -> bool:
|
|
1244
|
+
return self.plan.context_diff.directly_modified(self.snapshot_table_info.name)
|
|
1245
|
+
|
|
1246
|
+
@property
|
|
1247
|
+
def is_indirectly_modified(self) -> bool:
|
|
1248
|
+
return self.plan.context_diff.indirectly_modified(self.snapshot_table_info.name)
|
|
1249
|
+
|
|
1250
|
+
@property
|
|
1251
|
+
def is_modified(self) -> bool:
|
|
1252
|
+
return self.is_directly_modified or self.is_indirectly_modified
|
|
1253
|
+
|
|
1254
|
+
@property
|
|
1255
|
+
def is_metadata_updated(self) -> bool:
|
|
1256
|
+
return self.plan.context_diff.metadata_updated(self.snapshot_table_info.name)
|
|
1257
|
+
|
|
1258
|
+
@property
|
|
1259
|
+
def is_incremental(self) -> bool:
|
|
1260
|
+
return self.snapshot_table_info.is_incremental
|
|
1261
|
+
|
|
1262
|
+
@property
|
|
1263
|
+
def modification_type(self) -> str:
|
|
1264
|
+
if self.is_directly_modified:
|
|
1265
|
+
return "Directly modified"
|
|
1266
|
+
if self.is_indirectly_modified:
|
|
1267
|
+
return "Indirectly modified"
|
|
1268
|
+
if self.is_metadata_updated:
|
|
1269
|
+
return "Metadata updated"
|
|
1270
|
+
|
|
1271
|
+
return "Unknown"
|
|
1272
|
+
|
|
1273
|
+
@property
|
|
1274
|
+
def loaded_intervals(self) -> SnapshotIntervals:
|
|
1275
|
+
if self.is_removed:
|
|
1276
|
+
raise ValueError("Removed snapshots dont have loaded intervals available")
|
|
1277
|
+
|
|
1278
|
+
return SnapshotIntervals(
|
|
1279
|
+
snapshot_id=self.snapshot_id,
|
|
1280
|
+
intervals=(
|
|
1281
|
+
self.snapshot.dev_intervals
|
|
1282
|
+
if self.snapshot.is_forward_only
|
|
1283
|
+
else self.snapshot.intervals
|
|
1284
|
+
),
|
|
1285
|
+
)
|
|
1286
|
+
|
|
1287
|
+
@property
|
|
1288
|
+
def loaded_intervals_rendered(self) -> str:
|
|
1289
|
+
if self.is_removed:
|
|
1290
|
+
return "REMOVED"
|
|
1291
|
+
|
|
1292
|
+
return self._format_intervals(self.loaded_intervals)
|
|
1293
|
+
|
|
1294
|
+
@property
|
|
1295
|
+
def missing_intervals(self) -> t.Optional[SnapshotIntervals]:
|
|
1296
|
+
return next(
|
|
1297
|
+
(si for si in self.plan.missing_intervals if si.snapshot_id == self.snapshot_id),
|
|
1298
|
+
None,
|
|
1299
|
+
)
|
|
1300
|
+
|
|
1301
|
+
@property
|
|
1302
|
+
def missing_intervals_formatted(self) -> str:
|
|
1303
|
+
if not self.is_removed and (intervals := self.missing_intervals):
|
|
1304
|
+
return self._format_intervals(intervals)
|
|
1305
|
+
|
|
1306
|
+
return "N/A"
|
|
1307
|
+
|
|
1308
|
+
@property
|
|
1309
|
+
def as_markdown_list_item(self) -> str:
|
|
1310
|
+
if self.is_removed:
|
|
1311
|
+
return f"- `{self.display_name}` ({self.change_category})"
|
|
1312
|
+
|
|
1313
|
+
how_applied = ""
|
|
1314
|
+
|
|
1315
|
+
if not self.is_incremental:
|
|
1316
|
+
from sqlmesh.core.console import _format_missing_intervals
|
|
1317
|
+
|
|
1318
|
+
# note: this is to re-use the '[recreate view]' and '[full refresh]' text and keep it in sync with updates to the CLI
|
|
1319
|
+
# it doesnt actually use the passed intervals, those are handled differently
|
|
1320
|
+
how_applied = _format_missing_intervals(self.snapshot, self.loaded_intervals)
|
|
1321
|
+
|
|
1322
|
+
how_applied_str = f" [{how_applied}]" if how_applied else ""
|
|
1323
|
+
|
|
1324
|
+
item = f"- `{self.display_name}` ({self.change_category})\n"
|
|
1325
|
+
|
|
1326
|
+
if self.snapshot_table_info.model_kind_name:
|
|
1327
|
+
item += f" **Kind:** {self.snapshot_table_info.model_kind_name}{how_applied_str}\n"
|
|
1328
|
+
|
|
1329
|
+
if self.is_incremental:
|
|
1330
|
+
# in-depth interval info is only relevant for incremental models
|
|
1331
|
+
item += f" **Dates loaded in PR:** [{self.loaded_intervals_rendered}]\n"
|
|
1332
|
+
if self.missing_intervals:
|
|
1333
|
+
item += f" **Dates *not* loaded in PR:** [{self.missing_intervals_formatted}]\n"
|
|
1334
|
+
|
|
1335
|
+
return item
|
|
1336
|
+
|
|
1337
|
+
def _format_intervals(self, intervals: SnapshotIntervals) -> str:
|
|
1338
|
+
preview_modifier = " (**preview**)" if self.is_dev_preview else ""
|
|
1339
|
+
return f"{intervals.format_intervals(self.snapshot.node.interval_unit)}{preview_modifier}"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
sqlmesh/__init__.py,sha256=QjwaamJIdW7voXoS880yAx7tHm-Im4_mqGA-XJgafos,7631
|
|
2
|
-
sqlmesh/_version.py,sha256=
|
|
2
|
+
sqlmesh/_version.py,sha256=xr44hQ2o7jmOZ347ejlZNhnmweEDkJf_mQ6dquBDsDM,528
|
|
3
3
|
sqlmesh/magics.py,sha256=R9U0Mp9ZQhDYe751nxAQDLaMv3zLAR4HcTdIflKTmEE,41709
|
|
4
4
|
sqlmesh/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
sqlmesh/cicd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -11,7 +11,7 @@ sqlmesh/cli/options.py,sha256=-4ZclMIeIg8tghLowtmQNmpEFlm0QtOVEh40NqRdFHA,2499
|
|
|
11
11
|
sqlmesh/cli/project_init.py,sha256=5JmDmUK0WJs39hM-p76uzHYdo1Hnx6fKuJg_pR3Hn5A,16419
|
|
12
12
|
sqlmesh/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
sqlmesh/core/_typing.py,sha256=e1f7xO2aZCy38M_3VoDy-XI3shyd99-IFctgIFSgXWM,497
|
|
14
|
-
sqlmesh/core/console.py,sha256=
|
|
14
|
+
sqlmesh/core/console.py,sha256=ieSXS0G--HpVgziwAqgcJvE4zUn60xRswljpc1GO6pU,160753
|
|
15
15
|
sqlmesh/core/constants.py,sha256=gUABXWYgH_XOSbaZAJsR5o1dmA61JDCvBDyxqqXX3LM,2630
|
|
16
16
|
sqlmesh/core/context.py,sha256=K9bQqk5ATZ0cl7v7qaiOQq-b7hYfPIgdo9G9v51Y6y8,119111
|
|
17
17
|
sqlmesh/core/context_diff.py,sha256=CeUmMBJzN__98YohrtAiIYPIZI9mrGBk6bVADWBLUw8,21692
|
|
@@ -162,9 +162,9 @@ sqlmesh/integrations/llm.py,sha256=URgp3JKr06HYv6hGlJ7DQ9m3QCc41xRWK23HzmGpbDI,1
|
|
|
162
162
|
sqlmesh/integrations/slack.py,sha256=nxLxu5WztGbZH3JdqnzyPqmJUMfRdJ_49LQ7zR-u39Q,6801
|
|
163
163
|
sqlmesh/integrations/github/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
164
164
|
sqlmesh/integrations/github/cicd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
165
|
-
sqlmesh/integrations/github/cicd/command.py,sha256=
|
|
165
|
+
sqlmesh/integrations/github/cicd/command.py,sha256=neBs7BfuywRR_AnB8pXHMlEmgFe2-WPJka_KsE9YzkY,12097
|
|
166
166
|
sqlmesh/integrations/github/cicd/config.py,sha256=tRhMZaLf1p6AWXvi67n85Q-lptJNGt1Wk_Jr61ytpiM,2104
|
|
167
|
-
sqlmesh/integrations/github/cicd/controller.py,sha256=
|
|
167
|
+
sqlmesh/integrations/github/cicd/controller.py,sha256=I8jnpS2hvYwa-dpki0Cly246eeBM9-9Qy9loxw4bMpI,52284
|
|
168
168
|
sqlmesh/lsp/api.py,sha256=_lA_buSPZFIoLJuINbS53LbNOOaHp3orb0uFTE3aCqI,2328
|
|
169
169
|
sqlmesh/lsp/completions.py,sha256=Nt1XpFL_-QAIRM8BBBC6Ia6YG0hMZriiNJra7nGfD4Q,6676
|
|
170
170
|
sqlmesh/lsp/context.py,sha256=2aD_lH-GqAwCjTFHfCavH2fZz2T4IXQZVS7LDMAa2X4,12815
|
|
@@ -284,7 +284,7 @@ sqlmesh/utils/pydantic.py,sha256=o_NsXbIpDqNpUA1Uc5xF0ZzoXQYYB0DfHwdRxBwXPNk,120
|
|
|
284
284
|
sqlmesh/utils/rich.py,sha256=BpB47G0-JcU41LkGkeGKroexj4NOV5fox4-GOIXPgIc,3545
|
|
285
285
|
sqlmesh/utils/windows.py,sha256=r7Yin4WEwbvH7sKA3hOJtRsclmscNuBeCklCfVPvtQs,611
|
|
286
286
|
sqlmesh/utils/yaml.py,sha256=hvo5lmybYLcIx4p6CQwufS8X4MbwS60lEBnCmYJCXmU,2336
|
|
287
|
-
sqlmesh-0.199.1.
|
|
287
|
+
sqlmesh-0.199.1.dev2.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
|
|
288
288
|
web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
289
289
|
web/client/dist/index.html,sha256=kNpFjWxt34Pf-P-cROtPWi3GNSVk6zqA3eoN9tlZXI0,5472
|
|
290
290
|
web/client/dist/assets/Audits-DKgXravk.js,sha256=SaVQhNlislD_yQc_KivO7SPi1JH0ts1GnDPD5Zssutw,1041
|
|
@@ -402,8 +402,8 @@ web/server/api/endpoints/models.py,sha256=ud-lRwriyI_OYg9tZchrBgeiXELiRTLNEodraf
|
|
|
402
402
|
web/server/api/endpoints/modules.py,sha256=8hqqgonGay_mJmpCw0IdbjsPhWlQH2VLdKAqha-myac,468
|
|
403
403
|
web/server/api/endpoints/plan.py,sha256=bbbY50W_2MsZSTxOHWMKz0tbIm75nsRSlPy8GI2fg9Q,9306
|
|
404
404
|
web/server/api/endpoints/table_diff.py,sha256=SCy3-pr5x6bEEiOaZGCDbiHQhpgxwmnz3Acqa2Ie6jI,2090
|
|
405
|
-
sqlmesh-0.199.1.
|
|
406
|
-
sqlmesh-0.199.1.
|
|
407
|
-
sqlmesh-0.199.1.
|
|
408
|
-
sqlmesh-0.199.1.
|
|
409
|
-
sqlmesh-0.199.1.
|
|
405
|
+
sqlmesh-0.199.1.dev2.dist-info/METADATA,sha256=EC4u5rjn5cBDWonMr78Q0TCJHLbRJ4vZUx3Ay9ui6lo,26658
|
|
406
|
+
sqlmesh-0.199.1.dev2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
407
|
+
sqlmesh-0.199.1.dev2.dist-info/entry_points.txt,sha256=pIYKtg-u-uO7BK_Y0qrdmfyj0bYrcMc3Xiqr5WE3VFk,121
|
|
408
|
+
sqlmesh-0.199.1.dev2.dist-info/top_level.txt,sha256=bspjo5vjELQkuN0qKuTFIbsrUt3Zeo8D2mlKeHG1LdE,12
|
|
409
|
+
sqlmesh-0.199.1.dev2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|