sqlmesh 0.217.1.dev1__py3-none-any.whl → 0.227.2.dev20__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 +20 -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 +112 -35
- sqlmesh/core/dialect.py +3 -0
- sqlmesh/core/engine_adapter/_typing.py +2 -0
- sqlmesh/core/engine_adapter/base.py +330 -23
- 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 +50 -2
- sqlmesh/core/engine_adapter/fabric.py +110 -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 +5 -2
- sqlmesh/core/janitor.py +181 -0
- sqlmesh/core/lineage.py +1 -0
- sqlmesh/core/linter/rules/builtin.py +15 -0
- sqlmesh/core/loader.py +17 -30
- sqlmesh/core/macros.py +35 -13
- sqlmesh/core/model/common.py +2 -0
- sqlmesh/core/model/definition.py +72 -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/core/test/discovery.py +4 -0
- 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/integrations/github/cicd/controller.py +6 -2
- sqlmesh/lsp/context.py +4 -2
- sqlmesh/magics.py +1 -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/git.py +3 -1
- 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.dev20.dist-info}/METADATA +5 -5
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev20.dist-info}/RECORD +188 -183
- 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.dev20.dist-info}/WHEEL +0 -0
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev20.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev20.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev20.dist-info}/top_level.txt +0 -0
sqlmesh/dbt/manifest.py
CHANGED
|
@@ -11,7 +11,7 @@ from collections import defaultdict
|
|
|
11
11
|
from functools import cached_property
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
from dbt import
|
|
14
|
+
from dbt import flags
|
|
15
15
|
|
|
16
16
|
from sqlmesh.dbt.util import DBT_VERSION
|
|
17
17
|
from sqlmesh.utils.conversions import make_serializable
|
|
@@ -19,6 +19,8 @@ from sqlmesh.utils.conversions import make_serializable
|
|
|
19
19
|
# Override the file name to prevent dbt commands from invalidating the cache.
|
|
20
20
|
|
|
21
21
|
if DBT_VERSION >= (1, 6, 0):
|
|
22
|
+
from dbt import constants as dbt_constants
|
|
23
|
+
|
|
22
24
|
dbt_constants.PARTIAL_PARSE_FILE_NAME = "sqlmesh_partial_parse.msgpack" # type: ignore
|
|
23
25
|
else:
|
|
24
26
|
from dbt.parser import manifest as dbt_manifest # type: ignore
|
|
@@ -47,7 +49,7 @@ from sqlmesh.core.config import ModelDefaultsConfig
|
|
|
47
49
|
from sqlmesh.dbt.builtin import BUILTIN_FILTERS, BUILTIN_GLOBALS, OVERRIDDEN_MACROS
|
|
48
50
|
from sqlmesh.dbt.common import Dependencies
|
|
49
51
|
from sqlmesh.dbt.model import ModelConfig
|
|
50
|
-
from sqlmesh.dbt.package import HookConfig, MacroConfig
|
|
52
|
+
from sqlmesh.dbt.package import HookConfig, MacroConfig, MaterializationConfig
|
|
51
53
|
from sqlmesh.dbt.seed import SeedConfig
|
|
52
54
|
from sqlmesh.dbt.source import SourceConfig
|
|
53
55
|
from sqlmesh.dbt.target import TargetConfig
|
|
@@ -61,6 +63,7 @@ from sqlmesh.utils.jinja import (
|
|
|
61
63
|
extract_call_names,
|
|
62
64
|
jinja_call_arg_name,
|
|
63
65
|
)
|
|
66
|
+
from sqlglot.helper import ensure_list
|
|
64
67
|
|
|
65
68
|
if t.TYPE_CHECKING:
|
|
66
69
|
from dbt.contracts.graph.manifest import Macro, Manifest
|
|
@@ -75,6 +78,7 @@ SeedConfigs = t.Dict[str, SeedConfig]
|
|
|
75
78
|
SourceConfigs = t.Dict[str, SourceConfig]
|
|
76
79
|
MacroConfigs = t.Dict[str, MacroConfig]
|
|
77
80
|
HookConfigs = t.Dict[str, HookConfig]
|
|
81
|
+
MaterializationConfigs = t.Dict[str, MaterializationConfig]
|
|
78
82
|
|
|
79
83
|
|
|
80
84
|
IGNORED_PACKAGES = {"elementary"}
|
|
@@ -135,6 +139,7 @@ class ManifestHelper:
|
|
|
135
139
|
|
|
136
140
|
self._on_run_start_per_package: t.Dict[str, HookConfigs] = defaultdict(dict)
|
|
137
141
|
self._on_run_end_per_package: t.Dict[str, HookConfigs] = defaultdict(dict)
|
|
142
|
+
self._materializations: MaterializationConfigs = {}
|
|
138
143
|
|
|
139
144
|
def tests(self, package_name: t.Optional[str] = None) -> TestConfigs:
|
|
140
145
|
self._load_all()
|
|
@@ -164,6 +169,10 @@ class ManifestHelper:
|
|
|
164
169
|
self._load_all()
|
|
165
170
|
return self._on_run_end_per_package[package_name or self._project_name]
|
|
166
171
|
|
|
172
|
+
def materializations(self) -> MaterializationConfigs:
|
|
173
|
+
self._load_all()
|
|
174
|
+
return self._materializations
|
|
175
|
+
|
|
167
176
|
@property
|
|
168
177
|
def all_macros(self) -> t.Dict[str, t.Dict[str, MacroInfo]]:
|
|
169
178
|
self._load_all()
|
|
@@ -213,6 +222,7 @@ class ManifestHelper:
|
|
|
213
222
|
self._calls = {k: (v, False) for k, v in (self._call_cache.get("") or {}).items()}
|
|
214
223
|
|
|
215
224
|
self._load_macros()
|
|
225
|
+
self._load_materializations()
|
|
216
226
|
self._load_sources()
|
|
217
227
|
self._load_tests()
|
|
218
228
|
self._load_models_and_seeds()
|
|
@@ -250,11 +260,14 @@ class ManifestHelper:
|
|
|
250
260
|
|
|
251
261
|
def _load_macros(self) -> None:
|
|
252
262
|
for macro in self._manifest.macros.values():
|
|
263
|
+
if macro.name.startswith("materialization_"):
|
|
264
|
+
continue
|
|
265
|
+
|
|
253
266
|
if macro.name.startswith("test_"):
|
|
254
267
|
macro.macro_sql = _convert_jinja_test_to_macro(macro.macro_sql)
|
|
255
268
|
|
|
256
269
|
dependencies = Dependencies(macros=_macro_references(self._manifest, macro))
|
|
257
|
-
if not macro.name.startswith("
|
|
270
|
+
if not macro.name.startswith("test_"):
|
|
258
271
|
dependencies = dependencies.union(
|
|
259
272
|
self._extra_dependencies(macro.macro_sql, macro.package_name)
|
|
260
273
|
)
|
|
@@ -281,6 +294,32 @@ class ManifestHelper:
|
|
|
281
294
|
if pos > 0 and name[pos + 2 :] in adapter_macro_names:
|
|
282
295
|
macro_config.info.is_top_level = True
|
|
283
296
|
|
|
297
|
+
def _load_materializations(self) -> None:
|
|
298
|
+
for macro in self._manifest.macros.values():
|
|
299
|
+
if macro.name.startswith("materialization_"):
|
|
300
|
+
# Extract name and adapter ( "materialization_{name}_{adapter}" or "materialization_{name}_default")
|
|
301
|
+
name_parts = macro.name.split("_")
|
|
302
|
+
if len(name_parts) >= 3:
|
|
303
|
+
mat_name = "_".join(name_parts[1:-1])
|
|
304
|
+
adapter = name_parts[-1]
|
|
305
|
+
|
|
306
|
+
dependencies = Dependencies(macros=_macro_references(self._manifest, macro))
|
|
307
|
+
macro.macro_sql = _strip_jinja_materialization_tags(macro.macro_sql)
|
|
308
|
+
dependencies = dependencies.union(
|
|
309
|
+
self._extra_dependencies(macro.macro_sql, macro.package_name)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
materialization_config = MaterializationConfig(
|
|
313
|
+
name=mat_name,
|
|
314
|
+
adapter=adapter,
|
|
315
|
+
definition=macro.macro_sql,
|
|
316
|
+
dependencies=dependencies,
|
|
317
|
+
path=Path(macro.original_file_path),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
key = f"{mat_name}_{adapter}"
|
|
321
|
+
self._materializations[key] = materialization_config
|
|
322
|
+
|
|
284
323
|
def _load_tests(self) -> None:
|
|
285
324
|
for node in self._manifest.nodes.values():
|
|
286
325
|
if node.resource_type != "test":
|
|
@@ -317,15 +356,17 @@ class ManifestHelper:
|
|
|
317
356
|
)
|
|
318
357
|
|
|
319
358
|
test_model = _test_model(node)
|
|
359
|
+
node_config = _node_base_config(node)
|
|
360
|
+
node_config["name"] = _build_test_name(node, dependencies)
|
|
320
361
|
|
|
321
362
|
test = TestConfig(
|
|
322
363
|
sql=sql,
|
|
323
364
|
model_name=test_model,
|
|
324
365
|
test_kwargs=node.test_metadata.kwargs if hasattr(node, "test_metadata") else {},
|
|
325
366
|
dependencies=dependencies,
|
|
326
|
-
**
|
|
367
|
+
**node_config,
|
|
327
368
|
)
|
|
328
|
-
self._tests_per_package[node.package_name][node.
|
|
369
|
+
self._tests_per_package[node.package_name][node.unique_id] = test
|
|
329
370
|
if test_model:
|
|
330
371
|
self._tests_by_owner[test_model].append(test)
|
|
331
372
|
|
|
@@ -338,10 +379,12 @@ class ManifestHelper:
|
|
|
338
379
|
continue
|
|
339
380
|
|
|
340
381
|
macro_references = _macro_references(self._manifest, node)
|
|
341
|
-
|
|
382
|
+
all_tests = (
|
|
342
383
|
self._tests_by_owner[node.name]
|
|
343
384
|
+ self._tests_by_owner[f"{node.package_name}.{node.name}"]
|
|
344
385
|
)
|
|
386
|
+
# Only include non-standalone tests (tests that don't reference other models)
|
|
387
|
+
tests = [test for test in all_tests if not test.is_standalone]
|
|
345
388
|
node_config = _node_base_config(node)
|
|
346
389
|
|
|
347
390
|
node_name = node.name
|
|
@@ -357,6 +400,12 @@ class ManifestHelper:
|
|
|
357
400
|
dependencies = dependencies.union(
|
|
358
401
|
self._extra_dependencies(sql, node.package_name, track_all_model_attrs=True)
|
|
359
402
|
)
|
|
403
|
+
for hook in [*node_config.get("pre-hook", []), *node_config.get("post-hook", [])]:
|
|
404
|
+
dependencies = dependencies.union(
|
|
405
|
+
self._extra_dependencies(
|
|
406
|
+
hook["sql"], node.package_name, track_all_model_attrs=True
|
|
407
|
+
)
|
|
408
|
+
)
|
|
360
409
|
dependencies = dependencies.union(
|
|
361
410
|
self._flatten_dependencies_from_macros(dependencies.macros, node.package_name)
|
|
362
411
|
)
|
|
@@ -659,7 +708,7 @@ def _macro_references(
|
|
|
659
708
|
return result
|
|
660
709
|
|
|
661
710
|
for macro_node_id in node.depends_on.macros:
|
|
662
|
-
if not macro_node_id:
|
|
711
|
+
if not macro_node_id or macro_node_id == "None":
|
|
663
712
|
continue
|
|
664
713
|
|
|
665
714
|
macro_node = manifest.macros[macro_node_id]
|
|
@@ -697,7 +746,12 @@ def _test_model(node: ManifestNode) -> t.Optional[str]:
|
|
|
697
746
|
attached_node = getattr(node, "attached_node", None)
|
|
698
747
|
if attached_node:
|
|
699
748
|
pieces = attached_node.split(".")
|
|
700
|
-
|
|
749
|
+
if pieces[0] in ["model", "seed"]:
|
|
750
|
+
# versioned models have format "model.package.model_name.v1" (4 parts)
|
|
751
|
+
if len(pieces) == 4:
|
|
752
|
+
return f"{pieces[2]}_{pieces[3]}"
|
|
753
|
+
return pieces[-1]
|
|
754
|
+
return None
|
|
701
755
|
|
|
702
756
|
key_name = getattr(node, "file_key_name", None)
|
|
703
757
|
if key_name:
|
|
@@ -730,3 +784,77 @@ def _convert_jinja_test_to_macro(test_jinja: str) -> str:
|
|
|
730
784
|
macro = macro_tag + test_jinja[match.span()[-1] :]
|
|
731
785
|
|
|
732
786
|
return re.sub(ENDTEST_REGEX, lambda m: m.group(0).replace("endtest", "endmacro"), macro)
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def _strip_jinja_materialization_tags(materialization_jinja: str) -> str:
|
|
790
|
+
MATERIALIZATION_TAG_REGEX = r"\s*{%-?\s*materialization\s+[^%]*%}\s*\n?"
|
|
791
|
+
ENDMATERIALIZATION_REGEX = r"{%-?\s*endmaterialization\s*-?%}\s*\n?"
|
|
792
|
+
|
|
793
|
+
if not re.match(MATERIALIZATION_TAG_REGEX, materialization_jinja):
|
|
794
|
+
return materialization_jinja
|
|
795
|
+
|
|
796
|
+
materialization_jinja = re.sub(
|
|
797
|
+
MATERIALIZATION_TAG_REGEX,
|
|
798
|
+
"",
|
|
799
|
+
materialization_jinja,
|
|
800
|
+
flags=re.IGNORECASE,
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
materialization_jinja = re.sub(
|
|
804
|
+
ENDMATERIALIZATION_REGEX,
|
|
805
|
+
"",
|
|
806
|
+
materialization_jinja,
|
|
807
|
+
flags=re.IGNORECASE,
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
return materialization_jinja.strip()
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def _build_test_name(node: ManifestNode, dependencies: Dependencies) -> str:
|
|
814
|
+
"""
|
|
815
|
+
Build a user-friendly test name that includes the test's model/source, column,
|
|
816
|
+
and args for tests with custom user names. Needed because dbt only generates these
|
|
817
|
+
names for tests that do not specify the "name" field in their YAML definition.
|
|
818
|
+
|
|
819
|
+
Name structure
|
|
820
|
+
- Model test: [namespace]_[test name]_[model name]_[column name]__[arg values]
|
|
821
|
+
- Source test: [namespace]_source_[test name]_[source name]_[table name]_[column name]__[arg values]
|
|
822
|
+
"""
|
|
823
|
+
# standalone test
|
|
824
|
+
if not hasattr(node, "test_metadata"):
|
|
825
|
+
return node.name
|
|
826
|
+
|
|
827
|
+
model_name = _test_model(node)
|
|
828
|
+
source_name = None
|
|
829
|
+
if not model_name and dependencies.sources:
|
|
830
|
+
# extract source and table names
|
|
831
|
+
source_parts = list(dependencies.sources)[0].split(".")
|
|
832
|
+
source_name = "_".join(source_parts) if len(source_parts) == 2 else source_parts[-1]
|
|
833
|
+
entity_name = model_name or source_name or ""
|
|
834
|
+
entity_name = f"_{entity_name}" if entity_name else ""
|
|
835
|
+
|
|
836
|
+
name_prefix = ""
|
|
837
|
+
if namespace := getattr(node.test_metadata, "namespace", None):
|
|
838
|
+
name_prefix += f"{namespace}_"
|
|
839
|
+
if source_name and not model_name:
|
|
840
|
+
name_prefix += "source_"
|
|
841
|
+
|
|
842
|
+
metadata_kwargs = node.test_metadata.kwargs
|
|
843
|
+
arg_val_parts = []
|
|
844
|
+
for arg, val in sorted(metadata_kwargs.items()):
|
|
845
|
+
if arg == "model":
|
|
846
|
+
continue
|
|
847
|
+
if isinstance(val, dict):
|
|
848
|
+
val = list(val.values())
|
|
849
|
+
val = [re.sub("[^0-9a-zA-Z_]+", "_", str(v)) for v in ensure_list(val)]
|
|
850
|
+
arg_val_parts.extend(val)
|
|
851
|
+
unique_args = "__".join(arg_val_parts) if arg_val_parts else ""
|
|
852
|
+
unique_args = f"_{unique_args}" if unique_args else ""
|
|
853
|
+
|
|
854
|
+
auto_name = f"{name_prefix}{node.test_metadata.name}{entity_name}{unique_args}"
|
|
855
|
+
|
|
856
|
+
if node.name == auto_name:
|
|
857
|
+
return node.name
|
|
858
|
+
|
|
859
|
+
custom_prefix = name_prefix if source_name and not model_name else ""
|
|
860
|
+
return f"{custom_prefix}{node.name}{entity_name}{unique_args}"
|
sqlmesh/dbt/model.py
CHANGED
|
@@ -31,6 +31,7 @@ from sqlmesh.core.model.kind import (
|
|
|
31
31
|
OnAdditiveChange,
|
|
32
32
|
on_destructive_change_validator,
|
|
33
33
|
on_additive_change_validator,
|
|
34
|
+
DbtCustomKind,
|
|
34
35
|
)
|
|
35
36
|
from sqlmesh.dbt.basemodel import BaseModelConfig, Materialization, SnapshotStrategy
|
|
36
37
|
from sqlmesh.dbt.common import SqlStr, sql_str_validator
|
|
@@ -40,6 +41,7 @@ from sqlmesh.utils.pydantic import field_validator
|
|
|
40
41
|
if t.TYPE_CHECKING:
|
|
41
42
|
from sqlmesh.core.audit.definition import ModelAudit
|
|
42
43
|
from sqlmesh.dbt.context import DbtContext
|
|
44
|
+
from sqlmesh.dbt.package import MaterializationConfig
|
|
43
45
|
|
|
44
46
|
logger = logging.getLogger(__name__)
|
|
45
47
|
|
|
@@ -170,6 +172,22 @@ class ModelConfig(BaseModelConfig):
|
|
|
170
172
|
return "*"
|
|
171
173
|
return ensure_list(v)
|
|
172
174
|
|
|
175
|
+
@field_validator("updated_at", mode="before")
|
|
176
|
+
@classmethod
|
|
177
|
+
def _validate_updated_at(cls, v: t.Optional[str]) -> t.Optional[str]:
|
|
178
|
+
"""
|
|
179
|
+
Extract column name if updated_at contains a cast.
|
|
180
|
+
|
|
181
|
+
SCDType2ByTimeKind and SCDType2ByColumnKind expect a column, and the casting is done later.
|
|
182
|
+
"""
|
|
183
|
+
if v is None:
|
|
184
|
+
return None
|
|
185
|
+
parsed = d.parse_one(v)
|
|
186
|
+
if isinstance(parsed, exp.Cast) and isinstance(parsed.this, exp.Column):
|
|
187
|
+
return parsed.this.name
|
|
188
|
+
|
|
189
|
+
return v
|
|
190
|
+
|
|
173
191
|
@field_validator("sql", mode="before")
|
|
174
192
|
@classmethod
|
|
175
193
|
def _validate_sql(cls, v: t.Union[str, SqlStr]) -> SqlStr:
|
|
@@ -197,6 +215,14 @@ class ModelConfig(BaseModelConfig):
|
|
|
197
215
|
):
|
|
198
216
|
granularity = v["granularity"]
|
|
199
217
|
raise ConfigError(f"Unexpected granularity '{granularity}' in partition_by '{v}'.")
|
|
218
|
+
if "data_type" in v and v["data_type"].lower() not in (
|
|
219
|
+
"timestamp",
|
|
220
|
+
"date",
|
|
221
|
+
"datetime",
|
|
222
|
+
"int64",
|
|
223
|
+
):
|
|
224
|
+
data_type = v["data_type"]
|
|
225
|
+
raise ConfigError(f"Unexpected data_type '{data_type}' in partition_by '{v}'.")
|
|
200
226
|
return {"data_type": "date", "granularity": "day", **v}
|
|
201
227
|
raise ConfigError(f"Invalid format for partition_by '{v}'")
|
|
202
228
|
|
|
@@ -444,6 +470,19 @@ class ModelConfig(BaseModelConfig):
|
|
|
444
470
|
if materialization == Materialization.DYNAMIC_TABLE:
|
|
445
471
|
return ManagedKind()
|
|
446
472
|
|
|
473
|
+
if materialization == Materialization.CUSTOM:
|
|
474
|
+
if custom_materialization := self._get_custom_materialization(context):
|
|
475
|
+
return DbtCustomKind(
|
|
476
|
+
materialization=self.materialized,
|
|
477
|
+
adapter=custom_materialization.adapter,
|
|
478
|
+
dialect=self.dialect(context),
|
|
479
|
+
definition=custom_materialization.definition,
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
raise ConfigError(
|
|
483
|
+
f"Unknown materialization '{self.materialized}'. Custom materializations must be defined in your dbt project."
|
|
484
|
+
)
|
|
485
|
+
|
|
447
486
|
raise ConfigError(f"{materialization.value} materialization not supported.")
|
|
448
487
|
|
|
449
488
|
def _big_query_partition_by_expr(self, context: DbtContext) -> exp.Expression:
|
|
@@ -483,6 +522,18 @@ class ModelConfig(BaseModelConfig):
|
|
|
483
522
|
dialect="bigquery",
|
|
484
523
|
)
|
|
485
524
|
|
|
525
|
+
def _get_custom_materialization(self, context: DbtContext) -> t.Optional[MaterializationConfig]:
|
|
526
|
+
materializations = context.manifest.materializations()
|
|
527
|
+
name, target_adapter = self.materialized, context.target.dialect
|
|
528
|
+
|
|
529
|
+
adapter_specific_key = f"{name}_{target_adapter}"
|
|
530
|
+
default_key = f"{name}_default"
|
|
531
|
+
if adapter_specific_key in materializations:
|
|
532
|
+
return materializations[adapter_specific_key]
|
|
533
|
+
if default_key in materializations:
|
|
534
|
+
return materializations[default_key]
|
|
535
|
+
return None
|
|
536
|
+
|
|
486
537
|
@property
|
|
487
538
|
def sqlmesh_config_fields(self) -> t.Set[str]:
|
|
488
539
|
return super().sqlmesh_config_fields | {
|
|
@@ -510,39 +561,59 @@ class ModelConfig(BaseModelConfig):
|
|
|
510
561
|
physical_properties: t.Dict[str, t.Any] = {}
|
|
511
562
|
|
|
512
563
|
if self.partition_by:
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
564
|
+
if isinstance(kind, (ViewKind, EmbeddedKind)):
|
|
565
|
+
logger.warning(
|
|
566
|
+
"Ignoring partition_by config for model '%s'; partition_by is not supported for %s.",
|
|
567
|
+
self.name,
|
|
568
|
+
"views" if isinstance(kind, ViewKind) else "ephemeral models",
|
|
569
|
+
)
|
|
570
|
+
elif context.target.dialect == "snowflake":
|
|
571
|
+
logger.warning(
|
|
572
|
+
"Ignoring partition_by config for model '%s' targeting %s. The partition_by config is not supported for Snowflake.",
|
|
573
|
+
self.name,
|
|
574
|
+
context.target.dialect,
|
|
575
|
+
)
|
|
576
|
+
else:
|
|
577
|
+
partitioned_by = []
|
|
578
|
+
if isinstance(self.partition_by, list):
|
|
579
|
+
for p in self.partition_by:
|
|
580
|
+
try:
|
|
581
|
+
partitioned_by.append(d.parse_one(p, dialect=model_dialect))
|
|
582
|
+
except SqlglotError as e:
|
|
583
|
+
raise ConfigError(
|
|
584
|
+
f"Failed to parse model '{self.canonical_name(context)}' partition_by field '{p}' in '{self.path}': {e}"
|
|
585
|
+
) from e
|
|
586
|
+
elif isinstance(self.partition_by, dict):
|
|
587
|
+
if context.target.dialect == "bigquery":
|
|
588
|
+
partitioned_by.append(self._big_query_partition_by_expr(context))
|
|
589
|
+
else:
|
|
590
|
+
logger.warning(
|
|
591
|
+
"Ignoring partition_by config for model '%s' targeting %s. The format of the config field is only supported for BigQuery.",
|
|
592
|
+
self.name,
|
|
593
|
+
context.target.dialect,
|
|
594
|
+
)
|
|
531
595
|
|
|
532
|
-
|
|
533
|
-
|
|
596
|
+
if partitioned_by:
|
|
597
|
+
optional_kwargs["partitioned_by"] = partitioned_by
|
|
534
598
|
|
|
535
599
|
if self.cluster_by:
|
|
536
|
-
if isinstance(kind, ViewKind):
|
|
600
|
+
if isinstance(kind, (ViewKind, EmbeddedKind)):
|
|
537
601
|
logger.warning(
|
|
538
|
-
"Ignoring cluster_by config for model '%s'; cluster_by is not supported for
|
|
602
|
+
"Ignoring cluster_by config for model '%s'; cluster_by is not supported for %s.",
|
|
539
603
|
self.name,
|
|
604
|
+
"views" if isinstance(kind, ViewKind) else "ephemeral models",
|
|
540
605
|
)
|
|
541
606
|
else:
|
|
542
607
|
clustered_by = []
|
|
543
608
|
for c in self.cluster_by:
|
|
544
609
|
try:
|
|
545
|
-
|
|
610
|
+
cluster_expr = exp.maybe_parse(
|
|
611
|
+
c, into=exp.Cluster, prefix="CLUSTER BY", dialect=model_dialect
|
|
612
|
+
)
|
|
613
|
+
for expr in cluster_expr.expressions:
|
|
614
|
+
clustered_by.append(
|
|
615
|
+
expr.this if isinstance(expr, exp.Ordered) else expr
|
|
616
|
+
)
|
|
546
617
|
except SqlglotError as e:
|
|
547
618
|
raise ConfigError(
|
|
548
619
|
f"Failed to parse model '{self.canonical_name(context)}' cluster_by field '{c}' in '{self.path}': {e}"
|
|
@@ -644,11 +715,20 @@ class ModelConfig(BaseModelConfig):
|
|
|
644
715
|
if physical_properties:
|
|
645
716
|
model_kwargs["physical_properties"] = physical_properties
|
|
646
717
|
|
|
718
|
+
kind = self.model_kind(context)
|
|
719
|
+
|
|
720
|
+
# A falsy grants config (None or {}) is considered as unmanaged per dbt semantics
|
|
721
|
+
if self.grants and kind.supports_grants:
|
|
722
|
+
model_kwargs["grants"] = self.grants
|
|
723
|
+
|
|
647
724
|
allow_partials = model_kwargs.pop("allow_partials", None)
|
|
648
725
|
if allow_partials is None:
|
|
649
726
|
# Set allow_partials to True for dbt models to preserve the original semantics.
|
|
650
727
|
allow_partials = True
|
|
651
728
|
|
|
729
|
+
# pop begin for all models so we don't pass it through for non-incremental materializations
|
|
730
|
+
# (happens if model config is microbatch but project config overrides)
|
|
731
|
+
begin = model_kwargs.pop("begin", None)
|
|
652
732
|
if kind.is_incremental:
|
|
653
733
|
if self.batch_size and isinstance(self.batch_size, str):
|
|
654
734
|
if "interval_unit" in model_kwargs:
|
|
@@ -658,7 +738,7 @@ class ModelConfig(BaseModelConfig):
|
|
|
658
738
|
else:
|
|
659
739
|
model_kwargs["interval_unit"] = self.batch_size
|
|
660
740
|
self.batch_size = None
|
|
661
|
-
if begin
|
|
741
|
+
if begin:
|
|
662
742
|
if "start" in model_kwargs:
|
|
663
743
|
get_console().log_warning(
|
|
664
744
|
f"Both 'begin' and 'start' are set for model '{self.canonical_name(context)}'. 'start' will be used."
|
|
@@ -687,7 +767,7 @@ class ModelConfig(BaseModelConfig):
|
|
|
687
767
|
extract_dependencies_from_query=False,
|
|
688
768
|
allow_partials=allow_partials,
|
|
689
769
|
virtual_environment_mode=virtual_environment_mode,
|
|
690
|
-
|
|
770
|
+
dbt_node_info=self.node_info,
|
|
691
771
|
**optional_kwargs,
|
|
692
772
|
**model_kwargs,
|
|
693
773
|
)
|
sqlmesh/dbt/package.py
CHANGED
|
@@ -37,6 +37,16 @@ class HookConfig(PydanticModel):
|
|
|
37
37
|
dependencies: Dependencies
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
class MaterializationConfig(PydanticModel):
|
|
41
|
+
"""Class to contain custom materialization configuration."""
|
|
42
|
+
|
|
43
|
+
name: str
|
|
44
|
+
adapter: str
|
|
45
|
+
definition: str
|
|
46
|
+
dependencies: Dependencies
|
|
47
|
+
path: Path
|
|
48
|
+
|
|
49
|
+
|
|
40
50
|
class Package(PydanticModel):
|
|
41
51
|
"""Class to contain package configuration"""
|
|
42
52
|
|
|
@@ -47,6 +57,7 @@ class Package(PydanticModel):
|
|
|
47
57
|
models: t.Dict[str, ModelConfig]
|
|
48
58
|
variables: t.Dict[str, t.Any]
|
|
49
59
|
macros: t.Dict[str, MacroConfig]
|
|
60
|
+
materializations: t.Dict[str, MaterializationConfig]
|
|
50
61
|
on_run_start: t.Dict[str, HookConfig]
|
|
51
62
|
on_run_end: t.Dict[str, HookConfig]
|
|
52
63
|
files: t.Set[Path]
|
|
@@ -94,6 +105,7 @@ class PackageLoader:
|
|
|
94
105
|
models = _fix_paths(self._context.manifest.models(package_name), package_root)
|
|
95
106
|
seeds = _fix_paths(self._context.manifest.seeds(package_name), package_root)
|
|
96
107
|
macros = _fix_paths(self._context.manifest.macros(package_name), package_root)
|
|
108
|
+
materializations = _fix_paths(self._context.manifest.materializations(), package_root)
|
|
97
109
|
on_run_start = _fix_paths(self._context.manifest.on_run_start(package_name), package_root)
|
|
98
110
|
on_run_end = _fix_paths(self._context.manifest.on_run_end(package_name), package_root)
|
|
99
111
|
sources = self._context.manifest.sources(package_name)
|
|
@@ -114,13 +126,16 @@ class PackageLoader:
|
|
|
114
126
|
seeds=seeds,
|
|
115
127
|
variables=package_variables,
|
|
116
128
|
macros=macros,
|
|
129
|
+
materializations=materializations,
|
|
117
130
|
files=config_paths,
|
|
118
131
|
on_run_start=on_run_start,
|
|
119
132
|
on_run_end=on_run_end,
|
|
120
133
|
)
|
|
121
134
|
|
|
122
135
|
|
|
123
|
-
T = t.TypeVar(
|
|
136
|
+
T = t.TypeVar(
|
|
137
|
+
"T", TestConfig, ModelConfig, MacroConfig, MaterializationConfig, SeedConfig, HookConfig
|
|
138
|
+
)
|
|
124
139
|
|
|
125
140
|
|
|
126
141
|
def _fix_paths(configs: t.Dict[str, T], package_root: Path) -> t.Dict[str, T]:
|
sqlmesh/dbt/profile.py
CHANGED
|
@@ -60,7 +60,7 @@ class Profile:
|
|
|
60
60
|
if not context.profile_name:
|
|
61
61
|
raise ConfigError(f"{project_file.stem} must include project name.")
|
|
62
62
|
|
|
63
|
-
profile_filepath = cls._find_profile(context.project_root)
|
|
63
|
+
profile_filepath = cls._find_profile(context.project_root, context.profiles_dir)
|
|
64
64
|
if not profile_filepath:
|
|
65
65
|
raise ConfigError(f"{cls.PROFILE_FILE} not found.")
|
|
66
66
|
|
|
@@ -68,8 +68,8 @@ class Profile:
|
|
|
68
68
|
return Profile(profile_filepath, target_name, target)
|
|
69
69
|
|
|
70
70
|
@classmethod
|
|
71
|
-
def _find_profile(cls, project_root: Path) -> t.Optional[Path]:
|
|
72
|
-
dir = os.environ.get("DBT_PROFILES_DIR", "")
|
|
71
|
+
def _find_profile(cls, project_root: Path, profiles_dir: t.Optional[Path]) -> t.Optional[Path]:
|
|
72
|
+
dir = os.environ.get("DBT_PROFILES_DIR", profiles_dir or "")
|
|
73
73
|
path = Path(project_root, dir, cls.PROFILE_FILE)
|
|
74
74
|
if path.exists():
|
|
75
75
|
return path
|
sqlmesh/dbt/project.py
CHANGED
|
@@ -99,16 +99,21 @@ class Project:
|
|
|
99
99
|
package = package_loader.load(path.parent)
|
|
100
100
|
packages[package.name] = package
|
|
101
101
|
|
|
102
|
+
# Variable resolution precedence:
|
|
103
|
+
# 1. Variable overrides
|
|
104
|
+
# 2. Package-scoped variables in the root project's dbt_project.yml
|
|
105
|
+
# 3. Global project variables in the root project's dbt_project.yml
|
|
106
|
+
# 4. Variables in the package's dbt_project.yml
|
|
102
107
|
all_project_variables = {**(project_yaml.get("vars") or {}), **(variable_overrides or {})}
|
|
103
108
|
for name, package in packages.items():
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
package.variables.update(
|
|
108
|
-
|
|
109
|
-
if name == context.project_name:
|
|
110
|
-
package.variables.update(all_project_variables)
|
|
109
|
+
if isinstance(all_project_variables.get(name), dict):
|
|
110
|
+
project_vars_copy = all_project_variables.copy()
|
|
111
|
+
package_scoped_vars = project_vars_copy.pop(name)
|
|
112
|
+
package.variables.update(project_vars_copy)
|
|
113
|
+
package.variables.update(package_scoped_vars)
|
|
111
114
|
else:
|
|
115
|
+
package.variables.update(all_project_variables)
|
|
116
|
+
if variable_overrides:
|
|
112
117
|
package.variables.update(variable_overrides)
|
|
113
118
|
|
|
114
119
|
return Project(context, profile, packages)
|
sqlmesh/dbt/seed.py
CHANGED
|
@@ -92,7 +92,7 @@ class SeedConfig(BaseModelConfig):
|
|
|
92
92
|
audit_definitions=audit_definitions,
|
|
93
93
|
virtual_environment_mode=virtual_environment_mode,
|
|
94
94
|
start=self.start or context.sqlmesh_config.model_defaults.start,
|
|
95
|
-
|
|
95
|
+
dbt_node_info=self.node_info,
|
|
96
96
|
**kwargs,
|
|
97
97
|
)
|
|
98
98
|
|
sqlmesh/dbt/source.py
CHANGED
|
@@ -36,6 +36,7 @@ class SourceConfig(GeneralConfig):
|
|
|
36
36
|
# DBT configuration fields
|
|
37
37
|
name: str = ""
|
|
38
38
|
source_name_: str = Field("", alias="source_name")
|
|
39
|
+
fqn_: t.List[str] = Field(default_factory=list, alias="fqn")
|
|
39
40
|
database: t.Optional[str] = None
|
|
40
41
|
schema_: t.Optional[str] = Field(None, alias="schema")
|
|
41
42
|
identifier: t.Optional[str] = None
|
|
@@ -64,6 +65,10 @@ class SourceConfig(GeneralConfig):
|
|
|
64
65
|
def config_name(self) -> str:
|
|
65
66
|
return f"{self.source_name_}.{self.name}"
|
|
66
67
|
|
|
68
|
+
@property
|
|
69
|
+
def fqn(self) -> str:
|
|
70
|
+
return ".".join(self.fqn_)
|
|
71
|
+
|
|
67
72
|
def canonical_name(self, context: DbtContext) -> str:
|
|
68
73
|
if self._canonical_name is None:
|
|
69
74
|
source = context.get_callable_macro("source")
|
|
@@ -74,7 +79,7 @@ class SourceConfig(GeneralConfig):
|
|
|
74
79
|
relation = source(self.source_name_, self.name)
|
|
75
80
|
except Exception as e:
|
|
76
81
|
raise ConfigError(
|
|
77
|
-
f"'source' macro failed for '{self.config_name}' with
|
|
82
|
+
f"'source' macro failed for '{self.config_name}' with exception '{e}'."
|
|
78
83
|
)
|
|
79
84
|
|
|
80
85
|
relation = relation.quote(
|
sqlmesh/dbt/target.py
CHANGED
|
@@ -45,10 +45,24 @@ IncrementalKind = t.Union[
|
|
|
45
45
|
|
|
46
46
|
# We only serialize a subset of fields in order to avoid persisting sensitive information
|
|
47
47
|
SERIALIZABLE_FIELDS = {
|
|
48
|
-
|
|
48
|
+
# core
|
|
49
49
|
"name",
|
|
50
|
-
"database",
|
|
51
50
|
"schema_",
|
|
51
|
+
"type",
|
|
52
|
+
"threads",
|
|
53
|
+
# snowflake
|
|
54
|
+
"database",
|
|
55
|
+
"warehouse",
|
|
56
|
+
"user",
|
|
57
|
+
"role",
|
|
58
|
+
"account",
|
|
59
|
+
# postgres/redshift
|
|
60
|
+
"dbname",
|
|
61
|
+
"host",
|
|
62
|
+
"port",
|
|
63
|
+
# bigquery
|
|
64
|
+
"project",
|
|
65
|
+
"dataset",
|
|
52
66
|
}
|
|
53
67
|
|
|
54
68
|
SCHEMA_DIFFER_OVERRIDES = {
|
|
@@ -587,12 +601,17 @@ class BigQueryConfig(TargetConfig):
|
|
|
587
601
|
if not isinstance(data, dict):
|
|
588
602
|
return data
|
|
589
603
|
|
|
590
|
-
|
|
591
|
-
|
|
604
|
+
# dbt treats schema and dataset interchangeably
|
|
605
|
+
schema = data.get("schema") or data.get("dataset")
|
|
606
|
+
if not schema:
|
|
592
607
|
raise ConfigError("Either schema or dataset must be set")
|
|
593
|
-
data["
|
|
594
|
-
|
|
608
|
+
data["dataset"] = data["schema"] = schema
|
|
609
|
+
|
|
610
|
+
# dbt treats database and project interchangeably
|
|
611
|
+
database = data.get("database") or data.get("project")
|
|
612
|
+
if not database:
|
|
595
613
|
raise ConfigError("Either database or project must be set")
|
|
614
|
+
data["database"] = data["project"] = database
|
|
596
615
|
|
|
597
616
|
return data
|
|
598
617
|
|