sqlmesh 0.199.0__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 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.0'
21
- __version_tuple__ = version_tuple = (0, 199, 0)
20
+ __version__ = version = '0.199.1.dev2'
21
+ __version_tuple__ = version_tuple = (0, 199, 1, 'dev2')
@@ -120,6 +120,7 @@ class Config(BaseConfig):
120
120
  disable_anonymized_analytics: Whether to disable the anonymized analytics collection.
121
121
  before_all: SQL statements or macros to be executed at the start of the `sqlmesh plan` and `sqlmesh run` commands.
122
122
  after_all: SQL statements or macros to be executed at the end of the `sqlmesh plan` and `sqlmesh run` commands.
123
+ cache_dir: The directory to store the SQLMesh cache. Defaults to .cache in the project folder.
123
124
  """
124
125
 
125
126
  gateways: GatewayDict = {"": GatewayConfig()}
@@ -165,6 +166,7 @@ class Config(BaseConfig):
165
166
  after_all: t.Optional[t.List[str]] = None
166
167
  linter: LinterConfig = LinterConfig()
167
168
  janitor: JanitorConfig = JanitorConfig()
169
+ cache_dir: t.Optional[str] = None
168
170
 
169
171
  _FIELD_UPDATE_STRATEGY: t.ClassVar[t.Dict[str, UpdateStrategy]] = {
170
172
  "gateways": UpdateStrategy.NESTED_UPDATE,
@@ -105,7 +105,7 @@ class _EngineAdapterStateSyncSchedulerConfig(SchedulerConfig):
105
105
 
106
106
  schema = context.config.get_state_schema(context.gateway)
107
107
  return EngineAdapterStateSync(
108
- engine_adapter, schema=schema, context_path=context.path, console=context.console
108
+ engine_adapter, schema=schema, cache_dir=context.cache_dir, console=context.console
109
109
  )
110
110
 
111
111
  def state_sync_fingerprint(self, context: GenericContext) -> str:
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"- `{snapshot.display_name(environment_naming_info, default_catalog if self.verbosity < Verbosity.VERY_VERBOSE else None, dialect=self.dialect)}`"
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
- self._print(f"```diff\n{context_diff.text_diff(snapshot.name)}\n```")
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(
sqlmesh/core/context.py CHANGED
@@ -504,7 +504,11 @@ class GenericContext(BaseContext, t.Generic[C]):
504
504
  }
505
505
  )
506
506
 
507
- update_model_schemas(self.dag, models=self._models, context_path=self.path)
507
+ update_model_schemas(
508
+ self.dag,
509
+ models=self._models,
510
+ cache_dir=self.cache_dir,
511
+ )
508
512
 
509
513
  if model.dialect:
510
514
  self._all_dialects.add(model.dialect)
@@ -640,7 +644,11 @@ class GenericContext(BaseContext, t.Generic[C]):
640
644
  self._models.update({fqn: model.copy(update={"mapping_schema": {}})})
641
645
  continue
642
646
 
643
- update_model_schemas(self.dag, models=self._models, context_path=self.path)
647
+ update_model_schemas(
648
+ self.dag,
649
+ models=self._models,
650
+ cache_dir=self.cache_dir,
651
+ )
644
652
 
645
653
  models = self.models.values()
646
654
  for model in models:
@@ -2439,6 +2447,9 @@ class GenericContext(BaseContext, t.Generic[C]):
2439
2447
  cache_path = path / c.CACHE
2440
2448
  if cache_path.exists():
2441
2449
  rmtree(cache_path)
2450
+ if self.cache_dir.exists():
2451
+ rmtree(self.cache_dir)
2452
+
2442
2453
  if isinstance(self.state_sync, CachingStateSync):
2443
2454
  self.state_sync.clear_cache()
2444
2455
 
@@ -2538,6 +2549,17 @@ class GenericContext(BaseContext, t.Generic[C]):
2538
2549
  for fqn, snapshot in self.snapshots.items()
2539
2550
  }
2540
2551
 
2552
+ @cached_property
2553
+ def cache_dir(self) -> Path:
2554
+ if self.config.cache_dir:
2555
+ cache_path = Path(self.config.cache_dir)
2556
+ if cache_path.is_absolute():
2557
+ return cache_path
2558
+ return self.path / cache_path
2559
+
2560
+ # Default to .cache directory in the project path
2561
+ return self.path / c.CACHE
2562
+
2541
2563
  @cached_property
2542
2564
  def engine_adapters(self) -> t.Dict[str, EngineAdapter]:
2543
2565
  """Returns all the engine adapters for the gateways defined in the configuration."""
@@ -2735,6 +2757,7 @@ class GenericContext(BaseContext, t.Generic[C]):
2735
2757
  dag=dag,
2736
2758
  default_catalog=self.default_catalog,
2737
2759
  dialect=self.default_dialect,
2760
+ cache_dir=self.cache_dir,
2738
2761
  )
2739
2762
 
2740
2763
  def _register_notification_targets(self) -> None:
sqlmesh/core/loader.py CHANGED
@@ -887,7 +887,7 @@ class SqlMeshLoader(Loader):
887
887
  def __init__(self, loader: SqlMeshLoader, config_path: Path):
888
888
  self._loader = loader
889
889
  self.config_path = config_path
890
- self._model_cache = ModelCache(self.config_path / c.CACHE)
890
+ self._model_cache = ModelCache(self._loader.context.cache_dir)
891
891
 
892
892
  def get_or_load_models(
893
893
  self, target_path: Path, loader: t.Callable[[], t.List[Model]]
@@ -7,7 +7,6 @@ from pathlib import Path
7
7
  from sqlglot.errors import SchemaError
8
8
  from sqlglot.schema import MappingSchema
9
9
 
10
- from sqlmesh.core import constants as c
11
10
  from sqlmesh.core.model.cache import (
12
11
  load_optimized_query_and_mapping,
13
12
  optimized_query_cache_pool,
@@ -23,10 +22,10 @@ if t.TYPE_CHECKING:
23
22
  def update_model_schemas(
24
23
  dag: DAG[str],
25
24
  models: UniqueKeyDict[str, Model],
26
- context_path: Path,
25
+ cache_dir: Path,
27
26
  ) -> None:
28
27
  schema = MappingSchema(normalize=False)
29
- optimized_query_cache: OptimizedQueryCache = OptimizedQueryCache(context_path / c.CACHE)
28
+ optimized_query_cache: OptimizedQueryCache = OptimizedQueryCache(cache_dir)
30
29
 
31
30
  _update_model_schemas(dag, models, schema, optimized_query_cache)
32
31
 
sqlmesh/core/selector.py CHANGED
@@ -10,6 +10,7 @@ from sqlglot.tokens import Token, TokenType, Tokenizer as BaseTokenizer
10
10
  from sqlglot.dialects.dialect import Dialect, DialectType
11
11
  from sqlglot.helper import seq_get
12
12
 
13
+ from sqlmesh.core import constants as c
13
14
  from sqlmesh.core.dialect import normalize_model_name
14
15
  from sqlmesh.core.environment import Environment
15
16
  from sqlmesh.core.model import update_model_schemas
@@ -34,10 +35,12 @@ class Selector:
34
35
  dag: t.Optional[DAG[str]] = None,
35
36
  default_catalog: t.Optional[str] = None,
36
37
  dialect: t.Optional[str] = None,
38
+ cache_dir: t.Optional[Path] = None,
37
39
  ):
38
40
  self._state_reader = state_reader
39
41
  self._models = models
40
42
  self._context_path = context_path
43
+ self._cache_dir = cache_dir if cache_dir else context_path / c.CACHE
41
44
  self._default_catalog = default_catalog
42
45
  self._dialect = dialect
43
46
  self._git_client = GitClient(context_path)
@@ -157,7 +160,7 @@ class Selector:
157
160
  models[model.fqn] = model
158
161
 
159
162
  if needs_update:
160
- update_model_schemas(dag, models=models, context_path=self._context_path)
163
+ update_model_schemas(dag, models=models, cache_dir=self._cache_dir)
161
164
 
162
165
  return models
163
166
 
@@ -79,7 +79,7 @@ class EngineAdapterStateSync(StateSync):
79
79
  engine_adapter: The EngineAdapter to use to store and fetch snapshots.
80
80
  schema: The schema to store state metadata in. If None or empty string then no schema is defined
81
81
  console: The console to log information to.
82
- context_path: The context path, used for caching snapshot models.
82
+ cache_dir: The cache path, used for caching snapshot models.
83
83
  """
