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/basemodel.py
CHANGED
|
@@ -13,6 +13,8 @@ from sqlmesh.core import dialect as d
|
|
|
13
13
|
from sqlmesh.core.config.base import UpdateStrategy
|
|
14
14
|
from sqlmesh.core.config.common import VirtualEnvironmentMode
|
|
15
15
|
from sqlmesh.core.model import Model
|
|
16
|
+
from sqlmesh.core.model.common import ParsableSql
|
|
17
|
+
from sqlmesh.core.node import DbtNodeInfo
|
|
16
18
|
from sqlmesh.dbt.column import (
|
|
17
19
|
ColumnConfig,
|
|
18
20
|
column_descriptions_to_sqlmesh,
|
|
@@ -22,11 +24,13 @@ from sqlmesh.dbt.common import (
|
|
|
22
24
|
DbtConfig,
|
|
23
25
|
Dependencies,
|
|
24
26
|
GeneralConfig,
|
|
27
|
+
RAW_CODE_KEY,
|
|
25
28
|
SqlStr,
|
|
26
29
|
sql_str_validator,
|
|
27
30
|
)
|
|
28
31
|
from sqlmesh.dbt.relation import Policy, RelationType
|
|
29
32
|
from sqlmesh.dbt.test import TestConfig
|
|
33
|
+
from sqlmesh.dbt.util import DBT_VERSION
|
|
30
34
|
from sqlmesh.utils import AttributeDict
|
|
31
35
|
from sqlmesh.utils.errors import ConfigError
|
|
32
36
|
from sqlmesh.utils.pydantic import field_validator
|
|
@@ -54,6 +58,12 @@ class Materialization(str, Enum):
|
|
|
54
58
|
# Snowflake, https://docs.getdbt.com/reference/resource-configs/snowflake-configs#dynamic-tables
|
|
55
59
|
DYNAMIC_TABLE = "dynamic_table"
|
|
56
60
|
|
|
61
|
+
CUSTOM = "custom"
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def _missing_(cls, value): # type: ignore
|
|
65
|
+
return cls.CUSTOM
|
|
66
|
+
|
|
57
67
|
|
|
58
68
|
class SnapshotStrategy(str, Enum):
|
|
59
69
|
"""DBT snapshot strategies"""
|
|
@@ -78,7 +88,7 @@ class Hook(DbtConfig):
|
|
|
78
88
|
"""
|
|
79
89
|
|
|
80
90
|
sql: SqlStr
|
|
81
|
-
transaction: bool = True
|
|
91
|
+
transaction: bool = True
|
|
82
92
|
|
|
83
93
|
_sql_validator = sql_str_validator
|
|
84
94
|
|
|
@@ -118,8 +128,10 @@ class BaseModelConfig(GeneralConfig):
|
|
|
118
128
|
grain: t.Union[str, t.List[str]] = []
|
|
119
129
|
|
|
120
130
|
# DBT configuration fields
|
|
131
|
+
unique_id: str = ""
|
|
121
132
|
name: str = ""
|
|
122
133
|
package_name: str = ""
|
|
134
|
+
fqn_: t.List[str] = Field(default_factory=list, alias="fqn")
|
|
123
135
|
schema_: str = Field("", alias="schema")
|
|
124
136
|
database: t.Optional[str] = None
|
|
125
137
|
alias: t.Optional[str] = None
|
|
@@ -129,6 +141,7 @@ class BaseModelConfig(GeneralConfig):
|
|
|
129
141
|
grants: t.Dict[str, t.List[str]] = {}
|
|
130
142
|
columns: t.Dict[str, ColumnConfig] = {}
|
|
131
143
|
quoting: t.Dict[str, t.Optional[bool]] = {}
|
|
144
|
+
event_time: t.Optional[str] = None
|
|
132
145
|
|
|
133
146
|
version: t.Optional[int] = None
|
|
134
147
|
latest_version: t.Optional[int] = None
|
|
@@ -153,7 +166,11 @@ class BaseModelConfig(GeneralConfig):
|
|
|
153
166
|
|
|
154
167
|
@field_validator("grants", mode="before")
|
|
155
168
|
@classmethod
|
|
156
|
-
def _validate_grants(
|
|
169
|
+
def _validate_grants(
|
|
170
|
+
cls, v: t.Optional[t.Dict[str, str]]
|
|
171
|
+
) -> t.Optional[t.Dict[str, t.List[str]]]:
|
|
172
|
+
if v is None:
|
|
173
|
+
return None
|
|
157
174
|
return {key: ensure_list(value) for key, value in v.items()}
|
|
158
175
|
|
|
159
176
|
_FIELD_UPDATE_STRATEGY: t.ClassVar[t.Dict[str, UpdateStrategy]] = {
|
|
@@ -167,14 +184,6 @@ class BaseModelConfig(GeneralConfig):
|
|
|
167
184
|
},
|
|
168
185
|
}
|
|
169
186
|
|
|
170
|
-
@property
|
|
171
|
-
def sql_no_config(self) -> SqlStr:
|
|
172
|
-
return SqlStr("")
|
|
173
|
-
|
|
174
|
-
@property
|
|
175
|
-
def sql_embedded_config(self) -> SqlStr:
|
|
176
|
-
return SqlStr("")
|
|
177
|
-
|
|
178
187
|
@property
|
|
179
188
|
def table_schema(self) -> str:
|
|
180
189
|
"""
|
|
@@ -229,6 +238,12 @@ class BaseModelConfig(GeneralConfig):
|
|
|
229
238
|
else:
|
|
230
239
|
relation_type = RelationType.Table
|
|
231
240
|
|
|
241
|
+
extras = {}
|
|
242
|
+
if DBT_VERSION >= (1, 9, 0) and self.event_time:
|
|
243
|
+
extras["event_time_filter"] = {
|
|
244
|
+
"field_name": self.event_time,
|
|
245
|
+
}
|
|
246
|
+
|
|
232
247
|
return AttributeDict(
|
|
233
248
|
{
|
|
234
249
|
"database": self.database,
|
|
@@ -236,6 +251,7 @@ class BaseModelConfig(GeneralConfig):
|
|
|
236
251
|
"identifier": self.table_name,
|
|
237
252
|
"type": relation_type.value,
|
|
238
253
|
"quote_policy": AttributeDict(self.quoting),
|
|
254
|
+
**extras,
|
|
239
255
|
}
|
|
240
256
|
)
|
|
241
257
|
|
|
@@ -266,44 +282,17 @@ class BaseModelConfig(GeneralConfig):
|
|
|
266
282
|
and all(source in context.sources for source in test.dependencies.sources)
|
|
267
283
|
]
|
|
268
284
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
model if found. This addresses the most common circular reference - relationship tests in both
|
|
273
|
-
directions. In the future, we may want to increase coverage by checking for indirect circular references.
|
|
274
|
-
|
|
275
|
-
Args:
|
|
276
|
-
context: The dbt context this model resides within.
|
|
277
|
-
|
|
278
|
-
Returns:
|
|
279
|
-
None
|
|
280
|
-
"""
|
|
281
|
-
for test in self.tests.copy():
|
|
282
|
-
for ref in test.dependencies.refs:
|
|
283
|
-
if ref == self.name or ref in self.dependencies.refs:
|
|
284
|
-
continue
|
|
285
|
-
model = context.refs[ref]
|
|
286
|
-
if (
|
|
287
|
-
self.name in model.dependencies.refs
|
|
288
|
-
or self.name in model.tests_ref_source_dependencies.refs
|
|
289
|
-
):
|
|
290
|
-
logger.info(
|
|
291
|
-
f"Moving test '{test.name}' from model '{self.name}' to '{model.name}' to avoid circular reference."
|
|
292
|
-
)
|
|
293
|
-
model.tests.append(test)
|
|
294
|
-
self.tests.remove(test)
|
|
285
|
+
@property
|
|
286
|
+
def fqn(self) -> str:
|
|
287
|
+
return ".".join(self.fqn_)
|
|
295
288
|
|
|
296
289
|
@property
|
|
297
290
|
def sqlmesh_config_fields(self) -> t.Set[str]:
|
|
298
291
|
return {"description", "owner", "stamp", "storage_format"}
|
|
299
292
|
|
|
300
293
|
@property
|
|
301
|
-
def
|
|
302
|
-
|
|
303
|
-
node_name = f"{resource_type}.{self.package_name}.{self.name}"
|
|
304
|
-
if self.version:
|
|
305
|
-
node_name += f".v{self.version}"
|
|
306
|
-
return node_name
|
|
294
|
+
def node_info(self) -> DbtNodeInfo:
|
|
295
|
+
return DbtNodeInfo(unique_id=self.unique_id, name=self.name, fqn=self.fqn, alias=self.alias)
|
|
307
296
|
|
|
308
297
|
def sqlmesh_model_kwargs(
|
|
309
298
|
self,
|
|
@@ -312,7 +301,6 @@ class BaseModelConfig(GeneralConfig):
|
|
|
312
301
|
) -> t.Dict[str, t.Any]:
|
|
313
302
|
"""Get common sqlmesh model parameters"""
|
|
314
303
|
self.remove_tests_with_invalid_refs(context)
|
|
315
|
-
self.fix_circular_test_refs(context)
|
|
316
304
|
|
|
317
305
|
dependencies = self.dependencies.copy()
|
|
318
306
|
if dependencies.has_dynamic_var_names:
|
|
@@ -320,7 +308,19 @@ class BaseModelConfig(GeneralConfig):
|
|
|
320
308
|
# precisely which variables are referenced in the model
|
|
321
309
|
dependencies.variables |= set(context.variables)
|
|
322
310
|
|
|
311
|
+
if (
|
|
312
|
+
getattr(self, "model_materialization", None) == Materialization.CUSTOM
|
|
313
|
+
and hasattr(self, "_get_custom_materialization")
|
|
314
|
+
and (custom_mat := self._get_custom_materialization(context))
|
|
315
|
+
):
|
|
316
|
+
# include custom materialization dependencies as they might use macros
|
|
317
|
+
dependencies = dependencies.union(custom_mat.dependencies)
|
|
318
|
+
|
|
323
319
|
model_dialect = self.dialect(context)
|
|
320
|
+
|
|
321
|
+
# Only keep refs and sources that exist in the context to match dbt behavior
|
|
322
|
+
dependencies.refs.intersection_update(context.refs)
|
|
323
|
+
dependencies.sources.intersection_update(context.sources)
|
|
324
324
|
model_context = context.context_for_dependencies(
|
|
325
325
|
dependencies.union(self.tests_ref_source_dependencies)
|
|
326
326
|
)
|
|
@@ -330,15 +330,28 @@ class BaseModelConfig(GeneralConfig):
|
|
|
330
330
|
jinja_macros.add_globals(self._model_jinja_context(model_context, dependencies))
|
|
331
331
|
|
|
332
332
|
model_kwargs = {
|
|
333
|
-
"audits": [(test.
|
|
333
|
+
"audits": [(test.canonical_name, {}) for test in self.tests],
|
|
334
334
|
"column_descriptions": column_descriptions_to_sqlmesh(self.columns) or None,
|
|
335
335
|
"depends_on": {
|
|
336
336
|
model.canonical_name(context) for model in model_context.refs.values()
|
|
337
|
-
}.union(
|
|
337
|
+
}.union(
|
|
338
|
+
{
|
|
339
|
+
source.canonical_name(context)
|
|
340
|
+
for source in model_context.sources.values()
|
|
341
|
+
if source.fqn not in context.model_fqns
|
|
342
|
+
# Allow dbt projects to reference a model as a source without causing a cycle
|
|
343
|
+
},
|
|
344
|
+
),
|
|
338
345
|
"jinja_macros": jinja_macros,
|
|
339
346
|
"path": self.path,
|
|
340
|
-
"pre_statements": [
|
|
341
|
-
|
|
347
|
+
"pre_statements": [
|
|
348
|
+
ParsableSql(sql=d.jinja_statement(hook.sql).sql(), transaction=hook.transaction)
|
|
349
|
+
for hook in self.pre_hook
|
|
350
|
+
],
|
|
351
|
+
"post_statements": [
|
|
352
|
+
ParsableSql(sql=d.jinja_statement(hook.sql).sql(), transaction=hook.transaction)
|
|
353
|
+
for hook in self.post_hook
|
|
354
|
+
],
|
|
342
355
|
"tags": self.tags,
|
|
343
356
|
"physical_schema_mapping": context.sqlmesh_config.physical_schema_mapping,
|
|
344
357
|
"default_catalog": context.target.database,
|
|
@@ -375,15 +388,21 @@ class BaseModelConfig(GeneralConfig):
|
|
|
375
388
|
def _model_jinja_context(
|
|
376
389
|
self, context: DbtContext, dependencies: Dependencies
|
|
377
390
|
) -> t.Dict[str, t.Any]:
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
391
|
+
if context._manifest and self.unique_id in context._manifest._manifest.nodes:
|
|
392
|
+
attributes = context._manifest._manifest.nodes[self.unique_id].to_dict()
|
|
393
|
+
if dependencies.model_attrs.all_attrs:
|
|
394
|
+
model_node: AttributeDict[str, t.Any] = AttributeDict(attributes)
|
|
395
|
+
else:
|
|
396
|
+
model_node = AttributeDict(
|
|
397
|
+
filter(lambda kv: kv[0] in dependencies.model_attrs.attrs, attributes.items())
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# We exclude the raw SQL code to reduce the payload size. It's still accessible through
|
|
401
|
+
# the JinjaQuery instance stored in the resulting SQLMesh model's `query` field.
|
|
402
|
+
model_node.pop(RAW_CODE_KEY, None)
|
|
403
|
+
else:
|
|
404
|
+
model_node = AttributeDict({})
|
|
405
|
+
|
|
387
406
|
return {
|
|
388
407
|
"this": self.relation_info,
|
|
389
408
|
"model": model_node,
|
sqlmesh/dbt/builtin.py
CHANGED
|
@@ -16,14 +16,16 @@ from sqlglot import Dialect
|
|
|
16
16
|
|
|
17
17
|
from sqlmesh.core.console import get_console
|
|
18
18
|
from sqlmesh.core.engine_adapter import EngineAdapter
|
|
19
|
+
from sqlmesh.core.model.definition import SqlModel
|
|
19
20
|
from sqlmesh.core.snapshot.definition import DeployabilityIndex
|
|
20
21
|
from sqlmesh.dbt.adapter import BaseAdapter, ParsetimeAdapter, RuntimeAdapter
|
|
22
|
+
from sqlmesh.dbt.common import RAW_CODE_KEY
|
|
21
23
|
from sqlmesh.dbt.relation import Policy
|
|
22
24
|
from sqlmesh.dbt.target import TARGET_TYPE_TO_CONFIG_CLASS
|
|
23
25
|
from sqlmesh.dbt.util import DBT_VERSION
|
|
24
26
|
from sqlmesh.utils import AttributeDict, debug_mode_enabled, yaml
|
|
25
27
|
from sqlmesh.utils.date import now
|
|
26
|
-
from sqlmesh.utils.errors import ConfigError
|
|
28
|
+
from sqlmesh.utils.errors import ConfigError
|
|
27
29
|
from sqlmesh.utils.jinja import JinjaMacroRegistry, MacroReference, MacroReturnVal
|
|
28
30
|
|
|
29
31
|
logger = logging.getLogger(__name__)
|
|
@@ -48,6 +50,22 @@ class Exceptions:
|
|
|
48
50
|
return ""
|
|
49
51
|
|
|
50
52
|
|
|
53
|
+
def try_or_compiler_error(
|
|
54
|
+
message_if_exception: str, func: t.Callable, *args: t.Any, **kwargs: t.Any
|
|
55
|
+
) -> t.Any:
|
|
56
|
+
try:
|
|
57
|
+
return func(*args, **kwargs)
|
|
58
|
+
except Exception:
|
|
59
|
+
if DBT_VERSION >= (1, 4, 0):
|
|
60
|
+
from dbt.exceptions import CompilationError
|
|
61
|
+
|
|
62
|
+
raise CompilationError(message_if_exception)
|
|
63
|
+
else:
|
|
64
|
+
from dbt.exceptions import CompilationException # type: ignore
|
|
65
|
+
|
|
66
|
+
raise CompilationException(message_if_exception)
|
|
67
|
+
|
|
68
|
+
|
|
51
69
|
class Api:
|
|
52
70
|
def __init__(self, dialect: t.Optional[str]) -> None:
|
|
53
71
|
if dialect:
|
|
@@ -164,6 +182,74 @@ class Var:
|
|
|
164
182
|
return name in self.variables
|
|
165
183
|
|
|
166
184
|
|
|
185
|
+
class Config:
|
|
186
|
+
def __init__(self, config_dict: t.Dict[str, t.Any]) -> None:
|
|
187
|
+
self._config = config_dict
|
|
188
|
+
|
|
189
|
+
def __call__(self, *args: t.Any, **kwargs: t.Any) -> str:
|
|
190
|
+
if args and kwargs:
|
|
191
|
+
raise ConfigError(
|
|
192
|
+
"Invalid inline model config: cannot mix positional and keyword arguments"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if args:
|
|
196
|
+
if len(args) == 1 and isinstance(args[0], dict):
|
|
197
|
+
# Single dict argument: config({"materialized": "table"})
|
|
198
|
+
self._config.update(args[0])
|
|
199
|
+
else:
|
|
200
|
+
raise ConfigError(
|
|
201
|
+
f"Invalid inline model config: expected a single dictionary, got {len(args)} arguments"
|
|
202
|
+
)
|
|
203
|
+
elif kwargs:
|
|
204
|
+
# Keyword arguments: config(materialized="table")
|
|
205
|
+
self._config.update(kwargs)
|
|
206
|
+
|
|
207
|
+
return ""
|
|
208
|
+
|
|
209
|
+
def set(self, name: str, value: t.Any) -> str:
|
|
210
|
+
self._config.update({name: value})
|
|
211
|
+
return ""
|
|
212
|
+
|
|
213
|
+
def _validate(self, name: str, validator: t.Callable, value: t.Optional[t.Any] = None) -> None:
|
|
214
|
+
try:
|
|
215
|
+
validator(value)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
raise ConfigError(f"Config validation failed for '{name}': {e}")
|
|
218
|
+
|
|
219
|
+
def require(self, name: str, validator: t.Optional[t.Callable] = None) -> t.Any:
|
|
220
|
+
if name not in self._config:
|
|
221
|
+
raise ConfigError(f"Missing required config: {name}")
|
|
222
|
+
|
|
223
|
+
value = self._config[name]
|
|
224
|
+
|
|
225
|
+
if validator is not None:
|
|
226
|
+
self._validate(name, validator, value)
|
|
227
|
+
|
|
228
|
+
return value
|
|
229
|
+
|
|
230
|
+
def get(
|
|
231
|
+
self, name: str, default: t.Any = None, validator: t.Optional[t.Callable] = None
|
|
232
|
+
) -> t.Any:
|
|
233
|
+
value = self._config.get(name, default)
|
|
234
|
+
|
|
235
|
+
if validator is not None and value is not None:
|
|
236
|
+
self._validate(name, validator, value)
|
|
237
|
+
|
|
238
|
+
return value
|
|
239
|
+
|
|
240
|
+
def persist_relation_docs(self) -> bool:
|
|
241
|
+
persist_docs = self.get("persist_docs", default={})
|
|
242
|
+
if not isinstance(persist_docs, dict):
|
|
243
|
+
return False
|
|
244
|
+
return persist_docs.get("relation", False)
|
|
245
|
+
|
|
246
|
+
def persist_column_docs(self) -> bool:
|
|
247
|
+
persist_docs = self.get("persist_docs", default={})
|
|
248
|
+
if not isinstance(persist_docs, dict):
|
|
249
|
+
return False
|
|
250
|
+
return persist_docs.get("columns", False)
|
|
251
|
+
|
|
252
|
+
|
|
167
253
|
def env_var(name: str, default: t.Optional[str] = None) -> t.Optional[str]:
|
|
168
254
|
if name not in os.environ and default is None:
|
|
169
255
|
raise ConfigError(f"Missing environment variable '{name}'")
|
|
@@ -295,18 +381,16 @@ def do_zip(*args: t.Any, default: t.Optional[t.Any] = None) -> t.Optional[t.Any]
|
|
|
295
381
|
return default
|
|
296
382
|
|
|
297
383
|
|
|
298
|
-
def as_bool(value:
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
raise MacroEvalError(f"Failed to convert '{value}' into boolean.")
|
|
384
|
+
def as_bool(value: t.Any) -> t.Any:
|
|
385
|
+
# dbt's jinja TEXT_FILTERS just return the input value as is
|
|
386
|
+
# https://github.com/dbt-labs/dbt-common/blob/main/dbt_common/clients/jinja.py#L559
|
|
387
|
+
return value
|
|
303
388
|
|
|
304
389
|
|
|
305
390
|
def as_number(value: str) -> t.Any:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
raise MacroEvalError(f"Failed to convert '{value}' into number.")
|
|
391
|
+
# dbt's jinja TEXT_FILTERS just return the input value as is
|
|
392
|
+
# https://github.com/dbt-labs/dbt-common/blob/main/dbt_common/clients/jinja.py#L559
|
|
393
|
+
return value
|
|
310
394
|
|
|
311
395
|
|
|
312
396
|
def _try_literal_eval(value: str) -> t.Any:
|
|
@@ -341,6 +425,7 @@ BUILTIN_GLOBALS = {
|
|
|
341
425
|
"sqlmesh_incremental": True,
|
|
342
426
|
"tojson": to_json,
|
|
343
427
|
"toyaml": to_yaml,
|
|
428
|
+
"try_or_compiler_error": try_or_compiler_error,
|
|
344
429
|
"zip": do_zip,
|
|
345
430
|
"zip_strict": lambda *args: list(zip(*args)),
|
|
346
431
|
}
|
|
@@ -395,6 +480,8 @@ def create_builtin_globals(
|
|
|
395
480
|
if variables is not None:
|
|
396
481
|
builtin_globals["var"] = Var(variables)
|
|
397
482
|
|
|
483
|
+
builtin_globals["config"] = Config(jinja_globals.pop("config", {"tags": []}))
|
|
484
|
+
|
|
398
485
|
deployability_index = (
|
|
399
486
|
jinja_globals.get("deployability_index") or DeployabilityIndex.all_deployable()
|
|
400
487
|
)
|
|
@@ -415,14 +502,29 @@ def create_builtin_globals(
|
|
|
415
502
|
is_incremental &= snapshot_table_exists
|
|
416
503
|
else:
|
|
417
504
|
is_incremental = False
|
|
505
|
+
|
|
418
506
|
builtin_globals["is_incremental"] = lambda: is_incremental
|
|
419
507
|
|
|
420
508
|
builtin_globals["builtins"] = AttributeDict(
|
|
421
509
|
{k: builtin_globals.get(k) for k in ("ref", "source", "config", "var")}
|
|
422
510
|
)
|
|
423
511
|
|
|
512
|
+
if (model := jinja_globals.pop("model", None)) is not None:
|
|
513
|
+
if isinstance(model_instance := jinja_globals.pop("model_instance", None), SqlModel):
|
|
514
|
+
builtin_globals["model"] = AttributeDict(
|
|
515
|
+
{**model, RAW_CODE_KEY: model_instance.query.name}
|
|
516
|
+
)
|
|
517
|
+
else:
|
|
518
|
+
builtin_globals["model"] = AttributeDict(model.copy())
|
|
519
|
+
|
|
520
|
+
builtin_globals["flags"] = (
|
|
521
|
+
Flags(which="run") if engine_adapter is not None else Flags(which="parse")
|
|
522
|
+
)
|
|
523
|
+
builtin_globals["invocation_args_dict"] = {
|
|
524
|
+
k.lower(): v for k, v in builtin_globals["flags"].__dict__.items()
|
|
525
|
+
}
|
|
526
|
+
|
|
424
527
|
if engine_adapter is not None:
|
|
425
|
-
builtin_globals["flags"] = Flags(which="run")
|
|
426
528
|
adapter: BaseAdapter = RuntimeAdapter(
|
|
427
529
|
engine_adapter,
|
|
428
530
|
jinja_macros,
|
|
@@ -440,7 +542,6 @@ def create_builtin_globals(
|
|
|
440
542
|
project_dialect=project_dialect,
|
|
441
543
|
)
|
|
442
544
|
else:
|
|
443
|
-
builtin_globals["flags"] = Flags(which="parse")
|
|
444
545
|
adapter = ParsetimeAdapter(
|
|
445
546
|
jinja_macros,
|
|
446
547
|
jinja_globals={**builtin_globals, **jinja_globals},
|
|
@@ -459,11 +560,14 @@ def create_builtin_globals(
|
|
|
459
560
|
"run_query": sql_execution.run_query,
|
|
460
561
|
"statement": sql_execution.statement,
|
|
461
562
|
"graph": adapter.graph,
|
|
563
|
+
"selected_resources": list(jinja_globals.get("selected_models") or []),
|
|
564
|
+
"write": lambda input: None, # We don't support writing yet
|
|
462
565
|
}
|
|
463
566
|
)
|
|
464
567
|
|
|
465
568
|
builtin_globals["run_started_at"] = jinja_globals.get("execution_dt") or now()
|
|
466
569
|
builtin_globals["dbt"] = AttributeDict(builtin_globals)
|
|
570
|
+
builtin_globals["context"] = builtin_globals["dbt"]
|
|
467
571
|
|
|
468
572
|
return {**builtin_globals, **jinja_globals}
|
|
469
573
|
|
sqlmesh/dbt/column.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import typing as t
|
|
4
|
+
import logging
|
|
4
5
|
|
|
5
6
|
from sqlglot import exp, parse_one
|
|
6
7
|
from sqlglot.helper import ensure_list
|
|
@@ -9,6 +10,8 @@ from sqlmesh.dbt.common import GeneralConfig
|
|
|
9
10
|
from sqlmesh.utils.conversions import ensure_bool
|
|
10
11
|
from sqlmesh.utils.pydantic import field_validator
|
|
11
12
|
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
12
15
|
|
|
13
16
|
def yaml_to_columns(
|
|
14
17
|
yaml: t.Dict[str, ColumnConfig] | t.List[t.Dict[str, ColumnConfig]],
|
|
@@ -31,11 +34,20 @@ def column_types_to_sqlmesh(
|
|
|
31
34
|
Returns:
|
|
32
35
|
A dict of column name to exp.DataType
|
|
33
36
|
"""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
col_types_to_sqlmesh: t.Dict[str, exp.DataType] = {}
|
|
38
|
+
for name, column in columns.items():
|
|
39
|
+
if column.enabled and column.data_type:
|
|
40
|
+
column_def = parse_one(
|
|
41
|
+
f"{name} {column.data_type}", into=exp.ColumnDef, dialect=dialect or ""
|
|
42
|
+
)
|
|
43
|
+
if column_def.args.get("constraints"):
|
|
44
|
+
logger.warning(
|
|
45
|
+
f"Ignoring unsupported constraints for column '{name}' with definition '{column.data_type}'. Please refer to github.com/TobikoData/sqlmesh/issues/4717 for more information."
|
|
46
|
+
)
|
|
47
|
+
kind = column_def.kind
|
|
48
|
+
if kind:
|
|
49
|
+
col_types_to_sqlmesh[name] = kind
|
|
50
|
+
return col_types_to_sqlmesh
|
|
39
51
|
|
|
40
52
|
|
|
41
53
|
def column_descriptions_to_sqlmesh(columns: t.Dict[str, ColumnConfig]) -> t.Dict[str, str]:
|
sqlmesh/dbt/common.py
CHANGED
|
@@ -2,23 +2,26 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
import typing as t
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
from ruamel.yaml.constructor import DuplicateKeyError
|
|
8
9
|
from sqlglot.helper import ensure_list
|
|
9
10
|
|
|
11
|
+
from sqlmesh.dbt.util import DBT_VERSION
|
|
10
12
|
from sqlmesh.core.config.base import BaseConfig, UpdateStrategy
|
|
13
|
+
from sqlmesh.core.config.common import DBT_PROJECT_FILENAME
|
|
11
14
|
from sqlmesh.utils import AttributeDict
|
|
12
15
|
from sqlmesh.utils.conversions import ensure_bool, try_str_to_bool
|
|
13
16
|
from sqlmesh.utils.errors import ConfigError
|
|
14
17
|
from sqlmesh.utils.jinja import MacroReference
|
|
15
18
|
from sqlmesh.utils.pydantic import PydanticModel, field_validator
|
|
16
19
|
from sqlmesh.utils.yaml import load
|
|
17
|
-
from sqlmesh.core.config.common import DBT_PROJECT_FILENAME
|
|
18
20
|
|
|
19
21
|
T = t.TypeVar("T", bound="GeneralConfig")
|
|
20
22
|
|
|
21
23
|
PROJECT_FILENAME = DBT_PROJECT_FILENAME
|
|
24
|
+
RAW_CODE_KEY = "raw_code" if DBT_VERSION >= (1, 3, 0) else "raw_sql" # type: ignore
|
|
22
25
|
|
|
23
26
|
JINJA_ONLY = {
|
|
24
27
|
"adapter",
|
|
@@ -43,7 +46,9 @@ def load_yaml(source: str | Path) -> t.Dict:
|
|
|
43
46
|
raise ConfigError(f"{source}: {ex}" if isinstance(source, Path) else f"{ex}")
|
|
44
47
|
|
|
45
48
|
|
|
46
|
-
def parse_meta(v: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
|
|
49
|
+
def parse_meta(v: t.Optional[t.Dict[str, t.Any]]) -> t.Dict[str, t.Any]:
|
|
50
|
+
if v is None:
|
|
51
|
+
return {}
|
|
47
52
|
for key, value in v.items():
|
|
48
53
|
if isinstance(value, str):
|
|
49
54
|
v[key] = try_str_to_bool(value)
|
|
@@ -112,7 +117,7 @@ class GeneralConfig(DbtConfig):
|
|
|
112
117
|
|
|
113
118
|
@field_validator("meta", mode="before")
|
|
114
119
|
@classmethod
|
|
115
|
-
def _validate_meta(cls, v: t.Dict[str, t.Union[str, t.Any]]) -> t.Dict[str, t.Any]:
|
|
120
|
+
def _validate_meta(cls, v: t.Optional[t.Dict[str, t.Union[str, t.Any]]]) -> t.Dict[str, t.Any]:
|
|
116
121
|
return parse_meta(v)
|
|
117
122
|
|
|
118
123
|
_FIELD_UPDATE_STRATEGY: t.ClassVar[t.Dict[str, UpdateStrategy]] = {
|
|
@@ -172,6 +177,12 @@ class GeneralConfig(DbtConfig):
|
|
|
172
177
|
return set()
|
|
173
178
|
|
|
174
179
|
|
|
180
|
+
@dataclass
|
|
181
|
+
class ModelAttrs:
|
|
182
|
+
attrs: t.Set[str]
|
|
183
|
+
all_attrs: bool = False
|
|
184
|
+
|
|
185
|
+
|
|
175
186
|
class Dependencies(PydanticModel):
|
|
176
187
|
"""
|
|
177
188
|
DBT dependencies for a model, macro, etc.
|
|
@@ -186,7 +197,7 @@ class Dependencies(PydanticModel):
|
|
|
186
197
|
sources: t.Set[str] = set()
|
|
187
198
|
refs: t.Set[str] = set()
|
|
188
199
|
variables: t.Set[str] = set()
|
|
189
|
-
model_attrs:
|
|
200
|
+
model_attrs: ModelAttrs = ModelAttrs(attrs=set())
|
|
190
201
|
|
|
191
202
|
has_dynamic_var_names: bool = False
|
|
192
203
|
|
|
@@ -196,7 +207,10 @@ class Dependencies(PydanticModel):
|
|
|
196
207
|
sources=self.sources | other.sources,
|
|
197
208
|
refs=self.refs | other.refs,
|
|
198
209
|
variables=self.variables | other.variables,
|
|
199
|
-
model_attrs=
|
|
210
|
+
model_attrs=ModelAttrs(
|
|
211
|
+
attrs=self.model_attrs.attrs | other.model_attrs.attrs,
|
|
212
|
+
all_attrs=self.model_attrs.all_attrs or other.model_attrs.all_attrs,
|
|
213
|
+
),
|
|
200
214
|
has_dynamic_var_names=self.has_dynamic_var_names or other.has_dynamic_var_names,
|
|
201
215
|
)
|
|
202
216
|
|
sqlmesh/dbt/context.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import typing as t
|
|
4
5
|
from dataclasses import dataclass, field, replace
|
|
5
6
|
from pathlib import Path
|
|
@@ -28,12 +29,16 @@ if t.TYPE_CHECKING:
|
|
|
28
29
|
from sqlmesh.dbt.seed import SeedConfig
|
|
29
30
|
from sqlmesh.dbt.source import SourceConfig
|
|
30
31
|
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
31
34
|
|
|
32
35
|
@dataclass
|
|
33
36
|
class DbtContext:
|
|
34
37
|
"""Context for DBT environment"""
|
|
35
38
|
|
|
36
39
|
project_root: Path = Path()
|
|
40
|
+
profiles_dir: t.Optional[Path] = None
|
|
41
|
+
"""Optional override to specify the directory where profiles.yml is located, if not at the :project_root"""
|
|
37
42
|
target_name: t.Optional[str] = None
|
|
38
43
|
profile_name: t.Optional[str] = None
|
|
39
44
|
project_schema: t.Optional[str] = None
|
|
@@ -48,6 +53,7 @@ class DbtContext:
|
|
|
48
53
|
_project_name: t.Optional[str] = None
|
|
49
54
|
_variables: t.Dict[str, t.Any] = field(default_factory=dict)
|
|
50
55
|
_models: t.Dict[str, ModelConfig] = field(default_factory=dict)
|
|
56
|
+
_model_fqns: t.Set[str] = field(default_factory=set)
|
|
51
57
|
_seeds: t.Dict[str, SeedConfig] = field(default_factory=dict)
|
|
52
58
|
_sources: t.Dict[str, SourceConfig] = field(default_factory=dict)
|
|
53
59
|
_refs: t.Dict[str, t.Union[ModelConfig, SeedConfig]] = field(default_factory=dict)
|
|
@@ -125,7 +131,7 @@ class DbtContext:
|
|
|
125
131
|
try:
|
|
126
132
|
rendered_variables[k] = _render_var(v)
|
|
127
133
|
except Exception as ex:
|
|
128
|
-
|
|
134
|
+
logger.warning(f"Failed to render variable '{k}', value '{v}': {ex}")
|
|
129
135
|
|
|
130
136
|
self.variables = rendered_variables
|
|
131
137
|
|
|
@@ -141,6 +147,7 @@ class DbtContext:
|
|
|
141
147
|
def models(self, models: t.Dict[str, ModelConfig]) -> None:
|
|
142
148
|
self._models = {}
|
|
143
149
|
self._refs = {}
|
|
150
|
+
self._model_fqns = set()
|
|
144
151
|
self.add_models(models)
|
|
145
152
|
|
|
146
153
|
def add_models(self, models: t.Dict[str, ModelConfig]) -> None:
|
|
@@ -148,6 +155,12 @@ class DbtContext:
|
|
|
148
155
|
self._models.update(models)
|
|
149
156
|
self._jinja_environment = None
|
|
150
157
|
|
|
158
|
+
@property
|
|
159
|
+
def model_fqns(self) -> t.Set[str]:
|
|
160
|
+
if not self._model_fqns:
|
|
161
|
+
self._model_fqns = {model.fqn for model in self._models.values()}
|
|
162
|
+
return self._model_fqns
|
|
163
|
+
|
|
151
164
|
@property
|
|
152
165
|
def seeds(self) -> t.Dict[str, SeedConfig]:
|
|
153
166
|
return self._seeds
|