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.
Files changed (252) hide show
  1. sqlmesh/__init__.py +12 -2
  2. sqlmesh/_version.py +2 -2
  3. sqlmesh/cli/main.py +0 -44
  4. sqlmesh/cli/project_init.py +11 -2
  5. sqlmesh/core/_typing.py +1 -0
  6. sqlmesh/core/audit/definition.py +8 -2
  7. sqlmesh/core/config/__init__.py +1 -1
  8. sqlmesh/core/config/connection.py +17 -5
  9. sqlmesh/core/config/dbt.py +13 -0
  10. sqlmesh/core/config/janitor.py +12 -0
  11. sqlmesh/core/config/loader.py +7 -0
  12. sqlmesh/core/config/model.py +2 -0
  13. sqlmesh/core/config/root.py +3 -0
  14. sqlmesh/core/console.py +81 -3
  15. sqlmesh/core/constants.py +1 -1
  16. sqlmesh/core/context.py +69 -26
  17. sqlmesh/core/dialect.py +3 -0
  18. sqlmesh/core/engine_adapter/_typing.py +2 -0
  19. sqlmesh/core/engine_adapter/base.py +322 -22
  20. sqlmesh/core/engine_adapter/base_postgres.py +17 -1
  21. sqlmesh/core/engine_adapter/bigquery.py +146 -7
  22. sqlmesh/core/engine_adapter/clickhouse.py +17 -13
  23. sqlmesh/core/engine_adapter/databricks.py +33 -2
  24. sqlmesh/core/engine_adapter/fabric.py +10 -29
  25. sqlmesh/core/engine_adapter/mixins.py +142 -48
  26. sqlmesh/core/engine_adapter/mssql.py +15 -4
  27. sqlmesh/core/engine_adapter/mysql.py +2 -2
  28. sqlmesh/core/engine_adapter/postgres.py +9 -3
  29. sqlmesh/core/engine_adapter/redshift.py +4 -0
  30. sqlmesh/core/engine_adapter/risingwave.py +1 -0
  31. sqlmesh/core/engine_adapter/shared.py +6 -0
  32. sqlmesh/core/engine_adapter/snowflake.py +82 -11
  33. sqlmesh/core/engine_adapter/spark.py +14 -10
  34. sqlmesh/core/engine_adapter/trino.py +4 -2
  35. sqlmesh/core/environment.py +2 -0
  36. sqlmesh/core/janitor.py +181 -0
  37. sqlmesh/core/lineage.py +1 -0
  38. sqlmesh/core/linter/definition.py +13 -13
  39. sqlmesh/core/linter/rules/builtin.py +29 -0
  40. sqlmesh/core/macros.py +35 -13
  41. sqlmesh/core/model/common.py +2 -0
  42. sqlmesh/core/model/definition.py +82 -28
  43. sqlmesh/core/model/kind.py +66 -2
  44. sqlmesh/core/model/meta.py +108 -4
  45. sqlmesh/core/node.py +101 -1
  46. sqlmesh/core/plan/builder.py +18 -10
  47. sqlmesh/core/plan/common.py +199 -2
  48. sqlmesh/core/plan/definition.py +25 -6
  49. sqlmesh/core/plan/evaluator.py +75 -113
  50. sqlmesh/core/plan/explainer.py +90 -8
  51. sqlmesh/core/plan/stages.py +42 -21
  52. sqlmesh/core/renderer.py +78 -32
  53. sqlmesh/core/scheduler.py +102 -22
  54. sqlmesh/core/selector.py +137 -9
  55. sqlmesh/core/signal.py +64 -1
  56. sqlmesh/core/snapshot/__init__.py +2 -0
  57. sqlmesh/core/snapshot/definition.py +146 -34
  58. sqlmesh/core/snapshot/evaluator.py +689 -124
  59. sqlmesh/core/state_sync/__init__.py +0 -1
  60. sqlmesh/core/state_sync/base.py +55 -33
  61. sqlmesh/core/state_sync/cache.py +12 -7
  62. sqlmesh/core/state_sync/common.py +216 -111
  63. sqlmesh/core/state_sync/db/environment.py +6 -4
  64. sqlmesh/core/state_sync/db/facade.py +42 -24
  65. sqlmesh/core/state_sync/db/interval.py +27 -7
  66. sqlmesh/core/state_sync/db/migrator.py +34 -16
  67. sqlmesh/core/state_sync/db/snapshot.py +177 -169
  68. sqlmesh/core/table_diff.py +2 -2
  69. sqlmesh/core/test/context.py +2 -0
  70. sqlmesh/core/test/definition.py +14 -9
  71. sqlmesh/dbt/adapter.py +22 -16
  72. sqlmesh/dbt/basemodel.py +75 -56
  73. sqlmesh/dbt/builtin.py +116 -12
  74. sqlmesh/dbt/column.py +17 -5
  75. sqlmesh/dbt/common.py +19 -5
  76. sqlmesh/dbt/context.py +14 -1
  77. sqlmesh/dbt/loader.py +61 -9
  78. sqlmesh/dbt/manifest.py +174 -16
  79. sqlmesh/dbt/model.py +183 -85
  80. sqlmesh/dbt/package.py +16 -1
  81. sqlmesh/dbt/profile.py +3 -3
  82. sqlmesh/dbt/project.py +12 -7
  83. sqlmesh/dbt/seed.py +6 -1
  84. sqlmesh/dbt/source.py +13 -1
  85. sqlmesh/dbt/target.py +25 -6
  86. sqlmesh/dbt/test.py +36 -5
  87. sqlmesh/migrations/v0000_baseline.py +95 -0
  88. sqlmesh/migrations/v0061_mysql_fix_blob_text_type.py +5 -7
  89. sqlmesh/migrations/v0062_add_model_gateway.py +5 -1
  90. sqlmesh/migrations/v0063_change_signals.py +5 -3
  91. sqlmesh/migrations/v0064_join_when_matched_strings.py +5 -3
  92. sqlmesh/migrations/v0065_add_model_optimize.py +5 -1
  93. sqlmesh/migrations/v0066_add_auto_restatements.py +8 -3
  94. sqlmesh/migrations/v0067_add_tsql_date_full_precision.py +5 -1
  95. sqlmesh/migrations/v0068_include_unrendered_query_in_metadata_hash.py +5 -1
  96. sqlmesh/migrations/v0069_update_dev_table_suffix.py +5 -3
  97. sqlmesh/migrations/v0070_include_grains_in_metadata_hash.py +5 -1
  98. sqlmesh/migrations/v0071_add_dev_version_to_intervals.py +9 -5
  99. sqlmesh/migrations/v0072_add_environment_statements.py +5 -3
  100. sqlmesh/migrations/v0073_remove_symbolic_disable_restatement.py +5 -3
  101. sqlmesh/migrations/v0074_add_partition_by_time_column_property.py +5 -1
  102. sqlmesh/migrations/v0075_remove_validate_query.py +5 -3
  103. sqlmesh/migrations/v0076_add_cron_tz.py +5 -1
  104. sqlmesh/migrations/v0077_fix_column_type_hash_calculation.py +5 -1
  105. sqlmesh/migrations/v0078_warn_if_non_migratable_python_env.py +5 -3
  106. sqlmesh/migrations/v0079_add_gateway_managed_property.py +10 -5
  107. sqlmesh/migrations/v0080_add_batch_size_to_scd_type_2_models.py +5 -1
  108. sqlmesh/migrations/v0081_update_partitioned_by.py +5 -3
  109. sqlmesh/migrations/v0082_warn_if_incorrectly_duplicated_statements.py +5 -3
  110. sqlmesh/migrations/v0083_use_sql_for_scd_time_data_type_data_hash.py +5 -1
  111. sqlmesh/migrations/v0084_normalize_quote_when_matched_and_merge_filter.py +5 -1
  112. sqlmesh/migrations/v0085_deterministic_repr.py +5 -3
  113. sqlmesh/migrations/v0086_check_deterministic_bug.py +5 -3
  114. sqlmesh/migrations/v0087_normalize_blueprint_variables.py +5 -3
  115. sqlmesh/migrations/v0088_warn_about_variable_python_env_diffs.py +5 -3
  116. sqlmesh/migrations/v0089_add_virtual_environment_mode.py +5 -1
  117. sqlmesh/migrations/v0090_add_forward_only_column.py +9 -5
  118. sqlmesh/migrations/v0091_on_additive_change.py +5 -1
  119. sqlmesh/migrations/v0092_warn_about_dbt_data_type_diff.py +5 -3
  120. sqlmesh/migrations/v0093_use_raw_sql_in_fingerprint.py +5 -1
  121. sqlmesh/migrations/v0094_add_dev_version_and_fingerprint_columns.py +123 -0
  122. sqlmesh/migrations/v0095_warn_about_dbt_raw_sql_diff.py +49 -0
  123. sqlmesh/migrations/v0096_remove_plan_dags_table.py +13 -0
  124. sqlmesh/migrations/v0097_add_dbt_name_in_node.py +9 -0
  125. sqlmesh/migrations/{v0060_move_audits_to_model.py → v0098_add_dbt_node_info_in_node.py} +33 -16
  126. sqlmesh/migrations/v0099_add_last_altered_to_intervals.py +25 -0
  127. sqlmesh/migrations/v0100_add_grants_and_grants_target_layer.py +9 -0
  128. sqlmesh/utils/__init__.py +8 -1
  129. sqlmesh/utils/cache.py +5 -1
  130. sqlmesh/utils/connection_pool.py +2 -1
  131. sqlmesh/utils/dag.py +65 -10
  132. sqlmesh/utils/date.py +8 -1
  133. sqlmesh/utils/errors.py +8 -0
  134. sqlmesh/utils/jinja.py +54 -4
  135. sqlmesh/utils/pydantic.py +6 -6
  136. sqlmesh/utils/windows.py +13 -3
  137. {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/METADATA +7 -10
  138. sqlmesh-0.227.2.dev4.dist-info/RECORD +370 -0
  139. sqlmesh_dbt/cli.py +70 -7
  140. sqlmesh_dbt/console.py +14 -6
  141. sqlmesh_dbt/operations.py +103 -24
  142. sqlmesh_dbt/selectors.py +39 -1
  143. web/client/dist/assets/{Audits-Ucsx1GzF.js → Audits-CBiYyyx-.js} +1 -1
  144. web/client/dist/assets/{Banner-BWDzvavM.js → Banner-DSRbUlO5.js} +1 -1
  145. web/client/dist/assets/{ChevronDownIcon-D2VL13Ah.js → ChevronDownIcon-MK_nrjD_.js} +1 -1
  146. web/client/dist/assets/{ChevronRightIcon-DWGYbf1l.js → ChevronRightIcon-CLWtT22Q.js} +1 -1
  147. web/client/dist/assets/{Content-DdHDZM3I.js → Content-BNuGZN5l.js} +1 -1
  148. web/client/dist/assets/{Content-Bikfy8fh.js → Content-CSHJyW0n.js} +1 -1
  149. web/client/dist/assets/{Data-CzAJH7rW.js → Data-C1oRDbLx.js} +1 -1
  150. web/client/dist/assets/{DataCatalog-BJF11g8f.js → DataCatalog-HXyX2-_j.js} +1 -1
  151. web/client/dist/assets/{Editor-s0SBpV2y.js → Editor-BDyfpUuw.js} +1 -1
  152. web/client/dist/assets/{Editor-DgLhgKnm.js → Editor-D0jNItwC.js} +1 -1
  153. web/client/dist/assets/{Errors-D0m0O1d3.js → Errors-BfuFLcPi.js} +1 -1
  154. web/client/dist/assets/{FileExplorer-CEv0vXkt.js → FileExplorer-BR9IE3he.js} +1 -1
  155. web/client/dist/assets/{Footer-BwzXn8Ew.js → Footer-CgBEtiAh.js} +1 -1
  156. web/client/dist/assets/{Header-6heDkEqG.js → Header-DSqR6nSO.js} +1 -1
  157. web/client/dist/assets/{Input-obuJsD6k.js → Input-B-oZ6fGO.js} +1 -1
  158. web/client/dist/assets/Lineage-DYQVwDbD.js +1 -0
  159. web/client/dist/assets/{ListboxShow-HM9_qyrt.js → ListboxShow-BE5-xevs.js} +1 -1
  160. web/client/dist/assets/{ModelLineage-zWdKo0U2.js → ModelLineage-DkIFAYo4.js} +1 -1
  161. web/client/dist/assets/{Models-Bcu66SRz.js → Models-D5dWr8RB.js} +1 -1
  162. web/client/dist/assets/{Page-BWEEQfIt.js → Page-C-XfU5BR.js} +1 -1
  163. web/client/dist/assets/{Plan-C4gXCqlf.js → Plan-ZEuTINBq.js} +1 -1
  164. web/client/dist/assets/{PlusCircleIcon-CVDO651q.js → PlusCircleIcon-DVXAHG8_.js} +1 -1
  165. web/client/dist/assets/{ReportErrors-BT6xFwAr.js → ReportErrors-B7FEPzMB.js} +1 -1
  166. web/client/dist/assets/{Root-ryJoBK4h.js → Root-8aZyhPxF.js} +1 -1
  167. web/client/dist/assets/{SearchList-DB04sPb9.js → SearchList-W_iT2G82.js} +1 -1
  168. web/client/dist/assets/{SelectEnvironment-CUYcXUu6.js → SelectEnvironment-C65jALmO.js} +1 -1
  169. web/client/dist/assets/{SourceList-Doo_9ZGp.js → SourceList-DSLO6nVJ.js} +1 -1
  170. web/client/dist/assets/{SourceListItem-D5Mj7Dly.js → SourceListItem-BHt8d9-I.js} +1 -1
  171. web/client/dist/assets/{SplitPane-qHmkD1qy.js → SplitPane-CViaZmw6.js} +1 -1
  172. web/client/dist/assets/{Tests-DH1Z74ML.js → Tests-DhaVt5t1.js} +1 -1
  173. web/client/dist/assets/{Welcome-DqUJUNMF.js → Welcome-DvpjH-_4.js} +1 -1
  174. web/client/dist/assets/context-BctCsyGb.js +71 -0
  175. web/client/dist/assets/{context-Dr54UHLi.js → context-DFNeGsFF.js} +1 -1
  176. web/client/dist/assets/{editor-DYIP1yQ4.js → editor-CcO28cqd.js} +1 -1
  177. web/client/dist/assets/{file-DarlIDVi.js → file-CvJN3aZO.js} +1 -1
  178. web/client/dist/assets/{floating-ui.react-dom-BH3TFvkM.js → floating-ui.react-dom-CjE-JNW1.js} +1 -1
  179. web/client/dist/assets/{help-Bl8wqaQc.js → help-DuPhjipa.js} +1 -1
  180. web/client/dist/assets/{index-D1sR7wpN.js → index-C-dJH7yZ.js} +1 -1
  181. web/client/dist/assets/{index-O3mjYpnE.js → index-Dj0i1-CA.js} +2 -2
  182. web/client/dist/assets/{plan-CehRrJUG.js → plan-BTRSbjKn.js} +1 -1
  183. web/client/dist/assets/{popover-CqgMRE0G.js → popover-_Sf0yvOI.js} +1 -1
  184. web/client/dist/assets/{project-6gxepOhm.js → project-BvSOI8MY.js} +1 -1
  185. web/client/dist/index.html +1 -1
  186. sqlmesh/integrations/llm.py +0 -56
  187. sqlmesh/migrations/v0001_init.py +0 -60
  188. sqlmesh/migrations/v0002_remove_identify.py +0 -5
  189. sqlmesh/migrations/v0003_move_batch_size.py +0 -34
  190. sqlmesh/migrations/v0004_environmnent_add_finalized_at.py +0 -23
  191. sqlmesh/migrations/v0005_create_seed_table.py +0 -24
  192. sqlmesh/migrations/v0006_change_seed_hash.py +0 -5
  193. sqlmesh/migrations/v0007_env_table_info_to_kind.py +0 -99
  194. sqlmesh/migrations/v0008_create_intervals_table.py +0 -38
  195. sqlmesh/migrations/v0009_remove_pre_post_hooks.py +0 -62
  196. sqlmesh/migrations/v0010_seed_hash_batch_size.py +0 -5
  197. sqlmesh/migrations/v0011_add_model_kind_name.py +0 -63
  198. sqlmesh/migrations/v0012_update_jinja_expressions.py +0 -86
  199. sqlmesh/migrations/v0013_serde_using_model_dialects.py +0 -87
  200. sqlmesh/migrations/v0014_fix_dev_intervals.py +0 -14
  201. sqlmesh/migrations/v0015_environment_add_promoted_snapshot_ids.py +0 -26
  202. sqlmesh/migrations/v0016_fix_windows_path.py +0 -59
  203. sqlmesh/migrations/v0017_fix_windows_seed_path.py +0 -55
  204. sqlmesh/migrations/v0018_rename_snapshot_model_to_node.py +0 -53
  205. sqlmesh/migrations/v0019_add_env_suffix_target.py +0 -28
  206. sqlmesh/migrations/v0020_remove_redundant_attributes_from_dbt_models.py +0 -80
  207. sqlmesh/migrations/v0021_fix_table_properties.py +0 -62
  208. sqlmesh/migrations/v0022_move_project_to_model.py +0 -54
  209. sqlmesh/migrations/v0023_fix_added_models_with_forward_only_parents.py +0 -65
  210. sqlmesh/migrations/v0024_replace_model_kind_name_enum_with_value.py +0 -55
  211. sqlmesh/migrations/v0025_fix_intervals_and_missing_change_category.py +0 -117
  212. sqlmesh/migrations/v0026_remove_dialect_from_seed.py +0 -55
  213. sqlmesh/migrations/v0027_minute_interval_to_five.py +0 -57
  214. sqlmesh/migrations/v0028_add_plan_dags_table.py +0 -29
  215. sqlmesh/migrations/v0029_generate_schema_types_using_dialect.py +0 -69
  216. sqlmesh/migrations/v0030_update_unrestorable_snapshots.py +0 -65
  217. sqlmesh/migrations/v0031_remove_dbt_target_fields.py +0 -65
  218. sqlmesh/migrations/v0032_add_sqlmesh_version.py +0 -25
  219. sqlmesh/migrations/v0033_mysql_fix_blob_text_type.py +0 -45
  220. sqlmesh/migrations/v0034_add_default_catalog.py +0 -367
  221. sqlmesh/migrations/v0035_add_catalog_name_override.py +0 -22
  222. sqlmesh/migrations/v0036_delete_plan_dags_bug_fix.py +0 -14
  223. sqlmesh/migrations/v0037_remove_dbt_is_incremental_macro.py +0 -61
  224. sqlmesh/migrations/v0038_add_expiration_ts_to_snapshot.py +0 -73
  225. sqlmesh/migrations/v0039_include_environment_in_plan_dag_spec.py +0 -68
  226. sqlmesh/migrations/v0040_add_previous_finalized_snapshots.py +0 -26
  227. sqlmesh/migrations/v0041_remove_hash_raw_query_attribute.py +0 -59
  228. sqlmesh/migrations/v0042_trim_indirect_versions.py +0 -66
  229. sqlmesh/migrations/v0043_fix_remove_obsolete_attributes_in_plan_dags.py +0 -61
  230. sqlmesh/migrations/v0044_quote_identifiers_in_model_attributes.py +0 -5
  231. sqlmesh/migrations/v0045_move_gateway_variable.py +0 -70
  232. sqlmesh/migrations/v0046_add_batch_concurrency.py +0 -8
  233. sqlmesh/migrations/v0047_change_scd_string_to_column.py +0 -5
  234. sqlmesh/migrations/v0048_drop_indirect_versions.py +0 -59
  235. sqlmesh/migrations/v0049_replace_identifier_with_version_in_seeds_table.py +0 -57
  236. sqlmesh/migrations/v0050_drop_seeds_table.py +0 -11
  237. sqlmesh/migrations/v0051_rename_column_descriptions.py +0 -65
  238. sqlmesh/migrations/v0052_add_normalize_name_in_environment_naming_info.py +0 -28
  239. sqlmesh/migrations/v0053_custom_model_kind_extra_attributes.py +0 -5
  240. sqlmesh/migrations/v0054_fix_trailing_comments.py +0 -5
  241. sqlmesh/migrations/v0055_add_updated_ts_unpaused_ts_ttl_ms_unrestorable_to_snapshot.py +0 -132
  242. sqlmesh/migrations/v0056_restore_table_indexes.py +0 -118
  243. sqlmesh/migrations/v0057_add_table_format.py +0 -5
  244. sqlmesh/migrations/v0058_add_requirements.py +0 -26
  245. sqlmesh/migrations/v0059_add_physical_version.py +0 -5
  246. sqlmesh-0.213.1.dev1.dist-info/RECORD +0 -421
  247. web/client/dist/assets/Lineage-D0Hgdz2v.js +0 -1
  248. web/client/dist/assets/context-DgX0fp2E.js +0 -68
  249. {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/WHEEL +0 -0
  250. {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/entry_points.txt +0 -0
  251. {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/licenses/LICENSE +0 -0
  252. {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 # TODO not yet supported
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(cls, v: t.Dict[str, str]) -> t.Dict[str, t.List[str]]:
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
- def fix_circular_test_refs(self, context: DbtContext) -> None:
270
- """
271
- Checks for direct circular references between two models and moves the test to the downstream
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 node_name(self) -> str:
302
- resource_type = getattr(self, "resource_type", "model")
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.name, {}) for test in self.tests],
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({source.canonical_name(context) for source in model_context.sources.values()}),
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": [d.jinja_statement(hook.sql) for hook in self.pre_hook],
341
- "post_statements": [d.jinja_statement(hook.sql) for hook in self.post_hook],
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
- model_node: AttributeDict[str, t.Any] = AttributeDict(
379
- {
380
- k: v
381
- for k, v in context._manifest._manifest.nodes[self.node_name].to_dict().items()
382
- if k in dependencies.model_attrs
383
- }
384
- if context._manifest and self.node_name in context._manifest._manifest.nodes
385
- else {}
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, MacroEvalError
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: str) -> bool:
299
- result = _try_literal_eval(value)
300
- if isinstance(result, bool):
301
- return result
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
- result = _try_literal_eval(value)
307
- if isinstance(value, (int, float)) and not isinstance(result, bool):
308
- return result
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
- return {
35
- name: parse_one(column.data_type, into=exp.DataType, dialect=dialect or "")
36
- for name, column in columns.items()
37
- if column.enabled and column.data_type
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: t.Set[str] = set()
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=self.model_attrs | other.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
- raise ConfigError(f"Failed to render variable '{k}', value '{v}': {ex}") from ex
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