84
84
 
85
85
  def __init__(
@@ -87,14 +87,12 @@ class EngineAdapterStateSync(StateSync):
87
87
  engine_adapter: EngineAdapter,
88
88
  schema: t.Optional[str],
89
89
  console: t.Optional[Console] = None,
90
- context_path: Path = Path(),
90
+ cache_dir: Path = Path(),
91
91
  ):
92
92
  self.plan_dags_table = exp.table_("_plan_dags", db=schema)
93
93
  self.interval_state = IntervalState(engine_adapter, schema=schema)
94
94
  self.environment_state = EnvironmentState(engine_adapter, schema=schema)
95
- self.snapshot_state = SnapshotState(
96
- engine_adapter, schema=schema, context_path=context_path
97
- )
95
+ self.snapshot_state = SnapshotState(engine_adapter, schema=schema, cache_dir=cache_dir)
98
96
  self.version_state = VersionState(engine_adapter, schema=schema)
99
97
  self.migrator = StateMigrator(
100
98
  engine_adapter,
@@ -8,7 +8,6 @@ from collections import defaultdict
8
8
  from sqlglot import exp
9
9
  from pydantic import Field
10
10
 
11
- from sqlmesh.core import constants as c
12
11
  from sqlmesh.core.engine_adapter import EngineAdapter
13
12
  from sqlmesh.core.state_sync.db.utils import (
14
13
  snapshot_name_version_filter,
@@ -53,7 +52,7 @@ class SnapshotState:
53
52
  self,
54
53
  engine_adapter: EngineAdapter,
55
54
  schema: t.Optional[str] = None,
56
- context_path: Path = Path(),
55
+ cache_dir: Path = Path(),
57
56
  ):
58
57
  self.engine_adapter = engine_adapter
59
58
  self.snapshots_table = exp.table_("_snapshots", db=schema)
@@ -79,7 +78,7 @@ class SnapshotState:
79
78
  "next_auto_restatement_ts": exp.DataType.build("bigint"),
80
79
  }
