sqlmesh 0.213.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/main.py +0 -44
- sqlmesh/cli/project_init.py +11 -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 +81 -3
- sqlmesh/core/constants.py +1 -1
- sqlmesh/core/context.py +69 -26
- 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 +10 -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/environment.py +2 -0
- sqlmesh/core/janitor.py +181 -0
- sqlmesh/core/lineage.py +1 -0
- sqlmesh/core/linter/definition.py +13 -13
- sqlmesh/core/linter/rules/builtin.py +29 -0
- sqlmesh/core/macros.py +35 -13
- sqlmesh/core/model/common.py +2 -0
- sqlmesh/core/model/definition.py +82 -28
- sqlmesh/core/model/kind.py +66 -2
- sqlmesh/core/model/meta.py +108 -4
- sqlmesh/core/node.py +101 -1
- sqlmesh/core/plan/builder.py +18 -10
- sqlmesh/core/plan/common.py +199 -2
- sqlmesh/core/plan/definition.py +25 -6
- sqlmesh/core/plan/evaluator.py +75 -113
- sqlmesh/core/plan/explainer.py +90 -8
- sqlmesh/core/plan/stages.py +42 -21
- sqlmesh/core/renderer.py +78 -32
- sqlmesh/core/scheduler.py +102 -22
- sqlmesh/core/selector.py +137 -9
- sqlmesh/core/signal.py +64 -1
- sqlmesh/core/snapshot/__init__.py +2 -0
- sqlmesh/core/snapshot/definition.py +146 -34
- sqlmesh/core/snapshot/evaluator.py +689 -124
- sqlmesh/core/state_sync/__init__.py +0 -1
- sqlmesh/core/state_sync/base.py +55 -33
- sqlmesh/core/state_sync/cache.py +12 -7
- sqlmesh/core/state_sync/common.py +216 -111
- sqlmesh/core/state_sync/db/environment.py +6 -4
- sqlmesh/core/state_sync/db/facade.py +42 -24
- sqlmesh/core/state_sync/db/interval.py +27 -7
- sqlmesh/core/state_sync/db/migrator.py +34 -16
- sqlmesh/core/state_sync/db/snapshot.py +177 -169
- sqlmesh/core/table_diff.py +2 -2
- sqlmesh/core/test/context.py +2 -0
- sqlmesh/core/test/definition.py +14 -9
- sqlmesh/dbt/adapter.py +22 -16
- sqlmesh/dbt/basemodel.py +75 -56
- sqlmesh/dbt/builtin.py +116 -12
- sqlmesh/dbt/column.py +17 -5
- sqlmesh/dbt/common.py +19 -5
- sqlmesh/dbt/context.py +14 -1
- sqlmesh/dbt/loader.py +61 -9
- sqlmesh/dbt/manifest.py +174 -16
- sqlmesh/dbt/model.py +183 -85
- sqlmesh/dbt/package.py +16 -1
- sqlmesh/dbt/profile.py +3 -3
- sqlmesh/dbt/project.py +12 -7
- sqlmesh/dbt/seed.py +6 -1
- sqlmesh/dbt/source.py +13 -1
- sqlmesh/dbt/target.py +25 -6
- sqlmesh/dbt/test.py +36 -5
- sqlmesh/migrations/v0000_baseline.py +95 -0
- sqlmesh/migrations/v0061_mysql_fix_blob_text_type.py +5 -7
- sqlmesh/migrations/v0062_add_model_gateway.py +5 -1
- sqlmesh/migrations/v0063_change_signals.py +5 -3
- sqlmesh/migrations/v0064_join_when_matched_strings.py +5 -3
- sqlmesh/migrations/v0065_add_model_optimize.py +5 -1
- sqlmesh/migrations/v0066_add_auto_restatements.py +8 -3
- sqlmesh/migrations/v0067_add_tsql_date_full_precision.py +5 -1
- sqlmesh/migrations/v0068_include_unrendered_query_in_metadata_hash.py +5 -1
- sqlmesh/migrations/v0069_update_dev_table_suffix.py +5 -3
- sqlmesh/migrations/v0070_include_grains_in_metadata_hash.py +5 -1
- sqlmesh/migrations/v0071_add_dev_version_to_intervals.py +9 -5
- sqlmesh/migrations/v0072_add_environment_statements.py +5 -3
- sqlmesh/migrations/v0073_remove_symbolic_disable_restatement.py +5 -3
- sqlmesh/migrations/v0074_add_partition_by_time_column_property.py +5 -1
- sqlmesh/migrations/v0075_remove_validate_query.py +5 -3
- sqlmesh/migrations/v0076_add_cron_tz.py +5 -1
- sqlmesh/migrations/v0077_fix_column_type_hash_calculation.py +5 -1
- sqlmesh/migrations/v0078_warn_if_non_migratable_python_env.py +5 -3
- sqlmesh/migrations/v0079_add_gateway_managed_property.py +10 -5
- sqlmesh/migrations/v0080_add_batch_size_to_scd_type_2_models.py +5 -1
- sqlmesh/migrations/v0081_update_partitioned_by.py +5 -3
- sqlmesh/migrations/v0082_warn_if_incorrectly_duplicated_statements.py +5 -3
- sqlmesh/migrations/v0083_use_sql_for_scd_time_data_type_data_hash.py +5 -1
- sqlmesh/migrations/v0084_normalize_quote_when_matched_and_merge_filter.py +5 -1
- sqlmesh/migrations/v0085_deterministic_repr.py +5 -3
- sqlmesh/migrations/v0086_check_deterministic_bug.py +5 -3
- sqlmesh/migrations/v0087_normalize_blueprint_variables.py +5 -3
- sqlmesh/migrations/v0088_warn_about_variable_python_env_diffs.py +5 -3
- sqlmesh/migrations/v0089_add_virtual_environment_mode.py +5 -1
- sqlmesh/migrations/v0090_add_forward_only_column.py +9 -5
- sqlmesh/migrations/v0091_on_additive_change.py +5 -1
- sqlmesh/migrations/v0092_warn_about_dbt_data_type_diff.py +5 -3
- sqlmesh/migrations/v0093_use_raw_sql_in_fingerprint.py +5 -1
- sqlmesh/migrations/v0094_add_dev_version_and_fingerprint_columns.py +123 -0
- sqlmesh/migrations/v0095_warn_about_dbt_raw_sql_diff.py +49 -0
- sqlmesh/migrations/v0096_remove_plan_dags_table.py +13 -0
- sqlmesh/migrations/v0097_add_dbt_name_in_node.py +9 -0
- sqlmesh/migrations/{v0060_move_audits_to_model.py → v0098_add_dbt_node_info_in_node.py} +33 -16
- 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/connection_pool.py +2 -1
- sqlmesh/utils/dag.py +65 -10
- sqlmesh/utils/date.py +8 -1
- sqlmesh/utils/errors.py +8 -0
- sqlmesh/utils/jinja.py +54 -4
- sqlmesh/utils/pydantic.py +6 -6
- sqlmesh/utils/windows.py +13 -3
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/METADATA +7 -10
- sqlmesh-0.227.2.dev4.dist-info/RECORD +370 -0
- 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
- sqlmesh/integrations/llm.py +0 -56
- sqlmesh/migrations/v0001_init.py +0 -60
- sqlmesh/migrations/v0002_remove_identify.py +0 -5
- sqlmesh/migrations/v0003_move_batch_size.py +0 -34
- sqlmesh/migrations/v0004_environmnent_add_finalized_at.py +0 -23
- sqlmesh/migrations/v0005_create_seed_table.py +0 -24
- sqlmesh/migrations/v0006_change_seed_hash.py +0 -5
- sqlmesh/migrations/v0007_env_table_info_to_kind.py +0 -99
- sqlmesh/migrations/v0008_create_intervals_table.py +0 -38
- sqlmesh/migrations/v0009_remove_pre_post_hooks.py +0 -62
- sqlmesh/migrations/v0010_seed_hash_batch_size.py +0 -5
- sqlmesh/migrations/v0011_add_model_kind_name.py +0 -63
- sqlmesh/migrations/v0012_update_jinja_expressions.py +0 -86
- sqlmesh/migrations/v0013_serde_using_model_dialects.py +0 -87
- sqlmesh/migrations/v0014_fix_dev_intervals.py +0 -14
- sqlmesh/migrations/v0015_environment_add_promoted_snapshot_ids.py +0 -26
- sqlmesh/migrations/v0016_fix_windows_path.py +0 -59
- sqlmesh/migrations/v0017_fix_windows_seed_path.py +0 -55
- sqlmesh/migrations/v0018_rename_snapshot_model_to_node.py +0 -53
- sqlmesh/migrations/v0019_add_env_suffix_target.py +0 -28
- sqlmesh/migrations/v0020_remove_redundant_attributes_from_dbt_models.py +0 -80
- sqlmesh/migrations/v0021_fix_table_properties.py +0 -62
- sqlmesh/migrations/v0022_move_project_to_model.py +0 -54
- sqlmesh/migrations/v0023_fix_added_models_with_forward_only_parents.py +0 -65
- sqlmesh/migrations/v0024_replace_model_kind_name_enum_with_value.py +0 -55
- sqlmesh/migrations/v0025_fix_intervals_and_missing_change_category.py +0 -117
- sqlmesh/migrations/v0026_remove_dialect_from_seed.py +0 -55
- sqlmesh/migrations/v0027_minute_interval_to_five.py +0 -57
- sqlmesh/migrations/v0028_add_plan_dags_table.py +0 -29
- sqlmesh/migrations/v0029_generate_schema_types_using_dialect.py +0 -69
- sqlmesh/migrations/v0030_update_unrestorable_snapshots.py +0 -65
- sqlmesh/migrations/v0031_remove_dbt_target_fields.py +0 -65
- sqlmesh/migrations/v0032_add_sqlmesh_version.py +0 -25
- sqlmesh/migrations/v0033_mysql_fix_blob_text_type.py +0 -45
- sqlmesh/migrations/v0034_add_default_catalog.py +0 -367
- sqlmesh/migrations/v0035_add_catalog_name_override.py +0 -22
- sqlmesh/migrations/v0036_delete_plan_dags_bug_fix.py +0 -14
- sqlmesh/migrations/v0037_remove_dbt_is_incremental_macro.py +0 -61
- sqlmesh/migrations/v0038_add_expiration_ts_to_snapshot.py +0 -73
- sqlmesh/migrations/v0039_include_environment_in_plan_dag_spec.py +0 -68
- sqlmesh/migrations/v0040_add_previous_finalized_snapshots.py +0 -26
- sqlmesh/migrations/v0041_remove_hash_raw_query_attribute.py +0 -59
- sqlmesh/migrations/v0042_trim_indirect_versions.py +0 -66
- sqlmesh/migrations/v0043_fix_remove_obsolete_attributes_in_plan_dags.py +0 -61
- sqlmesh/migrations/v0044_quote_identifiers_in_model_attributes.py +0 -5
- sqlmesh/migrations/v0045_move_gateway_variable.py +0 -70
- sqlmesh/migrations/v0046_add_batch_concurrency.py +0 -8
- sqlmesh/migrations/v0047_change_scd_string_to_column.py +0 -5
- sqlmesh/migrations/v0048_drop_indirect_versions.py +0 -59
- sqlmesh/migrations/v0049_replace_identifier_with_version_in_seeds_table.py +0 -57
- sqlmesh/migrations/v0050_drop_seeds_table.py +0 -11
- sqlmesh/migrations/v0051_rename_column_descriptions.py +0 -65
- sqlmesh/migrations/v0052_add_normalize_name_in_environment_naming_info.py +0 -28
- sqlmesh/migrations/v0053_custom_model_kind_extra_attributes.py +0 -5
- sqlmesh/migrations/v0054_fix_trailing_comments.py +0 -5
- sqlmesh/migrations/v0055_add_updated_ts_unpaused_ts_ttl_ms_unrestorable_to_snapshot.py +0 -132
- sqlmesh/migrations/v0056_restore_table_indexes.py +0 -118
- sqlmesh/migrations/v0057_add_table_format.py +0 -5
- sqlmesh/migrations/v0058_add_requirements.py +0 -26
- sqlmesh/migrations/v0059_add_physical_version.py +0 -5
- sqlmesh-0.213.1.dev1.dist-info/RECORD +0 -421
- web/client/dist/assets/Lineage-D0Hgdz2v.js +0 -1
- web/client/dist/assets/context-DgX0fp2E.js +0 -68
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/WHEEL +0 -0
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/top_level.txt +0 -0
sqlmesh/dbt/model.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import datetime
|
|
4
4
|
import typing as t
|
|
5
|
+
import logging
|
|
5
6
|
|
|
6
7
|
from sqlglot import exp
|
|
7
8
|
from sqlglot.errors import SqlglotError
|
|
@@ -24,18 +25,33 @@ from sqlmesh.core.model import (
|
|
|
24
25
|
ManagedKind,
|
|
25
26
|
create_sql_model,
|
|
26
27
|
)
|
|
27
|
-
from sqlmesh.core.model.kind import
|
|
28
|
+
from sqlmesh.core.model.kind import (
|
|
29
|
+
SCDType2ByTimeKind,
|
|
30
|
+
OnDestructiveChange,
|
|
31
|
+
OnAdditiveChange,
|
|
32
|
+
on_destructive_change_validator,
|
|
33
|
+
on_additive_change_validator,
|
|
34
|
+
DbtCustomKind,
|
|
35
|
+
)
|
|
28
36
|
from sqlmesh.dbt.basemodel import BaseModelConfig, Materialization, SnapshotStrategy
|
|
29
|
-
from sqlmesh.dbt.common import SqlStr,
|
|
37
|
+
from sqlmesh.dbt.common import SqlStr, sql_str_validator
|
|
30
38
|
from sqlmesh.utils.errors import ConfigError
|
|
31
39
|
from sqlmesh.utils.pydantic import field_validator
|
|
32
40
|
|
|
33
41
|
if t.TYPE_CHECKING:
|
|
34
42
|
from sqlmesh.core.audit.definition import ModelAudit
|
|
35
43
|
from sqlmesh.dbt.context import DbtContext
|
|
44
|
+
from sqlmesh.dbt.package import MaterializationConfig
|
|
45
|
+
|
|
46
|
+
logger = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
logger = logging.getLogger(__name__)
|
|
36
50
|
|
|
37
51
|
|
|
38
|
-
|
|
52
|
+
INCREMENTAL_BY_TIME_RANGE_STRATEGIES = set(
|
|
53
|
+
["delete+insert", "insert_overwrite", "microbatch", "incremental_by_time_range"]
|
|
54
|
+
)
|
|
39
55
|
INCREMENTAL_BY_UNIQUE_KEY_STRATEGIES = set(["merge"])
|
|
40
56
|
|
|
41
57
|
|
|
@@ -71,16 +87,19 @@ class ModelConfig(BaseModelConfig):
|
|
|
71
87
|
|
|
72
88
|
# sqlmesh fields
|
|
73
89
|
sql: SqlStr = SqlStr("")
|
|
74
|
-
time_column: t.Optional[str] = None
|
|
90
|
+
time_column: t.Optional[t.Union[str, t.Dict[str, str]]] = None
|
|
75
91
|
cron: t.Optional[str] = None
|
|
76
92
|
interval_unit: t.Optional[str] = None
|
|
77
93
|
batch_concurrency: t.Optional[int] = None
|
|
78
94
|
forward_only: bool = True
|
|
79
95
|
disable_restatement: t.Optional[bool] = None
|
|
80
|
-
allow_partials:
|
|
96
|
+
allow_partials: bool = True
|
|
81
97
|
physical_version: t.Optional[str] = None
|
|
82
98
|
auto_restatement_cron: t.Optional[str] = None
|
|
83
99
|
auto_restatement_intervals: t.Optional[int] = None
|
|
100
|
+
partition_by_time_column: t.Optional[bool] = None
|
|
101
|
+
on_destructive_change: t.Optional[OnDestructiveChange] = None
|
|
102
|
+
on_additive_change: t.Optional[OnAdditiveChange] = None
|
|
84
103
|
|
|
85
104
|
# DBT configuration fields
|
|
86
105
|
cluster_by: t.Optional[t.List[str]] = None
|
|
@@ -132,11 +151,9 @@ class ModelConfig(BaseModelConfig):
|
|
|
132
151
|
inserts_only: t.Optional[bool] = None
|
|
133
152
|
incremental_predicates: t.Optional[t.List[str]] = None
|
|
134
153
|
|
|
135
|
-
# Private fields
|
|
136
|
-
_sql_embedded_config: t.Optional[SqlStr] = None
|
|
137
|
-
_sql_no_config: t.Optional[SqlStr] = None
|
|
138
|
-
|
|
139
154
|
_sql_validator = sql_str_validator
|
|
155
|
+
_on_destructive_change_validator = on_destructive_change_validator
|
|
156
|
+
_on_additive_change_validator = on_additive_change_validator
|
|
140
157
|
|
|
141
158
|
@field_validator(
|
|
142
159
|
"unique_key",
|
|
@@ -155,6 +172,22 @@ class ModelConfig(BaseModelConfig):
|
|
|
155
172
|
return "*"
|
|
156
173
|
return ensure_list(v)
|
|
157
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
|
+
|
|
158
191
|
@field_validator("sql", mode="before")
|
|
159
192
|
@classmethod
|
|
160
193
|
def _validate_sql(cls, v: t.Union[str, SqlStr]) -> SqlStr:
|
|
@@ -182,6 +215,14 @@ class ModelConfig(BaseModelConfig):
|
|
|
182
215
|
):
|
|
183
216
|
granularity = v["granularity"]
|
|
184
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}'.")
|
|
185
226
|
return {"data_type": "date", "granularity": "day", **v}
|
|
186
227
|
raise ConfigError(f"Invalid format for partition_by '{v}'")
|
|
187
228
|
|
|
@@ -228,17 +269,6 @@ class ModelConfig(BaseModelConfig):
|
|
|
228
269
|
def table_schema(self) -> str:
|
|
229
270
|
return self.target_schema or super().table_schema
|
|
230
271
|
|
|
231
|
-
def _get_overlapping_field_value(
|
|
232
|
-
self, context: DbtContext, dbt_field_name: str, sqlmesh_field_name: str
|
|
233
|
-
) -> t.Optional[t.Any]:
|
|
234
|
-
dbt_field = self._get_field_value(dbt_field_name)
|
|
235
|
-
sqlmesh_field = getattr(self, sqlmesh_field_name, None)
|
|
236
|
-
if dbt_field is not None and sqlmesh_field is not None:
|
|
237
|
-
get_console().log_warning(
|
|
238
|
-
f"Both '{dbt_field_name}' and '{sqlmesh_field_name}' are set for model '{self.canonical_name(context)}'. '{sqlmesh_field_name}' will be used."
|
|
239
|
-
)
|
|
240
|
-
return sqlmesh_field if sqlmesh_field is not None else dbt_field
|
|
241
|
-
|
|
242
272
|
def model_kind(self, context: DbtContext) -> ModelKind:
|
|
243
273
|
"""
|
|
244
274
|
Get the sqlmesh ModelKind
|
|
@@ -273,8 +303,12 @@ class ModelConfig(BaseModelConfig):
|
|
|
273
303
|
"Valid values are 'ignore', 'fail', 'append_new_columns', 'sync_all_columns'."
|
|
274
304
|
)
|
|
275
305
|
|
|
276
|
-
incremental_kind_kwargs["on_destructive_change"] =
|
|
277
|
-
|
|
306
|
+
incremental_kind_kwargs["on_destructive_change"] = (
|
|
307
|
+
self._get_field_value("on_destructive_change") or on_destructive_change
|
|
308
|
+
)
|
|
309
|
+
incremental_kind_kwargs["on_additive_change"] = (
|
|
310
|
+
self._get_field_value("on_additive_change") or on_additive_change
|
|
311
|
+
)
|
|
278
312
|
auto_restatement_cron_value = self._get_field_value("auto_restatement_cron")
|
|
279
313
|
if auto_restatement_cron_value is not None:
|
|
280
314
|
incremental_kind_kwargs["auto_restatement_cron"] = auto_restatement_cron_value
|
|
@@ -290,7 +324,8 @@ class ModelConfig(BaseModelConfig):
|
|
|
290
324
|
incremental_kind_kwargs["forward_only"] = forward_only_value
|
|
291
325
|
|
|
292
326
|
is_incremental_by_time_range = self.time_column or (
|
|
293
|
-
self.incremental_strategy
|
|
327
|
+
self.incremental_strategy
|
|
328
|
+
and self.incremental_strategy in {"microbatch", "incremental_by_time_range"}
|
|
294
329
|
)
|
|
295
330
|
# Get shared incremental by kwargs
|
|
296
331
|
for field in ("batch_size", "batch_concurrency", "lookback"):
|
|
@@ -311,35 +346,35 @@ class ModelConfig(BaseModelConfig):
|
|
|
311
346
|
)
|
|
312
347
|
incremental_by_kind_kwargs["disable_restatement"] = disable_restatement
|
|
313
348
|
|
|
314
|
-
# Incremental by time range which includes microbatch
|
|
315
349
|
if is_incremental_by_time_range:
|
|
316
350
|
strategy = self.incremental_strategy or target.default_incremental_strategy(
|
|
317
351
|
IncrementalByTimeRangeKind
|
|
318
352
|
)
|
|
319
353
|
|
|
320
|
-
if strategy not in
|
|
354
|
+
if strategy not in INCREMENTAL_BY_TIME_RANGE_STRATEGIES:
|
|
321
355
|
get_console().log_warning(
|
|
322
356
|
f"SQLMesh incremental by time strategy is not compatible with '{strategy}' incremental strategy in model '{self.canonical_name(context)}'. "
|
|
323
|
-
f"Supported strategies include {collection_to_str(
|
|
357
|
+
f"Supported strategies include {collection_to_str(INCREMENTAL_BY_TIME_RANGE_STRATEGIES)}."
|
|
324
358
|
)
|
|
325
359
|
|
|
326
|
-
if strategy
|
|
327
|
-
|
|
328
|
-
|
|
360
|
+
if self.time_column and strategy != "incremental_by_time_range":
|
|
361
|
+
get_console().log_warning(
|
|
362
|
+
f"Using `time_column` on a model with incremental_strategy '{strategy}' has been deprecated. "
|
|
363
|
+
f"Please use `incremental_by_time_range` instead in model '{self.canonical_name(context)}'."
|
|
329
364
|
)
|
|
365
|
+
|
|
366
|
+
if strategy == "microbatch":
|
|
367
|
+
if self.time_column:
|
|
368
|
+
raise ConfigError(
|
|
369
|
+
f"{self.canonical_name(context)}: 'time_column' cannot be used with 'microbatch' incremental strategy. Use 'event_time' instead."
|
|
370
|
+
)
|
|
371
|
+
time_column = self._get_field_value("event_time")
|
|
330
372
|
if not time_column:
|
|
331
373
|
raise ConfigError(
|
|
332
374
|
f"{self.canonical_name(context)}: 'event_time' is required for microbatch incremental strategy."
|
|
333
375
|
)
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if incremental_by_kind_kwargs.get("batch_size"):
|
|
337
|
-
get_console().log_warning(
|
|
338
|
-
f"'concurrent_batches' is set to True and 'batch_size' are defined in '{self.canonical_name(context)}'. The batch size will be set to the value of `batch_size`."
|
|
339
|
-
)
|
|
340
|
-
incremental_by_kind_kwargs["batch_size"] = incremental_by_kind_kwargs.get(
|
|
341
|
-
"batch_size", 1
|
|
342
|
-
)
|
|
376
|
+
# dbt microbatch always processes batches in a size of 1
|
|
377
|
+
incremental_by_kind_kwargs["batch_size"] = 1
|
|
343
378
|
else:
|
|
344
379
|
if not self.time_column:
|
|
345
380
|
raise ConfigError(
|
|
@@ -347,11 +382,22 @@ class ModelConfig(BaseModelConfig):
|
|
|
347
382
|
)
|
|
348
383
|
time_column = self.time_column
|
|
349
384
|
|
|
385
|
+
incremental_by_time_range_kwargs = {
|
|
386
|
+
"time_column": time_column,
|
|
387
|
+
}
|
|
388
|
+
if self.auto_restatement_intervals:
|
|
389
|
+
incremental_by_time_range_kwargs["auto_restatement_intervals"] = (
|
|
390
|
+
self.auto_restatement_intervals
|
|
391
|
+
)
|
|
392
|
+
if self.partition_by_time_column is not None:
|
|
393
|
+
incremental_by_time_range_kwargs["partition_by_time_column"] = (
|
|
394
|
+
self.partition_by_time_column
|
|
395
|
+
)
|
|
396
|
+
|
|
350
397
|
return IncrementalByTimeRangeKind(
|
|
351
|
-
time_column=time_column,
|
|
352
|
-
auto_restatement_intervals=self.auto_restatement_intervals,
|
|
353
398
|
**incremental_kind_kwargs,
|
|
354
399
|
**incremental_by_kind_kwargs,
|
|
400
|
+
**incremental_by_time_range_kwargs,
|
|
355
401
|
)
|
|
356
402
|
|
|
357
403
|
if self.unique_key:
|
|
@@ -389,7 +435,7 @@ class ModelConfig(BaseModelConfig):
|
|
|
389
435
|
IncrementalUnmanagedKind
|
|
390
436
|
)
|
|
391
437
|
return IncrementalUnmanagedKind(
|
|
392
|
-
insert_overwrite=strategy in
|
|
438
|
+
insert_overwrite=strategy in INCREMENTAL_BY_TIME_RANGE_STRATEGIES,
|
|
393
439
|
disable_restatement=incremental_by_kind_kwargs["disable_restatement"],
|
|
394
440
|
**incremental_kind_kwargs,
|
|
395
441
|
)
|
|
@@ -424,26 +470,20 @@ class ModelConfig(BaseModelConfig):
|
|
|
424
470
|
if materialization == Materialization.DYNAMIC_TABLE:
|
|
425
471
|
return ManagedKind()
|
|
426
472
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
+
)
|
|
435
481
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
self._sql_embedded_config = SqlStr("")
|
|
440
|
-
self._extract_sql_config()
|
|
441
|
-
return self._sql_embedded_config
|
|
482
|
+
raise ConfigError(
|
|
483
|
+
f"Unknown materialization '{self.materialized}'. Custom materializations must be defined in your dbt project."
|
|
484
|
+
)
|
|
442
485
|
|
|
443
|
-
|
|
444
|
-
no_config, embedded_config = extract_jinja_config(self.sql)
|
|
445
|
-
self._sql_no_config = SqlStr(no_config)
|
|
446
|
-
self._sql_embedded_config = SqlStr(embedded_config)
|
|
486
|
+
raise ConfigError(f"{materialization.value} materialization not supported.")
|
|
447
487
|
|
|
448
488
|
def _big_query_partition_by_expr(self, context: DbtContext) -> exp.Expression:
|
|
449
489
|
assert isinstance(self.partition_by, dict)
|
|
@@ -482,6 +522,18 @@ class ModelConfig(BaseModelConfig):
|
|
|
482
522
|
dialect="bigquery",
|
|
483
523
|
)
|
|
484
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
|
+
|
|
485
537
|
@property
|
|
486
538
|
def sqlmesh_config_fields(self) -> t.Set[str]:
|
|
487
539
|
return super().sqlmesh_config_fields | {
|
|
@@ -502,35 +554,71 @@ class ModelConfig(BaseModelConfig):
|
|
|
502
554
|
) -> Model:
|
|
503
555
|
"""Converts the dbt model into a SQLMesh model."""
|
|
504
556
|
model_dialect = self.dialect(context)
|
|
505
|
-
query = d.jinja_query(self.
|
|
557
|
+
query = d.jinja_query(self.sql)
|
|
558
|
+
kind = self.model_kind(context)
|
|
506
559
|
|
|
507
560
|
optional_kwargs: t.Dict[str, t.Any] = {}
|
|
508
561
|
physical_properties: t.Dict[str, t.Any] = {}
|
|
509
562
|
|
|
510
563
|
if self.partition_by:
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
+
)
|
|
595
|
+
|
|
596
|
+
if partitioned_by:
|
|
597
|
+
optional_kwargs["partitioned_by"] = partitioned_by
|
|
598
|
+
|
|
599
|
+
if self.cluster_by:
|
|
600
|
+
if isinstance(kind, (ViewKind, EmbeddedKind)):
|
|
601
|
+
logger.warning(
|
|
602
|
+
"Ignoring cluster_by config for model '%s'; cluster_by is not supported for %s.",
|
|
603
|
+
self.name,
|
|
604
|
+
"views" if isinstance(kind, ViewKind) else "ephemeral models",
|
|
605
|
+
)
|
|
606
|
+
else:
|
|
607
|
+
clustered_by = []
|
|
608
|
+
for c in self.cluster_by:
|
|
514
609
|
try:
|
|
515
|
-
|
|
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
|
+
)
|
|
516
617
|
except SqlglotError as e:
|
|
517
618
|
raise ConfigError(
|
|
518
|
-
f"Failed to parse model '{self.canonical_name(context)}'
|
|
619
|
+
f"Failed to parse model '{self.canonical_name(context)}' cluster_by field '{c}' in '{self.path}': {e}"
|
|
519
620
|
) from e
|
|
520
|
-
|
|
521
|
-
partitioned_by.append(self._big_query_partition_by_expr(context))
|
|
522
|
-
optional_kwargs["partitioned_by"] = partitioned_by
|
|
523
|
-
|
|
524
|
-
if self.cluster_by:
|
|
525
|
-
clustered_by = []
|
|
526
|
-
for c in self.cluster_by:
|
|
527
|
-
try:
|
|
528
|
-
clustered_by.append(d.parse_one(c, dialect=model_dialect))
|
|
529
|
-
except SqlglotError as e:
|
|
530
|
-
raise ConfigError(
|
|
531
|
-
f"Failed to parse model '{self.canonical_name(context)}' cluster_by field '{c}' in '{self.path}': {e}"
|
|
532
|
-
) from e
|
|
533
|
-
optional_kwargs["clustered_by"] = clustered_by
|
|
621
|
+
optional_kwargs["clustered_by"] = clustered_by
|
|
534
622
|
|
|
535
623
|
model_kwargs = self.sqlmesh_model_kwargs(context)
|
|
536
624
|
if self.sql_header:
|
|
@@ -628,15 +716,19 @@ class ModelConfig(BaseModelConfig):
|
|
|
628
716
|
model_kwargs["physical_properties"] = physical_properties
|
|
629
717
|
|
|
630
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
|
+
|
|
631
724
|
allow_partials = model_kwargs.pop("allow_partials", None)
|
|
632
|
-
if
|
|
633
|
-
allow_partials is None
|
|
634
|
-
and kind.is_materialized
|
|
635
|
-
and not kind.is_incremental_by_time_range
|
|
636
|
-
):
|
|
725
|
+
if allow_partials is None:
|
|
637
726
|
# Set allow_partials to True for dbt models to preserve the original semantics.
|
|
638
727
|
allow_partials = True
|
|
639
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)
|
|
640
732
|
if kind.is_incremental:
|
|
641
733
|
if self.batch_size and isinstance(self.batch_size, str):
|
|
642
734
|
if "interval_unit" in model_kwargs:
|
|
@@ -646,13 +738,18 @@ class ModelConfig(BaseModelConfig):
|
|
|
646
738
|
else:
|
|
647
739
|
model_kwargs["interval_unit"] = self.batch_size
|
|
648
740
|
self.batch_size = None
|
|
649
|
-
if begin
|
|
741
|
+
if begin:
|
|
650
742
|
if "start" in model_kwargs:
|
|
651
743
|
get_console().log_warning(
|
|
652
744
|
f"Both 'begin' and 'start' are set for model '{self.canonical_name(context)}'. 'start' will be used."
|
|
653
745
|
)
|
|
654
746
|
else:
|
|
655
747
|
model_kwargs["start"] = begin
|
|
748
|
+
# If user explicitly disables concurrent batches then we want to set depends on past to true which we
|
|
749
|
+
# will do by including the model in the depends_on
|
|
750
|
+
if self.concurrent_batches is not None and self.concurrent_batches is False:
|
|
751
|
+
depends_on = model_kwargs.get("depends_on", set())
|
|
752
|
+
depends_on.add(self.canonical_name(context))
|
|
656
753
|
|
|
657
754
|
model_kwargs["start"] = model_kwargs.get(
|
|
658
755
|
"start", context.sqlmesh_config.model_defaults.start
|
|
@@ -670,6 +767,7 @@ class ModelConfig(BaseModelConfig):
|
|
|
670
767
|
extract_dependencies_from_query=False,
|
|
671
768
|
allow_partials=allow_partials,
|
|
672
769
|
virtual_environment_mode=virtual_environment_mode,
|
|
770
|
+
dbt_node_info=self.node_info,
|
|
673
771
|
**optional_kwargs,
|
|
674
772
|
**model_kwargs,
|
|
675
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
|
@@ -79,7 +79,11 @@ class SeedConfig(BaseModelConfig):
|
|
|
79
79
|
kwargs["columns"] = new_columns
|
|
80
80
|
|
|
81
81
|
# dbt treats single whitespace as a null value
|
|
82
|
-
csv_settings = CsvSettings(
|
|
82
|
+
csv_settings = CsvSettings(
|
|
83
|
+
delimiter=self.delimiter,
|
|
84
|
+
na_values=[" "],
|
|
85
|
+
keep_default_na=True,
|
|
86
|
+
)
|
|
83
87
|
|
|
84
88
|
return create_seed_model(
|
|
85
89
|
self.canonical_name(context),
|
|
@@ -88,6 +92,7 @@ class SeedConfig(BaseModelConfig):
|
|
|
88
92
|
audit_definitions=audit_definitions,
|
|
89
93
|
virtual_environment_mode=virtual_environment_mode,
|
|
90
94
|
start=self.start or context.sqlmesh_config.model_defaults.start,
|
|
95
|
+
dbt_node_info=self.node_info,
|
|
91
96
|
**kwargs,
|
|
92
97
|
)
|
|
93
98
|
|
sqlmesh/dbt/source.py
CHANGED
|
@@ -8,6 +8,7 @@ from sqlmesh.core.config.base import UpdateStrategy
|
|
|
8
8
|
from sqlmesh.dbt.column import ColumnConfig
|
|
9
9
|
from sqlmesh.dbt.common import GeneralConfig
|
|
10
10
|
from sqlmesh.dbt.relation import RelationType
|
|
11
|
+
from sqlmesh.dbt.util import DBT_VERSION
|
|
11
12
|
from sqlmesh.utils import AttributeDict
|
|
12
13
|
from sqlmesh.utils.errors import ConfigError
|
|
13
14
|
|
|
@@ -35,6 +36,7 @@ class SourceConfig(GeneralConfig):
|
|
|
35
36
|
# DBT configuration fields
|
|
36
37
|
name: str = ""
|
|
37
38
|
source_name_: str = Field("", alias="source_name")
|
|
39
|
+
fqn_: t.List[str] = Field(default_factory=list, alias="fqn")
|
|
38
40
|
database: t.Optional[str] = None
|
|
39
41
|
schema_: t.Optional[str] = Field(None, alias="schema")
|
|
40
42
|
identifier: t.Optional[str] = None
|
|
@@ -46,6 +48,7 @@ class SourceConfig(GeneralConfig):
|
|
|
46
48
|
external: t.Optional[t.Dict[str, t.Any]] = {}
|
|
47
49
|
source_meta: t.Optional[t.Dict[str, t.Any]] = {}
|
|
48
50
|
columns: t.Dict[str, ColumnConfig] = {}
|
|
51
|
+
event_time: t.Optional[str] = None
|
|
49
52
|
|
|
50
53
|
_canonical_name: t.Optional[str] = None
|
|
51
54
|
|
|
@@ -62,6 +65,10 @@ class SourceConfig(GeneralConfig):
|
|
|
62
65
|
def config_name(self) -> str:
|
|
63
66
|
return f"{self.source_name_}.{self.name}"
|
|
64
67
|
|
|
68
|
+
@property
|
|
69
|
+
def fqn(self) -> str:
|
|
70
|
+
return ".".join(self.fqn_)
|
|
71
|
+
|
|
65
72
|
def canonical_name(self, context: DbtContext) -> str:
|
|
66
73
|
if self._canonical_name is None:
|
|
67
74
|
source = context.get_callable_macro("source")
|
|
@@ -72,7 +79,7 @@ class SourceConfig(GeneralConfig):
|
|
|
72
79
|
relation = source(self.source_name_, self.name)
|
|
73
80
|
except Exception as e:
|
|
74
81
|
raise ConfigError(
|
|
75
|
-
f"'source' macro failed for '{self.config_name}' with
|
|
82
|
+
f"'source' macro failed for '{self.config_name}' with exception '{e}'."
|
|
76
83
|
)
|
|
77
84
|
|
|
78
85
|
relation = relation.quote(
|
|
@@ -94,6 +101,11 @@ class SourceConfig(GeneralConfig):
|
|
|
94
101
|
if external_location:
|
|
95
102
|
extras["external"] = external_location.replace("{name}", self.table_name)
|
|
96
103
|
|
|
104
|
+
if DBT_VERSION >= (1, 9, 0) and self.event_time:
|
|
105
|
+
extras["event_time_filter"] = {
|
|
106
|
+
"field_name": self.event_time,
|
|
107
|
+
}
|
|
108
|
+
|
|
97
109
|
return AttributeDict(
|
|
98
110
|
{
|
|
99
111
|
"database": self.database,
|
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
|
|