sqlmesh 0.217.1.dev1__py3-none-any.whl → 0.227.2.dev4__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/__init__.py +12 -2
- sqlmesh/_version.py +2 -2
- sqlmesh/cli/project_init.py +10 -2
- sqlmesh/core/_typing.py +1 -0
- sqlmesh/core/audit/definition.py +8 -2
- sqlmesh/core/config/__init__.py +1 -1
- sqlmesh/core/config/connection.py +17 -5
- sqlmesh/core/config/dbt.py +13 -0
- sqlmesh/core/config/janitor.py +12 -0
- sqlmesh/core/config/loader.py +7 -0
- sqlmesh/core/config/model.py +2 -0
- sqlmesh/core/config/root.py +3 -0
- sqlmesh/core/console.py +80 -2
- sqlmesh/core/constants.py +1 -1
- sqlmesh/core/context.py +61 -25
- sqlmesh/core/dialect.py +3 -0
- sqlmesh/core/engine_adapter/_typing.py +2 -0
- sqlmesh/core/engine_adapter/base.py +322 -22
- sqlmesh/core/engine_adapter/base_postgres.py +17 -1
- sqlmesh/core/engine_adapter/bigquery.py +146 -7
- sqlmesh/core/engine_adapter/clickhouse.py +17 -13
- sqlmesh/core/engine_adapter/databricks.py +33 -2
- sqlmesh/core/engine_adapter/fabric.py +1 -29
- sqlmesh/core/engine_adapter/mixins.py +142 -48
- sqlmesh/core/engine_adapter/mssql.py +15 -4
- sqlmesh/core/engine_adapter/mysql.py +2 -2
- sqlmesh/core/engine_adapter/postgres.py +9 -3
- sqlmesh/core/engine_adapter/redshift.py +4 -0
- sqlmesh/core/engine_adapter/risingwave.py +1 -0
- sqlmesh/core/engine_adapter/shared.py +6 -0
- sqlmesh/core/engine_adapter/snowflake.py +82 -11
- sqlmesh/core/engine_adapter/spark.py +14 -10
- sqlmesh/core/engine_adapter/trino.py +4 -2
- sqlmesh/core/janitor.py +181 -0
- sqlmesh/core/lineage.py +1 -0
- sqlmesh/core/macros.py +35 -13
- sqlmesh/core/model/common.py +2 -0
- sqlmesh/core/model/definition.py +65 -4
- sqlmesh/core/model/kind.py +66 -2
- sqlmesh/core/model/meta.py +107 -2
- sqlmesh/core/node.py +101 -2
- sqlmesh/core/plan/builder.py +15 -10
- sqlmesh/core/plan/common.py +196 -2
- sqlmesh/core/plan/definition.py +21 -6
- sqlmesh/core/plan/evaluator.py +72 -113
- sqlmesh/core/plan/explainer.py +90 -8
- sqlmesh/core/plan/stages.py +42 -21
- sqlmesh/core/renderer.py +26 -18
- sqlmesh/core/scheduler.py +60 -19
- sqlmesh/core/selector.py +137 -9
- sqlmesh/core/signal.py +64 -1
- sqlmesh/core/snapshot/__init__.py +1 -0
- sqlmesh/core/snapshot/definition.py +109 -25
- sqlmesh/core/snapshot/evaluator.py +610 -50
- sqlmesh/core/state_sync/__init__.py +0 -1
- sqlmesh/core/state_sync/base.py +31 -27
- sqlmesh/core/state_sync/cache.py +12 -4
- sqlmesh/core/state_sync/common.py +216 -111
- sqlmesh/core/state_sync/db/facade.py +30 -15
- sqlmesh/core/state_sync/db/interval.py +27 -7
- sqlmesh/core/state_sync/db/migrator.py +14 -8
- sqlmesh/core/state_sync/db/snapshot.py +119 -87
- sqlmesh/core/table_diff.py +2 -2
- sqlmesh/core/test/definition.py +14 -9
- sqlmesh/dbt/adapter.py +20 -11
- sqlmesh/dbt/basemodel.py +52 -41
- sqlmesh/dbt/builtin.py +27 -11
- sqlmesh/dbt/column.py +17 -5
- sqlmesh/dbt/common.py +4 -2
- sqlmesh/dbt/context.py +14 -1
- sqlmesh/dbt/loader.py +60 -8
- sqlmesh/dbt/manifest.py +136 -8
- sqlmesh/dbt/model.py +105 -25
- sqlmesh/dbt/package.py +16 -1
- sqlmesh/dbt/profile.py +3 -3
- sqlmesh/dbt/project.py +12 -7
- sqlmesh/dbt/seed.py +1 -1
- sqlmesh/dbt/source.py +6 -1
- sqlmesh/dbt/target.py +25 -6
- sqlmesh/dbt/test.py +31 -1
- sqlmesh/migrations/v0000_baseline.py +3 -6
- sqlmesh/migrations/v0061_mysql_fix_blob_text_type.py +2 -5
- sqlmesh/migrations/v0062_add_model_gateway.py +2 -2
- sqlmesh/migrations/v0063_change_signals.py +2 -4
- sqlmesh/migrations/v0064_join_when_matched_strings.py +2 -4
- sqlmesh/migrations/v0065_add_model_optimize.py +2 -2
- sqlmesh/migrations/v0066_add_auto_restatements.py +2 -6
- sqlmesh/migrations/v0067_add_tsql_date_full_precision.py +2 -2
- sqlmesh/migrations/v0068_include_unrendered_query_in_metadata_hash.py +2 -2
- sqlmesh/migrations/v0069_update_dev_table_suffix.py +2 -4
- sqlmesh/migrations/v0070_include_grains_in_metadata_hash.py +2 -2
- sqlmesh/migrations/v0071_add_dev_version_to_intervals.py +2 -6
- sqlmesh/migrations/v0072_add_environment_statements.py +2 -4
- sqlmesh/migrations/v0073_remove_symbolic_disable_restatement.py +2 -4
- sqlmesh/migrations/v0074_add_partition_by_time_column_property.py +2 -2
- sqlmesh/migrations/v0075_remove_validate_query.py +2 -4
- sqlmesh/migrations/v0076_add_cron_tz.py +2 -2
- sqlmesh/migrations/v0077_fix_column_type_hash_calculation.py +2 -2
- sqlmesh/migrations/v0078_warn_if_non_migratable_python_env.py +2 -4
- sqlmesh/migrations/v0079_add_gateway_managed_property.py +7 -9
- sqlmesh/migrations/v0080_add_batch_size_to_scd_type_2_models.py +2 -2
- sqlmesh/migrations/v0081_update_partitioned_by.py +2 -4
- sqlmesh/migrations/v0082_warn_if_incorrectly_duplicated_statements.py +2 -4
- sqlmesh/migrations/v0083_use_sql_for_scd_time_data_type_data_hash.py +2 -2
- sqlmesh/migrations/v0084_normalize_quote_when_matched_and_merge_filter.py +2 -2
- sqlmesh/migrations/v0085_deterministic_repr.py +2 -4
- sqlmesh/migrations/v0086_check_deterministic_bug.py +2 -4
- sqlmesh/migrations/v0087_normalize_blueprint_variables.py +2 -4
- sqlmesh/migrations/v0088_warn_about_variable_python_env_diffs.py +2 -4
- sqlmesh/migrations/v0089_add_virtual_environment_mode.py +2 -2
- sqlmesh/migrations/v0090_add_forward_only_column.py +2 -6
- sqlmesh/migrations/v0091_on_additive_change.py +2 -2
- sqlmesh/migrations/v0092_warn_about_dbt_data_type_diff.py +2 -4
- sqlmesh/migrations/v0093_use_raw_sql_in_fingerprint.py +2 -2
- sqlmesh/migrations/v0094_add_dev_version_and_fingerprint_columns.py +2 -6
- sqlmesh/migrations/v0095_warn_about_dbt_raw_sql_diff.py +2 -4
- sqlmesh/migrations/v0096_remove_plan_dags_table.py +2 -4
- sqlmesh/migrations/v0097_add_dbt_name_in_node.py +2 -2
- sqlmesh/migrations/v0098_add_dbt_node_info_in_node.py +103 -0
- sqlmesh/migrations/v0099_add_last_altered_to_intervals.py +25 -0
- sqlmesh/migrations/v0100_add_grants_and_grants_target_layer.py +9 -0
- sqlmesh/utils/__init__.py +8 -1
- sqlmesh/utils/cache.py +5 -1
- sqlmesh/utils/date.py +1 -1
- sqlmesh/utils/errors.py +4 -0
- sqlmesh/utils/jinja.py +25 -2
- sqlmesh/utils/pydantic.py +6 -6
- sqlmesh/utils/windows.py +13 -3
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/METADATA +5 -5
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/RECORD +181 -176
- sqlmesh_dbt/cli.py +70 -7
- sqlmesh_dbt/console.py +14 -6
- sqlmesh_dbt/operations.py +103 -24
- sqlmesh_dbt/selectors.py +39 -1
- web/client/dist/assets/{Audits-Ucsx1GzF.js → Audits-CBiYyyx-.js} +1 -1
- web/client/dist/assets/{Banner-BWDzvavM.js → Banner-DSRbUlO5.js} +1 -1
- web/client/dist/assets/{ChevronDownIcon-D2VL13Ah.js → ChevronDownIcon-MK_nrjD_.js} +1 -1
- web/client/dist/assets/{ChevronRightIcon-DWGYbf1l.js → ChevronRightIcon-CLWtT22Q.js} +1 -1
- web/client/dist/assets/{Content-DdHDZM3I.js → Content-BNuGZN5l.js} +1 -1
- web/client/dist/assets/{Content-Bikfy8fh.js → Content-CSHJyW0n.js} +1 -1
- web/client/dist/assets/{Data-CzAJH7rW.js → Data-C1oRDbLx.js} +1 -1
- web/client/dist/assets/{DataCatalog-BJF11g8f.js → DataCatalog-HXyX2-_j.js} +1 -1
- web/client/dist/assets/{Editor-s0SBpV2y.js → Editor-BDyfpUuw.js} +1 -1
- web/client/dist/assets/{Editor-DgLhgKnm.js → Editor-D0jNItwC.js} +1 -1
- web/client/dist/assets/{Errors-D0m0O1d3.js → Errors-BfuFLcPi.js} +1 -1
- web/client/dist/assets/{FileExplorer-CEv0vXkt.js → FileExplorer-BR9IE3he.js} +1 -1
- web/client/dist/assets/{Footer-BwzXn8Ew.js → Footer-CgBEtiAh.js} +1 -1
- web/client/dist/assets/{Header-6heDkEqG.js → Header-DSqR6nSO.js} +1 -1
- web/client/dist/assets/{Input-obuJsD6k.js → Input-B-oZ6fGO.js} +1 -1
- web/client/dist/assets/Lineage-DYQVwDbD.js +1 -0
- web/client/dist/assets/{ListboxShow-HM9_qyrt.js → ListboxShow-BE5-xevs.js} +1 -1
- web/client/dist/assets/{ModelLineage-zWdKo0U2.js → ModelLineage-DkIFAYo4.js} +1 -1
- web/client/dist/assets/{Models-Bcu66SRz.js → Models-D5dWr8RB.js} +1 -1
- web/client/dist/assets/{Page-BWEEQfIt.js → Page-C-XfU5BR.js} +1 -1
- web/client/dist/assets/{Plan-C4gXCqlf.js → Plan-ZEuTINBq.js} +1 -1
- web/client/dist/assets/{PlusCircleIcon-CVDO651q.js → PlusCircleIcon-DVXAHG8_.js} +1 -1
- web/client/dist/assets/{ReportErrors-BT6xFwAr.js → ReportErrors-B7FEPzMB.js} +1 -1
- web/client/dist/assets/{Root-ryJoBK4h.js → Root-8aZyhPxF.js} +1 -1
- web/client/dist/assets/{SearchList-DB04sPb9.js → SearchList-W_iT2G82.js} +1 -1
- web/client/dist/assets/{SelectEnvironment-CUYcXUu6.js → SelectEnvironment-C65jALmO.js} +1 -1
- web/client/dist/assets/{SourceList-Doo_9ZGp.js → SourceList-DSLO6nVJ.js} +1 -1
- web/client/dist/assets/{SourceListItem-D5Mj7Dly.js → SourceListItem-BHt8d9-I.js} +1 -1
- web/client/dist/assets/{SplitPane-qHmkD1qy.js → SplitPane-CViaZmw6.js} +1 -1
- web/client/dist/assets/{Tests-DH1Z74ML.js → Tests-DhaVt5t1.js} +1 -1
- web/client/dist/assets/{Welcome-DqUJUNMF.js → Welcome-DvpjH-_4.js} +1 -1
- web/client/dist/assets/context-BctCsyGb.js +71 -0
- web/client/dist/assets/{context-Dr54UHLi.js → context-DFNeGsFF.js} +1 -1
- web/client/dist/assets/{editor-DYIP1yQ4.js → editor-CcO28cqd.js} +1 -1
- web/client/dist/assets/{file-DarlIDVi.js → file-CvJN3aZO.js} +1 -1
- web/client/dist/assets/{floating-ui.react-dom-BH3TFvkM.js → floating-ui.react-dom-CjE-JNW1.js} +1 -1
- web/client/dist/assets/{help-Bl8wqaQc.js → help-DuPhjipa.js} +1 -1
- web/client/dist/assets/{index-D1sR7wpN.js → index-C-dJH7yZ.js} +1 -1
- web/client/dist/assets/{index-O3mjYpnE.js → index-Dj0i1-CA.js} +2 -2
- web/client/dist/assets/{plan-CehRrJUG.js → plan-BTRSbjKn.js} +1 -1
- web/client/dist/assets/{popover-CqgMRE0G.js → popover-_Sf0yvOI.js} +1 -1
- web/client/dist/assets/{project-6gxepOhm.js → project-BvSOI8MY.js} +1 -1
- web/client/dist/index.html +1 -1
- web/client/dist/assets/Lineage-D0Hgdz2v.js +0 -1
- web/client/dist/assets/context-DgX0fp2E.js +0 -68
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/WHEEL +0 -0
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/top_level.txt +0 -0
sqlmesh/__init__.py
CHANGED
|
@@ -126,6 +126,8 @@ def is_cicd_environment() -> bool:
|
|
|
126
126
|
|
|
127
127
|
|
|
128
128
|
def is_interactive_environment() -> bool:
|
|
129
|
+
if sys.stdin is None or sys.stdout is None:
|
|
130
|
+
return False
|
|
129
131
|
return sys.stdin.isatty() and sys.stdout.isatty()
|
|
130
132
|
|
|
131
133
|
|
|
@@ -186,6 +188,7 @@ def configure_logging(
|
|
|
186
188
|
write_to_file: bool = True,
|
|
187
189
|
log_file_dir: t.Optional[t.Union[str, Path]] = None,
|
|
188
190
|
ignore_warnings: bool = False,
|
|
191
|
+
log_level: t.Optional[t.Union[str, int]] = None,
|
|
189
192
|
) -> None:
|
|
190
193
|
# Remove noisy grpc logs that are not useful for users
|
|
191
194
|
os.environ["GRPC_VERBOSITY"] = os.environ.get("GRPC_VERBOSITY", "NONE")
|
|
@@ -193,8 +196,15 @@ def configure_logging(
|
|
|
193
196
|
logger = logging.getLogger()
|
|
194
197
|
debug = force_debug or debug_mode_enabled()
|
|
195
198
|
|
|
196
|
-
|
|
197
|
-
|
|
199
|
+
if log_level is not None:
|
|
200
|
+
if isinstance(log_level, str):
|
|
201
|
+
level = logging._nameToLevel.get(log_level.upper()) or logging.INFO
|
|
202
|
+
else:
|
|
203
|
+
level = log_level
|
|
204
|
+
else:
|
|
205
|
+
# base logger needs to be the lowest level that we plan to log
|
|
206
|
+
level = logging.DEBUG if debug else logging.INFO
|
|
207
|
+
|
|
198
208
|
logger.setLevel(level)
|
|
199
209
|
|
|
200
210
|
if debug:
|
sqlmesh/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.227.2.dev4'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 227, 2, 'dev4')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
sqlmesh/cli/project_init.py
CHANGED
|
@@ -116,7 +116,15 @@ linter:
|
|
|
116
116
|
- invalidselectstarexpansion
|
|
117
117
|
- noambiguousprojections
|
|
118
118
|
""",
|
|
119
|
-
ProjectTemplate.DBT: f"""# ---
|
|
119
|
+
ProjectTemplate.DBT: f"""# --- DBT-specific options ---
|
|
120
|
+
dbt:
|
|
121
|
+
# This configuration ensures that each dbt target gets its own isolated state.
|
|
122
|
+
# The inferred state schemas are named "sqlmesh_state_<profile name>_<target schema>", eg "sqlmesh_state_jaffle_shop_dev"
|
|
123
|
+
# If this is undesirable, you may manually configure the gateway to use a specific state schema name
|
|
124
|
+
# https://sqlmesh.readthedocs.io/en/stable/integrations/dbt/#selecting-a-different-state-connection
|
|
125
|
+
infer_state_schema_name: True
|
|
126
|
+
|
|
127
|
+
# --- Virtual Data Environment Mode ---
|
|
120
128
|
# Enable Virtual Data Environments (VDE) for *development* environments.
|
|
121
129
|
# Note that the production environment in dbt projects is not virtual by default to maintain compatibility with existing tooling.
|
|
122
130
|
# https://sqlmesh.readthedocs.io/en/stable/guides/configuration/#virtual-data-environment-modes
|
|
@@ -298,6 +306,7 @@ def init_example_project(
|
|
|
298
306
|
dlt_path: t.Optional[str] = None,
|
|
299
307
|
schema_name: str = "sqlmesh_example",
|
|
300
308
|
cli_mode: InitCliMode = InitCliMode.DEFAULT,
|
|
309
|
+
start: t.Optional[str] = None,
|
|
301
310
|
) -> Path:
|
|
302
311
|
root_path = Path(path)
|
|
303
312
|
|
|
@@ -336,7 +345,6 @@ def init_example_project(
|
|
|
336
345
|
|
|
337
346
|
models: t.Set[t.Tuple[str, str]] = set()
|
|
338
347
|
settings = None
|
|
339
|
-
start = None
|
|
340
348
|
if engine_type and template == ProjectTemplate.DLT:
|
|
341
349
|
project_dialect = dialect or DIALECT_TO_TYPE.get(engine_type)
|
|
342
350
|
if pipeline and project_dialect:
|
sqlmesh/core/_typing.py
CHANGED
|
@@ -11,6 +11,7 @@ if t.TYPE_CHECKING:
|
|
|
11
11
|
SessionProperties = t.Dict[str, t.Union[exp.Expression, str, int, float, bool]]
|
|
12
12
|
CustomMaterializationProperties = t.Dict[str, t.Union[exp.Expression, str, int, float, bool]]
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
if sys.version_info >= (3, 11):
|
|
15
16
|
from typing import Self as Self
|
|
16
17
|
else:
|
sqlmesh/core/audit/definition.py
CHANGED
|
@@ -19,7 +19,7 @@ from sqlmesh.core.model.common import (
|
|
|
19
19
|
sorted_python_env_payloads,
|
|
20
20
|
)
|
|
21
21
|
from sqlmesh.core.model.common import make_python_env, single_value_or_tuple, ParsableSql
|
|
22
|
-
from sqlmesh.core.node import _Node
|
|
22
|
+
from sqlmesh.core.node import _Node, DbtInfoMixin, DbtNodeInfo
|
|
23
23
|
from sqlmesh.core.renderer import QueryRenderer
|
|
24
24
|
from sqlmesh.utils.date import TimeLike
|
|
25
25
|
from sqlmesh.utils.errors import AuditConfigError, SQLMeshError, raise_config_error
|
|
@@ -120,7 +120,7 @@ def audit_map_validator(cls: t.Type, v: t.Any, values: t.Any) -> t.Dict[str, t.A
|
|
|
120
120
|
return {}
|
|
121
121
|
|
|
122
122
|
|
|
123
|
-
class ModelAudit(PydanticModel, AuditMixin, frozen=True):
|
|
123
|
+
class ModelAudit(PydanticModel, AuditMixin, DbtInfoMixin, frozen=True):
|
|
124
124
|
"""
|
|
125
125
|
Audit is an assertion made about your tables.
|
|
126
126
|
|
|
@@ -137,6 +137,7 @@ class ModelAudit(PydanticModel, AuditMixin, frozen=True):
|
|
|
137
137
|
expressions_: t.Optional[t.List[ParsableSql]] = Field(default=None, alias="expressions")
|
|
138
138
|
jinja_macros: JinjaMacroRegistry = JinjaMacroRegistry()
|
|
139
139
|
formatting: t.Optional[bool] = Field(default=None, exclude=True)
|
|
140
|
+
dbt_node_info_: t.Optional[DbtNodeInfo] = Field(alias="dbt_node_info", default=None)
|
|
140
141
|
|
|
141
142
|
_path: t.Optional[Path] = None
|
|
142
143
|
|
|
@@ -150,6 +151,10 @@ class ModelAudit(PydanticModel, AuditMixin, frozen=True):
|
|
|
150
151
|
path = f": {self._path.name}" if self._path else ""
|
|
151
152
|
return f"{self.__class__.__name__}<{self.name}{path}>"
|
|
152
153
|
|
|
154
|
+
@property
|
|
155
|
+
def dbt_node_info(self) -> t.Optional[DbtNodeInfo]:
|
|
156
|
+
return self.dbt_node_info_
|
|
157
|
+
|
|
153
158
|
|
|
154
159
|
class StandaloneAudit(_Node, AuditMixin):
|
|
155
160
|
"""
|
|
@@ -552,4 +557,5 @@ META_FIELD_CONVERTER: t.Dict[str, t.Callable] = {
|
|
|
552
557
|
"depends_on_": lambda value: exp.Tuple(expressions=sorted(value)),
|
|
553
558
|
"tags": single_value_or_tuple,
|
|
554
559
|
"default_catalog": exp.to_identifier,
|
|
560
|
+
"dbt_node_info_": lambda value: value.to_expression(),
|
|
555
561
|
}
|
sqlmesh/core/config/__init__.py
CHANGED
|
@@ -36,6 +36,6 @@ from sqlmesh.core.config.model import ModelDefaultsConfig as ModelDefaultsConfig
|
|
|
36
36
|
from sqlmesh.core.config.naming import NameInferenceConfig as NameInferenceConfig
|
|
37
37
|
from sqlmesh.core.config.linter import LinterConfig as LinterConfig
|
|
38
38
|
from sqlmesh.core.config.plan import PlanConfig as PlanConfig
|
|
39
|
-
from sqlmesh.core.config.root import Config as Config
|
|
39
|
+
from sqlmesh.core.config.root import Config as Config, DbtConfig as DbtConfig
|
|
40
40
|
from sqlmesh.core.config.run import RunConfig as RunConfig
|
|
41
41
|
from sqlmesh.core.config.scheduler import BuiltInSchedulerConfig as BuiltInSchedulerConfig
|
|
@@ -58,6 +58,7 @@ FORBIDDEN_STATE_SYNC_ENGINES = {
|
|
|
58
58
|
"clickhouse",
|
|
59
59
|
}
|
|
60
60
|
MOTHERDUCK_TOKEN_REGEX = re.compile(r"(\?|\&)(motherduck_token=)(\S*)")
|
|
61
|
+
PASSWORD_REGEX = re.compile(r"(password=)(\S+)")
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
def _get_engine_import_validator(
|
|
@@ -101,6 +102,7 @@ class ConnectionConfig(abc.ABC, BaseConfig):
|
|
|
101
102
|
pre_ping: bool
|
|
102
103
|
pretty_sql: bool = False
|
|
103
104
|
schema_differ_overrides: t.Optional[t.Dict[str, t.Any]] = None
|
|
105
|
+
catalog_type_overrides: t.Optional[t.Dict[str, str]] = None
|
|
104
106
|
|
|
105
107
|
# Whether to share a single connection across threads or create a new connection per thread.
|
|
106
108
|
shared_connection: t.ClassVar[bool] = False
|
|
@@ -176,6 +178,7 @@ class ConnectionConfig(abc.ABC, BaseConfig):
|
|
|
176
178
|
pretty_sql=self.pretty_sql,
|
|
177
179
|
shared_connection=self.shared_connection,
|
|
178
180
|
schema_differ_overrides=self.schema_differ_overrides,
|
|
181
|
+
catalog_type_overrides=self.catalog_type_overrides,
|
|
179
182
|
**self._extra_engine_config,
|
|
180
183
|
)
|
|
181
184
|
|
|
@@ -477,13 +480,13 @@ class BaseDuckDBConnectionConfig(ConnectionConfig):
|
|
|
477
480
|
adapter = BaseDuckDBConnectionConfig._data_file_to_adapter.get(key)
|
|
478
481
|
if adapter is not None:
|
|
479
482
|
logger.info(
|
|
480
|
-
f"Using existing DuckDB adapter due to overlapping data file: {self.
|
|
483
|
+
f"Using existing DuckDB adapter due to overlapping data file: {self._mask_sensitive_data(key)}"
|
|
481
484
|
)
|
|
482
485
|
return adapter
|
|
483
486
|
|
|
484
487
|
if data_files:
|
|
485
488
|
masked_files = {
|
|
486
|
-
self.
|
|
489
|
+
self._mask_sensitive_data(file if isinstance(file, str) else file.path)
|
|
487
490
|
for file in data_files
|
|
488
491
|
}
|
|
489
492
|
logger.info(f"Creating new DuckDB adapter for data files: {masked_files}")
|
|
@@ -505,10 +508,14 @@ class BaseDuckDBConnectionConfig(ConnectionConfig):
|
|
|
505
508
|
return list(self.catalogs)[0]
|
|
506
509
|
return None
|
|
507
510
|
|
|
508
|
-
def
|
|
509
|
-
|
|
510
|
-
|
|
511
|
+
def _mask_sensitive_data(self, string: str) -> str:
|
|
512
|
+
# Mask MotherDuck tokens with fixed number of asterisks
|
|
513
|
+
result = MOTHERDUCK_TOKEN_REGEX.sub(
|
|
514
|
+
lambda m: f"{m.group(1)}{m.group(2)}{'*' * 8 if m.group(3) else ''}", string
|
|
511
515
|
)
|
|
516
|
+
# Mask PostgreSQL/MySQL passwords with fixed number of asterisks
|
|
517
|
+
result = PASSWORD_REGEX.sub(lambda m: f"{m.group(1)}{'*' * 8}", result)
|
|
518
|
+
return result
|
|
512
519
|
|
|
513
520
|
|
|
514
521
|
class MotherDuckConnectionConfig(BaseDuckDBConnectionConfig):
|
|
@@ -1753,6 +1760,7 @@ class SparkConnectionConfig(ConnectionConfig):
|
|
|
1753
1760
|
config_dir: t.Optional[str] = None
|
|
1754
1761
|
catalog: t.Optional[str] = None
|
|
1755
1762
|
config: t.Dict[str, t.Any] = {}
|
|
1763
|
+
wap_enabled: bool = False
|
|
1756
1764
|
|
|
1757
1765
|
concurrent_tasks: int = 4
|
|
1758
1766
|
register_comments: bool = True
|
|
@@ -1799,6 +1807,10 @@ class SparkConnectionConfig(ConnectionConfig):
|
|
|
1799
1807
|
.getOrCreate(),
|
|
1800
1808
|
}
|
|
1801
1809
|
|
|
1810
|
+
@property
|
|
1811
|
+
def _extra_engine_config(self) -> t.Dict[str, t.Any]:
|
|
1812
|
+
return {"wap_enabled": self.wap_enabled}
|
|
1813
|
+
|
|
1802
1814
|
|
|
1803
1815
|
class TrinoAuthenticationMethod(str, Enum):
|
|
1804
1816
|
NO_AUTH = "no-auth"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from sqlmesh.core.config.base import BaseConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DbtConfig(BaseConfig):
|
|
5
|
+
"""
|
|
6
|
+
Represents dbt-specific options on the SQLMesh root config.
|
|
7
|
+
|
|
8
|
+
These options are only taken into account for dbt projects and are ignored on native projects
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
infer_state_schema_name: bool = False
|
|
12
|
+
"""If set, indicates to the dbt loader that the state schema should be inferred based on the profile/target
|
|
13
|
+
so that each target gets its own isolated state"""
|
sqlmesh/core/config/janitor.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import typing as t
|
|
3
4
|
|
|
4
5
|
from sqlmesh.core.config.base import BaseConfig
|
|
6
|
+
from sqlmesh.utils.pydantic import field_validator
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class JanitorConfig(BaseConfig):
|
|
@@ -9,6 +11,16 @@ class JanitorConfig(BaseConfig):
|
|
|
9
11
|
|
|
10
12
|
Args:
|
|
11
13
|
warn_on_delete_failure: Whether to warn instead of erroring if the janitor fails to delete the expired environment schema / views.
|
|
14
|
+
expired_snapshots_batch_size: Maximum number of expired snapshots to clean in a single batch.
|
|
12
15
|
"""
|
|
13
16
|
|
|
14
17
|
warn_on_delete_failure: bool = False
|
|
18
|
+
expired_snapshots_batch_size: t.Optional[int] = None
|
|
19
|
+
|
|
20
|
+
@field_validator("expired_snapshots_batch_size", mode="before")
|
|
21
|
+
@classmethod
|
|
22
|
+
def _validate_batch_size(cls, value: int) -> int:
|
|
23
|
+
batch_size = int(value)
|
|
24
|
+
if batch_size <= 0:
|
|
25
|
+
raise ValueError("expired_snapshots_batch_size must be greater than 0")
|
|
26
|
+
return batch_size
|
sqlmesh/core/config/loader.py
CHANGED
|
@@ -172,11 +172,18 @@ def load_config_from_paths(
|
|
|
172
172
|
if dbt_project_file:
|
|
173
173
|
from sqlmesh.dbt.loader import sqlmesh_config
|
|
174
174
|
|
|
175
|
+
infer_state_schema_name = False
|
|
176
|
+
if dbt := non_python_config.dbt:
|
|
177
|
+
infer_state_schema_name = dbt.infer_state_schema_name
|
|
178
|
+
|
|
175
179
|
dbt_python_config = sqlmesh_config(
|
|
176
180
|
project_root=dbt_project_file.parent,
|
|
181
|
+
profiles_dir=kwargs.pop("profiles_dir", None),
|
|
177
182
|
dbt_profile_name=kwargs.pop("profile", None),
|
|
178
183
|
dbt_target_name=kwargs.pop("target", None),
|
|
179
184
|
variables=variables,
|
|
185
|
+
threads=kwargs.pop("threads", None),
|
|
186
|
+
infer_state_schema_name=infer_state_schema_name,
|
|
180
187
|
)
|
|
181
188
|
if type(dbt_python_config) != config_type:
|
|
182
189
|
dbt_python_config = convert_config_type(dbt_python_config, config_type)
|
sqlmesh/core/config/model.py
CHANGED
|
@@ -45,6 +45,7 @@ class ModelDefaultsConfig(BaseConfig):
|
|
|
45
45
|
allow_partials: Whether the models can process partial (incomplete) data intervals.
|
|
46
46
|
enabled: Whether the models are enabled.
|
|
47
47
|
interval_unit: The temporal granularity of the models data intervals. By default computed from cron.
|
|
48
|
+
batch_concurrency: The maximum number of batches that can run concurrently for an incremental model.
|
|
48
49
|
pre_statements: The list of SQL statements that get executed before a model runs.
|
|
49
50
|
post_statements: The list of SQL statements that get executed before a model runs.
|
|
50
51
|
on_virtual_update: The list of SQL statements to be executed after the virtual update.
|
|
@@ -69,6 +70,7 @@ class ModelDefaultsConfig(BaseConfig):
|
|
|
69
70
|
interval_unit: t.Optional[t.Union[str, IntervalUnit]] = None
|
|
70
71
|
enabled: t.Optional[t.Union[str, bool]] = None
|
|
71
72
|
formatting: t.Optional[t.Union[str, bool]] = None
|
|
73
|
+
batch_concurrency: t.Optional[int] = None
|
|
72
74
|
pre_statements: t.Optional[t.List[t.Union[str, exp.Expression]]] = None
|
|
73
75
|
post_statements: t.Optional[t.List[t.Union[str, exp.Expression]]] = None
|
|
74
76
|
on_virtual_update: t.Optional[t.List[t.Union[str, exp.Expression]]] = None
|
sqlmesh/core/config/root.py
CHANGED
|
@@ -36,6 +36,7 @@ from sqlmesh.core.config.naming import NameInferenceConfig as NameInferenceConfi
|
|
|
36
36
|
from sqlmesh.core.config.linter import LinterConfig as LinterConfig
|
|
37
37
|
from sqlmesh.core.config.plan import PlanConfig
|
|
38
38
|
from sqlmesh.core.config.run import RunConfig
|
|
39
|
+
from sqlmesh.core.config.dbt import DbtConfig
|
|
39
40
|
from sqlmesh.core.config.scheduler import (
|
|
40
41
|
BuiltInSchedulerConfig,
|
|
41
42
|
SchedulerConfig,
|
|
@@ -173,6 +174,7 @@ class Config(BaseConfig):
|
|
|
173
174
|
linter: LinterConfig = LinterConfig()
|
|
174
175
|
janitor: JanitorConfig = JanitorConfig()
|
|
175
176
|
cache_dir: t.Optional[str] = None
|
|
177
|
+
dbt: t.Optional[DbtConfig] = None
|
|
176
178
|
|
|
177
179
|
_FIELD_UPDATE_STRATEGY: t.ClassVar[t.Dict[str, UpdateStrategy]] = {
|
|
178
180
|
"gateways": UpdateStrategy.NESTED_UPDATE,
|
|
@@ -191,6 +193,7 @@ class Config(BaseConfig):
|
|
|
191
193
|
"before_all": UpdateStrategy.EXTEND,
|
|
192
194
|
"after_all": UpdateStrategy.EXTEND,
|
|
193
195
|
"linter": UpdateStrategy.NESTED_UPDATE,
|
|
196
|
+
"dbt": UpdateStrategy.NESTED_UPDATE,
|
|
194
197
|
}
|
|
195
198
|
|
|
196
199
|
_connection_config_validator = connection_config_validator
|
sqlmesh/core/console.py
CHANGED
|
@@ -551,6 +551,22 @@ class Console(
|
|
|
551
551
|
def log_failed_models(self, errors: t.List[NodeExecutionFailedError]) -> None:
|
|
552
552
|
"""Display list of models that failed during evaluation to the user."""
|
|
553
553
|
|
|
554
|
+
@abc.abstractmethod
|
|
555
|
+
def log_models_updated_during_restatement(
|
|
556
|
+
self,
|
|
557
|
+
snapshots: t.List[t.Tuple[SnapshotTableInfo, SnapshotTableInfo]],
|
|
558
|
+
environment_naming_info: EnvironmentNamingInfo,
|
|
559
|
+
default_catalog: t.Optional[str],
|
|
560
|
+
) -> None:
|
|
561
|
+
"""Display a list of models where new versions got deployed to the specified :environment while we were restating data the old versions
|
|
562
|
+
|
|
563
|
+
Args:
|
|
564
|
+
snapshots: a list of (snapshot_we_restated, snapshot_it_got_replaced_with_during_restatement) tuples
|
|
565
|
+
environment: which environment got updated while we were restating models
|
|
566
|
+
environment_naming_info: how snapshots are named in that :environment (for display name purposes)
|
|
567
|
+
default_catalog: the configured default catalog (for display name purposes)
|
|
568
|
+
"""
|
|
569
|
+
|
|
554
570
|
@abc.abstractmethod
|
|
555
571
|
def loading_start(self, message: t.Optional[str] = None) -> uuid.UUID:
|
|
556
572
|
"""Starts loading and returns a unique ID that can be used to stop the loading. Optionally can display a message."""
|
|
@@ -771,6 +787,14 @@ class NoopConsole(Console):
|
|
|
771
787
|
def log_failed_models(self, errors: t.List[NodeExecutionFailedError]) -> None:
|
|
772
788
|
pass
|
|
773
789
|
|
|
790
|
+
def log_models_updated_during_restatement(
|
|
791
|
+
self,
|
|
792
|
+
snapshots: t.List[t.Tuple[SnapshotTableInfo, SnapshotTableInfo]],
|
|
793
|
+
environment_naming_info: EnvironmentNamingInfo,
|
|
794
|
+
default_catalog: t.Optional[str],
|
|
795
|
+
) -> None:
|
|
796
|
+
pass
|
|
797
|
+
|
|
774
798
|
def log_destructive_change(
|
|
775
799
|
self,
|
|
776
800
|
snapshot_name: str,
|
|
@@ -1998,7 +2022,34 @@ class TerminalConsole(Console):
|
|
|
1998
2022
|
plan = plan_builder.build()
|
|
1999
2023
|
|
|
2000
2024
|
if plan.restatements:
|
|
2001
|
-
|
|
2025
|
+
# A plan can have restatements for the following reasons:
|
|
2026
|
+
# - The user specifically called `sqlmesh plan` with --restate-model.
|
|
2027
|
+
# This creates a "restatement plan" which disallows all other changes and simply force-backfills
|
|
2028
|
+
# the selected models and their downstream dependencies using the versions of the models stored in state.
|
|
2029
|
+
# - There are no specific restatements (so changes are allowed) AND dev previews need to be computed.
|
|
2030
|
+
# The "restatements" feature is currently reused for dev previews.
|
|
2031
|
+
if plan.selected_models_to_restate:
|
|
2032
|
+
# There were legitimate restatements, no dev previews
|
|
2033
|
+
tree = Tree(
|
|
2034
|
+
"[bold]Models selected for restatement:[/bold]\n"
|
|
2035
|
+
"This causes backfill of the model itself as well as affected downstream models"
|
|
2036
|
+
)
|
|
2037
|
+
model_fqn_to_snapshot = {s.name: s for s in plan.snapshots.values()}
|
|
2038
|
+
for model_fqn in plan.selected_models_to_restate:
|
|
2039
|
+
snapshot = model_fqn_to_snapshot[model_fqn]
|
|
2040
|
+
display_name = snapshot.display_name(
|
|
2041
|
+
plan.environment_naming_info,
|
|
2042
|
+
default_catalog if self.verbosity < Verbosity.VERY_VERBOSE else None,
|
|
2043
|
+
dialect=self.dialect,
|
|
2044
|
+
)
|
|
2045
|
+
tree.add(
|
|
2046
|
+
display_name
|
|
2047
|
+
) # note: we deliberately dont show any intervals here; they get shown in the backfill section
|
|
2048
|
+
self._print(tree)
|
|
2049
|
+
else:
|
|
2050
|
+
# We are computing dev previews, do not confuse the user by printing out something to do
|
|
2051
|
+
# with restatements. Dev previews are already highlighted in the backfill step
|
|
2052
|
+
pass
|
|
2002
2053
|
else:
|
|
2003
2054
|
self.show_environment_difference_summary(
|
|
2004
2055
|
plan.context_diff,
|
|
@@ -2225,6 +2276,30 @@ class TerminalConsole(Console):
|
|
|
2225
2276
|
for node_name, msg in error_messages.items():
|
|
2226
2277
|
self._print(f" [red]{node_name}[/red]\n\n{msg}")
|
|
2227
2278
|
|
|
2279
|
+
def log_models_updated_during_restatement(
|
|
2280
|
+
self,
|
|
2281
|
+
snapshots: t.List[t.Tuple[SnapshotTableInfo, SnapshotTableInfo]],
|
|
2282
|
+
environment_naming_info: EnvironmentNamingInfo,
|
|
2283
|
+
default_catalog: t.Optional[str] = None,
|
|
2284
|
+
) -> None:
|
|
2285
|
+
if snapshots:
|
|
2286
|
+
tree = Tree(
|
|
2287
|
+
f"[yellow]The following models had new versions deployed while data was being restated:[/yellow]"
|
|
2288
|
+
)
|
|
2289
|
+
|
|
2290
|
+
for restated_snapshot, updated_snapshot in snapshots:
|
|
2291
|
+
display_name = restated_snapshot.display_name(
|
|
2292
|
+
environment_naming_info,
|
|
2293
|
+
default_catalog if self.verbosity < Verbosity.VERY_VERBOSE else None,
|
|
2294
|
+
dialect=self.dialect,
|
|
2295
|
+
)
|
|
2296
|
+
current_branch = tree.add(display_name)
|
|
2297
|
+
current_branch.add(f"restated version: '{restated_snapshot.version}'")
|
|
2298
|
+
current_branch.add(f"currently active version: '{updated_snapshot.version}'")
|
|
2299
|
+
|
|
2300
|
+
self._print(tree)
|
|
2301
|
+
self._print("") # newline spacer
|
|
2302
|
+
|
|
2228
2303
|
def log_destructive_change(
|
|
2229
2304
|
self,
|
|
2230
2305
|
snapshot_name: str,
|
|
@@ -3566,7 +3641,10 @@ class MarkdownConsole(CaptureTerminalConsole):
|
|
|
3566
3641
|
msg = f"\nLinter {severity} for `{model._path}`:\n{violations_msg}\n"
|
|
3567
3642
|
|
|
3568
3643
|
self._print(msg)
|
|
3569
|
-
|
|
3644
|
+
if is_error:
|
|
3645
|
+
self._errors.append(msg)
|
|
3646
|
+
else:
|
|
3647
|
+
self._warnings.append(msg)
|
|
3570
3648
|
|
|
3571
3649
|
@property
|
|
3572
3650
|
def captured_warnings(self) -> str:
|
sqlmesh/core/constants.py
CHANGED
sqlmesh/core/context.py
CHANGED
|
@@ -93,7 +93,7 @@ from sqlmesh.core.plan.definition import UserProvidedFlags
|
|
|
93
93
|
from sqlmesh.core.reference import ReferenceGraph
|
|
94
94
|
from sqlmesh.core.scheduler import Scheduler, CompletionStatus
|
|
95
95
|
from sqlmesh.core.schema_loader import create_external_models_file
|
|
96
|
-
from sqlmesh.core.selector import Selector
|
|
96
|
+
from sqlmesh.core.selector import Selector, NativeSelector
|
|
97
97
|
from sqlmesh.core.snapshot import (
|
|
98
98
|
DeployabilityIndex,
|
|
99
99
|
Snapshot,
|
|
@@ -107,8 +107,8 @@ from sqlmesh.core.state_sync import (
|
|
|
107
107
|
CachingStateSync,
|
|
108
108
|
StateReader,
|
|
109
109
|
StateSync,
|
|
110
|
-
cleanup_expired_views,
|
|
111
110
|
)
|
|
111
|
+
from sqlmesh.core.janitor import cleanup_expired_views, delete_expired_snapshots
|
|
112
112
|
from sqlmesh.core.table_diff import TableDiff
|
|
113
113
|
from sqlmesh.core.test import (
|
|
114
114
|
ModelTextTestResult,
|
|
@@ -139,6 +139,7 @@ from sqlmesh.utils.errors import (
|
|
|
139
139
|
)
|
|
140
140
|
from sqlmesh.utils.config import print_config
|
|
141
141
|
from sqlmesh.utils.jinja import JinjaMacroRegistry
|
|
142
|
+
from sqlmesh.utils.windows import IS_WINDOWS, fix_windows_path
|
|
142
143
|
|
|
143
144
|
if t.TYPE_CHECKING:
|
|
144
145
|
import pandas as pd
|
|
@@ -153,6 +154,8 @@ if t.TYPE_CHECKING:
|
|
|
153
154
|
)
|
|
154
155
|
from sqlmesh.core.snapshot import Node
|
|
155
156
|
|
|
157
|
+
from sqlmesh.core.snapshot.definition import Intervals
|
|
158
|
+
|
|
156
159
|
ModelOrSnapshot = t.Union[str, Model, Snapshot]
|
|
157
160
|
NodeOrSnapshot = t.Union[str, Model, StandaloneAudit, Snapshot]
|
|
158
161
|
|
|
@@ -274,6 +277,8 @@ class ExecutionContext(BaseContext):
|
|
|
274
277
|
deployability_index: t.Optional[DeployabilityIndex] = None,
|
|
275
278
|
default_dialect: t.Optional[str] = None,
|
|
276
279
|
default_catalog: t.Optional[str] = None,
|
|
280
|
+
is_restatement: t.Optional[bool] = None,
|
|
281
|
+
parent_intervals: t.Optional[Intervals] = None,
|
|
277
282
|
variables: t.Optional[t.Dict[str, t.Any]] = None,
|
|
278
283
|
blueprint_variables: t.Optional[t.Dict[str, t.Any]] = None,
|
|
279
284
|
):
|
|
@@ -284,6 +289,8 @@ class ExecutionContext(BaseContext):
|
|
|
284
289
|
self._default_dialect = default_dialect
|
|
285
290
|
self._variables = variables or {}
|
|
286
291
|
self._blueprint_variables = blueprint_variables or {}
|
|
292
|
+
self._is_restatement = is_restatement
|
|
293
|
+
self._parent_intervals = parent_intervals
|
|
287
294
|
|
|
288
295
|
@property
|
|
289
296
|
def default_dialect(self) -> t.Optional[str]:
|
|
@@ -308,6 +315,14 @@ class ExecutionContext(BaseContext):
|
|
|
308
315
|
"""Returns the gateway name."""
|
|
309
316
|
return self.var(c.GATEWAY)
|
|
310
317
|
|
|
318
|
+
@property
|
|
319
|
+
def is_restatement(self) -> t.Optional[bool]:
|
|
320
|
+
return self._is_restatement
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def parent_intervals(self) -> t.Optional[Intervals]:
|
|
324
|
+
return self._parent_intervals
|
|
325
|
+
|
|
311
326
|
def var(self, var_name: str, default: t.Optional[t.Any] = None) -> t.Optional[t.Any]:
|
|
312
327
|
"""Returns a variable value."""
|
|
313
328
|
return self._variables.get(var_name.lower(), default)
|
|
@@ -328,6 +343,7 @@ class ExecutionContext(BaseContext):
|
|
|
328
343
|
self.deployability_index,
|
|
329
344
|
self._default_dialect,
|
|
330
345
|
self._default_catalog,
|
|
346
|
+
self._is_restatement,
|
|
331
347
|
variables=variables,
|
|
332
348
|
blueprint_variables=blueprint_variables,
|
|
333
349
|
)
|
|
@@ -368,6 +384,7 @@ class GenericContext(BaseContext, t.Generic[C]):
|
|
|
368
384
|
load: bool = True,
|
|
369
385
|
users: t.Optional[t.List[User]] = None,
|
|
370
386
|
config_loader_kwargs: t.Optional[t.Dict[str, t.Any]] = None,
|
|
387
|
+
selector: t.Optional[t.Type[Selector]] = None,
|
|
371
388
|
):
|
|
372
389
|
self.configs = (
|
|
373
390
|
config
|
|
@@ -390,6 +407,7 @@ class GenericContext(BaseContext, t.Generic[C]):
|
|
|
390
407
|
self._engine_adapter: t.Optional[EngineAdapter] = None
|
|
391
408
|
self._linters: t.Dict[str, Linter] = {}
|
|
392
409
|
self._loaded: bool = False
|
|
410
|
+
self._selector_cls = selector or NativeSelector
|
|
393
411
|
|
|
394
412
|
self.path, self.config = t.cast(t.Tuple[Path, C], next(iter(self.configs.items())))
|
|
395
413
|
|
|
@@ -1429,6 +1447,7 @@ class GenericContext(BaseContext, t.Generic[C]):
|
|
|
1429
1447
|
explain: t.Optional[bool] = None,
|
|
1430
1448
|
ignore_cron: t.Optional[bool] = None,
|
|
1431
1449
|
min_intervals: t.Optional[int] = None,
|
|
1450
|
+
always_include_local_changes: t.Optional[bool] = None,
|
|
1432
1451
|
) -> PlanBuilder:
|
|
1433
1452
|
"""Creates a plan builder.
|
|
1434
1453
|
|
|
@@ -1467,6 +1486,8 @@ class GenericContext(BaseContext, t.Generic[C]):
|
|
|
1467
1486
|
diff_rendered: Whether the diff should compare raw vs rendered models
|
|
1468
1487
|
min_intervals: Adjust the plan start date on a per-model basis in order to ensure at least this many intervals are covered
|
|
1469
1488
|
on every model when checking for missing intervals
|
|
1489
|
+
always_include_local_changes: Usually when restatements are present, local changes in the filesystem are ignored.
|
|
1490
|
+
However, it can be desirable to deploy changes + restatements in the same plan, so this flag overrides the default behaviour.
|
|
1470
1491
|
|
|
1471
1492
|
Returns:
|
|
1472
1493
|
The plan builder.
|
|
@@ -1583,13 +1604,20 @@ class GenericContext(BaseContext, t.Generic[C]):
|
|
|
1583
1604
|
"Selector did not return any models. Please check your model selection and try again."
|
|
1584
1605
|
)
|
|
1585
1606
|
|
|
1607
|
+
if always_include_local_changes is None:
|
|
1608
|
+
# default behaviour - if restatements are detected; we operate entirely out of state and ignore local changes
|
|
1609
|
+
force_no_diff = restate_models is not None or (
|
|
1610
|
+
backfill_models is not None and not backfill_models
|
|
1611
|
+
)
|
|
1612
|
+
else:
|
|
1613
|
+
force_no_diff = not always_include_local_changes
|
|
1614
|
+
|
|
1586
1615
|
snapshots = self._snapshots(models_override)
|
|
1587
1616
|
context_diff = self._context_diff(
|
|
1588
1617
|
environment or c.PROD,
|
|
1589
1618
|
snapshots=snapshots,
|
|
1590
1619
|
create_from=create_from,
|
|
1591
|
-
force_no_diff=
|
|
1592
|
-
or (backfill_models is not None and not backfill_models),
|
|
1620
|
+
force_no_diff=force_no_diff,
|
|
1593
1621
|
ensure_finalized_snapshots=self.config.plan.use_finalized_state,
|
|
1594
1622
|
diff_rendered=diff_rendered,
|
|
1595
1623
|
always_recreate_environment=self.config.plan.always_recreate_environment,
|
|
@@ -1644,6 +1672,14 @@ class GenericContext(BaseContext, t.Generic[C]):
|
|
|
1644
1672
|
elif forward_only is None:
|
|
1645
1673
|
forward_only = self.config.plan.forward_only
|
|
1646
1674
|
|
|
1675
|
+
# When handling prod restatements, only clear intervals from other model versions if we are using full virtual environments
|
|
1676
|
+
# If we are not, then there is no point, because none of the data in dev environments can be promoted by definition
|
|
1677
|
+
restate_all_snapshots = (
|
|
1678
|
+
expanded_restate_models is not None
|
|
1679
|
+
and not is_dev
|
|
1680
|
+
and self.config.virtual_environment_mode.is_full
|
|
1681
|
+
)
|
|
1682
|
+
|
|
1647
1683
|
return self.PLAN_BUILDER_TYPE(
|
|
1648
1684
|
context_diff=context_diff,
|
|
1649
1685
|
start=start,
|
|
@@ -1651,6 +1687,7 @@ class GenericContext(BaseContext, t.Generic[C]):
|
|
|
1651
1687
|
execution_time=execution_time,
|
|
1652
1688
|
apply=self.apply,
|
|
1653
1689
|
restate_models=expanded_restate_models,
|
|
1690
|
+
restate_all_snapshots=restate_all_snapshots,
|
|
1654
1691
|
backfill_models=backfill_models,
|
|
1655
1692
|
no_gaps=no_gaps,
|
|
1656
1693
|
skip_backfill=skip_backfill,
|
|
@@ -1678,9 +1715,9 @@ class GenericContext(BaseContext, t.Generic[C]):
|
|
|
1678
1715
|
console=self.console,
|
|
1679
1716
|
user_provided_flags=user_provided_flags,
|
|
1680
1717
|
selected_models={
|
|
1681
|
-
|
|
1718
|
+
dbt_unique_id
|
|
1682
1719
|
for model in model_selector.expand_model_selections(select_models or "*")
|
|
1683
|
-
if (
|
|
1720
|
+
if (dbt_unique_id := snapshots[model].node.dbt_unique_id)
|
|
1684
1721
|
},
|
|
1685
1722
|
explain=explain or False,
|
|
1686
1723
|
ignore_cron=ignore_cron or False,
|
|
@@ -2242,6 +2279,7 @@ class GenericContext(BaseContext, t.Generic[C]):
|
|
|
2242
2279
|
snapshot=snapshot,
|
|
2243
2280
|
start=start,
|
|
2244
2281
|
end=end,
|
|
2282
|
+
execution_time=execution_time,
|
|
2245
2283
|
snapshots=self.snapshots,
|
|
2246
2284
|
):
|
|
2247
2285
|
audit_id = f"{audit_result.audit.name}"
|
|
@@ -2562,12 +2600,15 @@ class GenericContext(BaseContext, t.Generic[C]):
|
|
|
2562
2600
|
)
|
|
2563
2601
|
|
|
2564
2602
|
def clear_caches(self) -> None:
|
|
2565
|
-
for path in self.configs
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2603
|
+
paths_to_remove = [path / c.CACHE for path in self.configs]
|
|
2604
|
+
paths_to_remove.append(self.cache_dir)
|
|
2605
|
+
|
|
2606
|
+
if IS_WINDOWS:
|
|
2607
|
+
paths_to_remove = [fix_windows_path(path) for path in paths_to_remove]
|
|
2608
|
+
|
|
2609
|
+
for path in paths_to_remove:
|
|
2610
|
+
if path.exists():
|
|
2611
|
+
rmtree(path)
|
|
2571
2612
|
|
|
2572
2613
|
if isinstance(self._state_sync, CachingStateSync):
|
|
2573
2614
|
self._state_sync.clear_cache()
|
|
@@ -2824,19 +2865,14 @@ class GenericContext(BaseContext, t.Generic[C]):
|
|
|
2824
2865
|
# Clean up expired environments by removing their views and schemas
|
|
2825
2866
|
self._cleanup_environments(current_ts=current_ts)
|
|
2826
2867
|
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
on_complete=self.console.update_cleanup_progress,
|
|
2868
|
+
delete_expired_snapshots(
|
|
2869
|
+
self.state_sync,
|
|
2870
|
+
self.snapshot_evaluator,
|
|
2871
|
+
current_ts=current_ts,
|
|
2872
|
+
ignore_ttl=ignore_ttl,
|
|
2873
|
+
console=self.console,
|
|
2874
|
+
batch_size=self.config.janitor.expired_snapshots_batch_size,
|
|
2835
2875
|
)
|
|
2836
|
-
|
|
2837
|
-
# Delete the expired snapshot records from the state sync
|
|
2838
|
-
self.state_sync.delete_expired_snapshots(ignore_ttl=ignore_ttl, current_ts=current_ts)
|
|
2839
|
-
|
|
2840
2876
|
self.state_sync.compact_intervals()
|
|
2841
2877
|
|
|
2842
2878
|
def _cleanup_environments(self, current_ts: t.Optional[int] = None) -> None:
|
|
@@ -2874,7 +2910,7 @@ class GenericContext(BaseContext, t.Generic[C]):
|
|
|
2874
2910
|
def _new_selector(
|
|
2875
2911
|
self, models: t.Optional[UniqueKeyDict[str, Model]] = None, dag: t.Optional[DAG[str]] = None
|
|
2876
2912
|
) -> Selector:
|
|
2877
|
-
return
|
|
2913
|
+
return self._selector_cls(
|
|
2878
2914
|
self.state_reader,
|
|
2879
2915
|
models=models or self._models,
|
|
2880
2916
|
context_path=self.path,
|