81
80
 
82
- self._snapshot_cache = SnapshotCache(context_path / c.CACHE)
81
+ self._snapshot_cache = SnapshotCache(cache_dir)
83
82
 
84
83
  def push_snapshots(self, snapshots: t.Iterable[Snapshot], overwrite: bool = False) -> None:
85
84
  """Pushes snapshots to the state store.
sqlmesh/dbt/loader.py CHANGED
@@ -5,7 +5,6 @@ import sys
5
5
  import typing as t
6
6
  import sqlmesh.core.dialect as d
7
7
  from pathlib import Path
8
- from sqlmesh.core import constants as c
9
8
  from sqlmesh.core.config import (
10
9
  Config,
11
10
  ConnectionConfig,
@@ -330,8 +329,8 @@ class DbtLoader(Loader):
330
329
  self._yaml_max_mtimes = yaml_max_mtimes
331
330
 
332
331
  target = t.cast(TargetConfig, project.context.target)
333
- cache_path = loader.config_path / c.CACHE / target.name
334
- self._model_cache = ModelCache(cache_path)
332
+ cache_dir = loader.context.cache_dir / target.name
333
+ self._model_cache = ModelCache(cache_dir)
335
334
 
336
335
  def get_or_load_models(
337
336
  self, target_path: Path, loader: t.Callable[[], t.List[Model]]
sqlmesh/dbt/manifest.py CHANGED
@@ -77,6 +77,7 @@ class ManifestHelper:
77
77
  profile_name: str,
78
78
  target: TargetConfig,
79
79
  variable_overrides: t.Optional[t.Dict[str, t.Any]] = None,
80
+ cache_dir: t.Optional[str] = None,
80
81
  ):
81
82
  self.project_path = project_path
82
83
  self.profiles_path = profiles_path
@@ -99,8 +100,16 @@ class ManifestHelper:
99
100
  self._tests_by_owner: t.Dict[str, t.List[TestConfig]] = defaultdict(list)
100
101
  self._disabled_refs: t.Optional[t.Set[str]] = None
101
102
  self._disabled_sources: t.Optional[t.Set[str]] = None
103
+
104
+ if cache_dir is not None:
105
+ cache_path = Path(cache_dir)
106
+ if not cache_path.is_absolute():
107
+ cache_path = self.project_path / cache_path
108
+ else:
109
+ cache_path = self.project_path / c.CACHE
110
+
102
111
  self._call_cache: FileCache[t.Dict[str, t.List[CallNames]]] = FileCache(
103
- self.project_path / c.CACHE, "jinja_calls"
112
+ cache_path, "jinja_calls"
104
113
  )
105
114
 
106
115
  self._on_run_start_per_package: t.Dict[str, HookConfigs] = defaultdict(dict)
sqlmesh/dbt/project.py CHANGED
@@ -75,6 +75,7 @@ class Project:
75
75
  profile_name,
76
76
  target=profile.target,
77
77
  variable_overrides=variable_overrides,
78
+ cache_dir=context.sqlmesh_config.cache_dir,
78
79
  )
79
80
 
80
81
  extra_fields = profile.target.extra
@@ -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, exception=e, plan=controller.pr_plan_or_none
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
- return f"{difference_summary}\n{missing_dates}"
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
- if not self.modified_snapshots:
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
- header_rows = [
859
- h("th", {"colspan": "3"}, "PR Environment Summary"),
860
- [
861
- h("th", "Model"),
862
- h("th", "Change Type"),
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\n{self._console.captured_output}"
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}`.\n\n{failure_msg}",
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlmesh
3
- Version: 0.199.0
3
+ Version: 0.199.1.dev2
4
4
  Summary: Next-generation data transformation framework
5
5
  Author-email: "TobikoData Inc." <engineering@tobikodata.com>
6
6
  License: Apache License
@@ -1,5 +1,5 @@
1
1
  sqlmesh/__init__.py,sha256=QjwaamJIdW7voXoS880yAx7tHm-Im4_mqGA-XJgafos,7631
2
- sqlmesh/_version.py,sha256=qLLyqG094QHsmxpbLJmgbyIBzkMnKjxOvoZCUGHl5bc,515
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,14 +11,14 @@ 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=HInr6MOcxgn8Z-B9fAoghoDXbBEZqkB5ClyZF-fRP58,159032
14
+ sqlmesh/core/console.py,sha256=ieSXS0G--HpVgziwAqgcJvE4zUn60xRswljpc1GO6pU,160753
15
15
  sqlmesh/core/constants.py,sha256=gUABXWYgH_XOSbaZAJsR5o1dmA61JDCvBDyxqqXX3LM,2630
16
- sqlmesh/core/context.py,sha256=WyaHN2Fh3FMMHN5MQNnfq5fm89wkfJujq-dkhccRId0,118534
16
+ sqlmesh/core/context.py,sha256=K9bQqk5ATZ0cl7v7qaiOQq-b7hYfPIgdo9G9v51Y6y8,119111
17
17
  sqlmesh/core/context_diff.py,sha256=CeUmMBJzN__98YohrtAiIYPIZI9mrGBk6bVADWBLUw8,21692
18
18
  sqlmesh/core/dialect.py,sha256=1ykYwb9WZn_nCaX08PcQ0BJh2Pihv9SPvoMNjpMeCfs,50158
19
19
  sqlmesh/core/environment.py,sha256=7DbJX0r5nZRU3LhcgIxbSV-JosQYDjAt3V5Wc2Fpns8,13005
20
20
  sqlmesh/core/lineage.py,sha256=zjB0Zfamo2Fja2r5SSZPMbrEKTXr1WjozZPVqvxdybI,3143
21
- sqlmesh/core/loader.py,sha256=98lSZMe6pJUx1gHkLeMLkkxOG9wgRlIQlK-qvk9DWgc,40530
21
+ sqlmesh/core/loader.py,sha256=Imb65SX5TtvQrtlilin2hkYg12gm9e9c30I8o95nkVE,40534
22
22
  sqlmesh/core/macros.py,sha256=Me4QjgW_K4DzQVi8I3G0XwoC3cXSFFWt3sJyynZ2GOM,60585
23
23
  sqlmesh/core/node.py,sha256=r4_1MRTPcU0MAlwZ5KzrykLT58B7dkKvHzPCsJmEKD8,14497
24
24
  sqlmesh/core/notification_target.py,sha256=PPGoDrgbRKxr27vJEu03XqNTQLYTw0ZF_b0yAapxGeI,16158
@@ -27,7 +27,7 @@ sqlmesh/core/renderer.py,sha256=ksdD2x2Q5Ns5fOLIGfWIznx_T3fXbP3CfwUhD5kVSOQ,2667
27
27
  sqlmesh/core/scheduler.py,sha256=lA3zhpz52HV-QBGoPirtqd2KakivnTuqiqZp09pP8js,39318
28
28
  sqlmesh/core/schema_diff.py,sha256=Sp2MoqAngp-yTYevkLaf9jb26SuAdGXoZ54g5MjWowU,30516
29
29
  sqlmesh/core/schema_loader.py,sha256=lvrI9k7hr1P4X4E6t3U-KrcuvdQuw0UdNlN90P2s52w,3563
30
- sqlmesh/core/selector.py,sha256=_hcktUQiKXCPE-mOqXClHUVWBfEZKMPVVNDtpSSRhbI,12660
30
+ sqlmesh/core/selector.py,sha256=CBuCbaFzidinaoS66o4MY9F-1C3k9YddNMyUv-5Z2i8,12815
31
31
  sqlmesh/core/signal.py,sha256=yVLXewYP6eH0vt-EwaIaI2WwHz6hWsVr-Kb2m4kP1FU,1353
32
32
  sqlmesh/core/table_diff.py,sha256=_LhSwAhCctkqWini0n-qeIK-cOPeuGnwlLSuUNM8n2k,29062
33
33
  sqlmesh/core/user.py,sha256=EJ6R4R1iK67n80vBoCCsidF56IR7xEYqiCEO-nrVMso,1660
@@ -52,9 +52,9 @@ sqlmesh/core/config/migration.py,sha256=3lZIlmIcQH3_hIEm3Y3jFtvpw0YXzRzAnk4Odikl
52
52
  sqlmesh/core/config/model.py,sha256=GIz9kAfKD2NEXNzCZCQ17-CK2OtqN6WgeG1fvibcChM,3650
53
53
  sqlmesh/core/config/naming.py,sha256=KVGaxUgswoQajXR_cHFJvgkmEFcU1V182GkOH1SJxuE,326
54
54
  sqlmesh/core/config/plan.py,sha256=wJEDfocMcKSMN0-5gVojjIcmJZ0376LJU3tkGMTm-Zc,1558
55
- sqlmesh/core/config/root.py,sha256=tZxArC4kjPqrt43R9uErH_awwwNQVaNIaTAb-ot9WiU,15529
55
+ sqlmesh/core/config/root.py,sha256=CutuHcOqF1trDiz1VckikMvvaqafcyU8iil11H-w8rk,15670
56
56
  sqlmesh/core/config/run.py,sha256=rIK0QnN_vArrwps2TXGVEbvao2AnGZgzXo7EliJa42E,852
57
- sqlmesh/core/config/scheduler.py,sha256=k3BVMpy5ZrC05ZlyE3vec8ZGfTjUjWyfsKZtYgflbyA,7029
57
+ sqlmesh/core/config/scheduler.py,sha256=LVEHIln7dqv8ygo4uniji4j9-gfuJlk-WpQU9FAqILs,7031
58
58
  sqlmesh/core/config/ui.py,sha256=jsO-S6_d9NkLZGG5pT4mgKgxMF34KzkDociZAMvCX3U,278
59
59
  sqlmesh/core/engine_adapter/__init__.py,sha256=two_WTs4UnSNp-fscJh8vOunJq0Xi37EuDuMjKsKuNE,2281
60
60
  sqlmesh/core/engine_adapter/_typing.py,sha256=_63pYCThfC39UtPVHY3N0H6qJ1YBT32OqwwR3swolGM,1037
@@ -91,7 +91,7 @@ sqlmesh/core/model/decorator.py,sha256=wUUg5t0K559tV9Pqt6FE2EWXt5TPGb4wF2Yg4B4hX
91
91
  sqlmesh/core/model/definition.py,sha256=mG6UPwxiZj0xP_cdHSVELEcXusAcltpgHBX-wiUSuPM,111963
92
92
  sqlmesh/core/model/kind.py,sha256=8Dp8yiTiXltboZWrB2FJ-LQ3zx4Fv_5Tj0KdUIPwCHI,36358
93
93
  sqlmesh/core/model/meta.py,sha256=D9InI9M2-IXeb34vkXNQSLALL8xVXjZ_LXwI-Tqxm7I,21197
94
- sqlmesh/core/model/schema.py,sha256=OClbAeA8LpW-IiSiLT_TlDWkEqmIIvSk0GXq8SWI-GQ,3443
94
+ sqlmesh/core/model/schema.py,sha256=_HMYfzK9wWXh7_CQDIIGnuQUD4aiX3o5D2cRp2sERzc,3387
95
95
  sqlmesh/core/model/seed.py,sha256=a0M-1zY1gOkN5ph2GQyataEdBSCtq50YjeFk2LyvInI,5058
96
96
  sqlmesh/core/plan/__init__.py,sha256=NKSvM7ZBVjw9ho3J65M1wFvG3KURB8PJ0FHHLmtSF44,443
97
97
  sqlmesh/core/plan/builder.py,sha256=g07h-NOsOr_4spnOrlQ4KrFjh3RmbvHSydEuSOsShNc,41416
@@ -111,10 +111,10 @@ sqlmesh/core/state_sync/common.py,sha256=x8Nkox6VmkD-lYZkjRcNe3iVsChR1HBroTorf1k
111
111
  sqlmesh/core/state_sync/export_import.py,sha256=WJ7sFX2b_RPAFE4yspJRi_4JQnaO23xpJgRxwXD0agA,8709
112
112
  sqlmesh/core/state_sync/db/__init__.py,sha256=jGCqKYB7npq4eh6H2LyfBAJWdOP-IFu0DC4CMiQgYvw,107
113
113
  sqlmesh/core/state_sync/db/environment.py,sha256=iTxrdNlKB36S54PUdlqEqlxiAZJtKNExHBtsIbaXT30,13675
114
- sqlmesh/core/state_sync/db/facade.py,sha256=xeJpEgrvztn3Ye2JM9tJOZxozYlEtpAYwJta3kEf3gQ,25302
114
+ sqlmesh/core/state_sync/db/facade.py,sha256=WWditN46A9DXHDcHT7kUZaCfkkjcaDfMC9qO1EfH4Og,25266
115
115
  sqlmesh/core/state_sync/db/interval.py,sha256=qDo5qX7vulibbg9M8mDA0PjoQQJlUCouBJeWuTuLJ6Y,18043
116
116
  sqlmesh/core/state_sync/db/migrator.py,sha256=2LNUwOBNFkA1ntp-Z93S_OWxFQhOBj3ZD_ZqPCyPEAE,18139
117
- sqlmesh/core/state_sync/db/snapshot.py,sha256=7ccdRxni4UGAul-_15z6-I9GAifC94jfNTvAdUEQ9pw,29480
117
+ sqlmesh/core/state_sync/db/snapshot.py,sha256=LV5w3hvwxa-v_ilC1iuQb4iAJDNoi9DH1aNb5RzdSps,29424
118
118
  sqlmesh/core/state_sync/db/utils.py,sha256=204OpT3ISFPCwWP9Aqq8nr6ETl24sbG5uOyo86IJv6E,3819
119
119
  sqlmesh/core/state_sync/db/version.py,sha256=fl5g1tNMYu8O6WTN4J3bciwjGq0c5nZlRDlxxRKGU78,2210
120
120
  sqlmesh/core/test/__init__.py,sha256=e83TJPwPRR_rAG29Y0OVbZb-5oWVBzz-_wrcd22Qk10,418
@@ -130,12 +130,12 @@ sqlmesh/dbt/builtin.py,sha256=p4E1rmSLM0B5PHcFWA9J7FwScR90pIPqiMN8G4M3sBM,15173
130
130
  sqlmesh/dbt/column.py,sha256=Kvj_N3D_QegcoNQG7VqRiqy-FRfupud-6ilhlykBOgE,1873
131
131
  sqlmesh/dbt/common.py,sha256=g4PAL-sEdlnfb_zT-WW3xh-oamyCUoK8dUiDEMVwDWI,7804
132
132
  sqlmesh/dbt/context.py,sha256=dxClM-G1IeMbP0v5yxC_va8QNU-uKghW2dBlSZHJOk0,10331
133
- sqlmesh/dbt/loader.py,sha256=U95hDUGgWgsC6clhMubMO0jfaID6_ivHu7pfUUSt26I,15576
134
- sqlmesh/dbt/manifest.py,sha256=HP2RszGfYZw0VVO3Ifndkxg1PUQpTGvan_tHzMqPqjE,24619
133
+ sqlmesh/dbt/loader.py,sha256=K5UW1RvkOi1uK9vdhAYBi80XmwlAaBwZXflH3P36hXE,15530
134
+ sqlmesh/dbt/manifest.py,sha256=jlafUYdoG7gS2pzvJrt9JfC2afbAMORhBbBUaLl-ZTM,24894
135
135
  sqlmesh/dbt/model.py,sha256=FgQpl8pUNOnihGUo5PYdqOqqOX3XtfDHX3xEJc-CVC4,25782
136
136
  sqlmesh/dbt/package.py,sha256=IBBQueVB4_4C9tIk0-GUL-O2uibNxXu6W2a4AeHHcOU,4322
137
137
  sqlmesh/dbt/profile.py,sha256=Fz2c0dJNN0xBgYEogCIKBTByjNvlA2BVBzOLQCfdEfM,3788
138
- sqlmesh/dbt/project.py,sha256=4PJHPVWIKDd71J5__iICqEp21nFiijKjJkiE5kZIH5Y,3980
138
+ sqlmesh/dbt/project.py,sha256=a21czHhdcm2J80Zllj2q6VvH-W_rDtlXHa8wmvHaLTo,4036
139
139
  sqlmesh/dbt/relation.py,sha256=rgDJQUuENjqq7kpMJJ6AdlgwYmcYjA1oFj6LCYCS-Hg,227
140
140
  sqlmesh/dbt/seed.py,sha256=E7pFhtGwRrC9U_VZJNABwLTa-Nk4OeD-gOtjSpvYmDA,4251
141
141
  sqlmesh/dbt/source.py,sha256=IZK-aOV-KdWIOPuXqRjJZAZ7phW1HUXoHtE-3clgkx4,4125
@@ -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=1tsLOrGibDwoKKgFWf03H8OKOoAW3TWLesXuBRYTtBY,11824
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=QCs51Ag52Eu-2C5B2ivJ3Jgh7yThKj0eBqILGCHqizk,44436
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.0.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
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
@@ -388,7 +388,7 @@ web/server/models.py,sha256=NUI2xqDcav20uuC2BZ9xDL2x0v16-gpVT1Sb-WHfAcg,15518
388
388
  web/server/openapi.py,sha256=NOg__YQg7arVdajQv5CvOA5KLTQCfGZFvwnjARuM6iE,586
389
389
  web/server/settings.py,sha256=PuWrOmn5LpJMBTymCMQreqMsL93tClTJ6wlcAZXjgCo,3755
390
390
  web/server/utils.py,sha256=bb6AOM3Nga-zihPjZvJeDL6sHqTPNC_SYjklsy8tAQA,4013
391
- web/server/watcher.py,sha256=5biJ8gbHmGNUrooMFWRmw00XDVg0bMx6Hub571cs6TI,5246
391
+ web/server/watcher.py,sha256=2c08EfXkgAfqpAEoFidkYjGH48DkLDyMueVp4afICHM,5334
392
392
  web/server/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
393
393
  web/server/api/endpoints/__init__.py,sha256=FcXl56-kPqbKgCZKOx0kjE50hn37q1m-nwOCxI9tRcc,932
394
394
  web/server/api/endpoints/commands.py,sha256=Fj7FUxOuageasij_7-dVvOlJMsBnRmFBWYk_-t91UQI,8242
@@ -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.0.dist-info/METADATA,sha256=WCarjHznaFXfa6Jk3aUr9ruElzfHr9yAR4_nxz2pKV0,26653
406
- sqlmesh-0.199.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
407
- sqlmesh-0.199.0.dist-info/entry_points.txt,sha256=pIYKtg-u-uO7BK_Y0qrdmfyj0bYrcMc3Xiqr5WE3VFk,121
408
- sqlmesh-0.199.0.dist-info/top_level.txt,sha256=bspjo5vjELQkuN0qKuTFIbsrUt3Zeo8D2mlKeHG1LdE,12
409
- sqlmesh-0.199.0.dist-info/RECORD,,
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,,
web/server/watcher.py CHANGED
@@ -30,7 +30,10 @@ async def watch_project() -> None:
30
30
  (settings.project_path / c.SEEDS).resolve(),
31
31
  ]
32
32
  ignore_dirs = [".env"]
33
- ignore_paths: t.List[t.Union[str, Path]] = [(settings.project_path / c.CACHE).resolve()]
33
+ cache_path = (
34
+ context.cache_dir.resolve() if context else (settings.project_path / c.CACHE).resolve()
35
+ )
36
+ ignore_paths: t.List[t.Union[str, Path]] = [cache_path]
34
37
  ignore_entity_patterns = context.config.ignore_patterns if context else c.IGNORE_PATTERNS
35
38
  ignore_entity_patterns.append("^.*\\.db(\\.wal)?$")
36
39