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/loader.py CHANGED
@@ -5,11 +5,13 @@ import sys
5
5
  import typing as t
6
6
  import sqlmesh.core.dialect as d
7
7
  from pathlib import Path
8
+ from collections import defaultdict
8
9
  from sqlmesh.core.config import (
9
10
  Config,
10
11
  ConnectionConfig,
11
12
  GatewayConfig,
12
13
  ModelDefaultsConfig,
14
+ DbtConfig as RootDbtConfig,
13
15
  )
14
16
  from sqlmesh.core.environment import EnvironmentStatements
15
17
  from sqlmesh.core.loader import CacheBase, LoadedProject, Loader
@@ -48,11 +50,21 @@ def sqlmesh_config(
48
50
  dbt_profile_name: t.Optional[str] = None,
49
51
  dbt_target_name: t.Optional[str] = None,
50
52
  variables: t.Optional[t.Dict[str, t.Any]] = None,
53
+ threads: t.Optional[int] = None,
51
54
  register_comments: t.Optional[bool] = None,
55
+ infer_state_schema_name: bool = False,
56
+ profiles_dir: t.Optional[Path] = None,
52
57
  **kwargs: t.Any,
53
58
  ) -> Config:
54
59
  project_root = project_root or Path()
55
- context = DbtContext(project_root=project_root, profile_name=dbt_profile_name)
60
+ context = DbtContext(
61
+ project_root=project_root, profiles_dir=profiles_dir, profile_name=dbt_profile_name
62
+ )
63
+
64
+ # note: Profile.load() is called twice with different DbtContext's:
65
+ # - once here with the above DbtContext (to determine connnection / gateway config which has to be set up before everything else)
66
+ # - again on the SQLMesh side via GenericContext.load() -> DbtLoader._load_projects() -> Project.load() which constructs a fresh DbtContext and ignores the above one
67
+ # it's important to ensure that the DbtContext created within the DbtLoader uses the same project root / profiles dir that we use here
56
68
  profile = Profile.load(context, target_name=dbt_target_name)
57
69
  model_defaults = kwargs.pop("model_defaults", ModelDefaultsConfig())
58
70
  if model_defaults.dialect is None:
@@ -66,16 +78,45 @@ def sqlmesh_config(
66
78
  if not issubclass(loader, DbtLoader):
67
79
  raise ConfigError("The loader must be a DbtLoader.")
68
80
 
81
+ if threads is not None:
82
+ # the to_sqlmesh() function on TargetConfig maps self.threads -> concurrent_tasks
83
+ profile.target.threads = threads
84
+
85
+ gateway_kwargs = {}
86
+ if infer_state_schema_name:
87
+ profile_name = context.profile_name
88
+
89
+ # Note: we deliberately isolate state based on the target *schema* and not the target name.
90
+ # It is assumed that the project will define a target, eg 'dev', and then in each users own ~/.dbt/profiles.yml the schema
91
+ # for the 'dev' target is overriden to something user-specific, rather than making the target name itself user-specific.
92
+ # This means that the schema name is the indicator of isolated state, not the target name which may be re-used across multiple schemas.
93
+ target_schema = profile.target.schema_
94
+
95
+ # dbt-core doesnt allow schema to be undefined, but it does allow an empty string, and then just
96
+ # fails at runtime when `CREATE SCHEMA ""` doesnt work
97
+ if not target_schema:
98
+ raise ConfigError(
99
+ f"Target '{profile.target_name}' does not specify a schema.\n"
100
+ "A schema is required in order to infer where to store SQLMesh state"
101
+ )
102
+
103
+ inferred_state_schema_name = f"sqlmesh_state_{profile_name}_{target_schema}"
104
+ logger.info("Inferring state schema: %s", inferred_state_schema_name)
105
+ gateway_kwargs["state_schema"] = inferred_state_schema_name
106
+
69
107
  return Config(
70
108
  loader=loader,
109
+ loader_kwargs=dict(profiles_dir=profiles_dir),
71
110
  model_defaults=model_defaults,
72
111
  variables=variables or {},
112
+ dbt=RootDbtConfig(infer_state_schema_name=infer_state_schema_name),
73
113
  **{
74
114
  "default_gateway": profile.target_name if "gateways" not in kwargs else "",
75
115
  "gateways": {
76
116
  profile.target_name: GatewayConfig(
77
117
  connection=profile.target.to_sqlmesh(**target_to_sqlmesh_args),
78
118
  state_connection=state_connection,
119
+ **gateway_kwargs,
79
120
  )
80
121
  }, # type: ignore
81
122
  **kwargs,
@@ -84,9 +125,12 @@ def sqlmesh_config(
84
125
 
85
126
 
86
127
  class DbtLoader(Loader):
87
- def __init__(self, context: GenericContext, path: Path) -> None:
128
+ def __init__(
129
+ self, context: GenericContext, path: Path, profiles_dir: t.Optional[Path] = None
130
+ ) -> None:
88
131
  self._projects: t.List[Project] = []
89
132
  self._macros_max_mtime: t.Optional[float] = None
133
+ self._profiles_dir = profiles_dir
90
134
  super().__init__(context, path)
91
135
 
92
136
  def load(self) -> LoadedProject:
@@ -137,16 +181,22 @@ class DbtLoader(Loader):
137
181
  package_context.set_and_render_variables(package.variables, package.name)
138
182
  package_models: t.Dict[str, BaseModelConfig] = {**package.models, **package.seeds}
139
183
 
184
+ package_models_by_path: t.Dict[Path, t.List[BaseModelConfig]] = defaultdict(list)
140
185
  for model in package_models.values():
141
- if isinstance(model, ModelConfig) and not model.sql_no_config:
186
+ if isinstance(model, ModelConfig) and not model.sql.strip():
142
187
  logger.info(f"Skipping empty model '{model.name}' at path '{model.path}'.")
143
188
  continue
189
+ package_models_by_path[model.path].append(model)
144
190
 
145
- sqlmesh_model = cache.get_or_load_models(
146
- model.path, loader=lambda: [_to_sqlmesh(model, package_context)]
147
- )[0]
148
-
149
- models[sqlmesh_model.fqn] = sqlmesh_model
191
+ for path, path_models in package_models_by_path.items():
192
+ sqlmesh_models = cache.get_or_load_models(
193
+ path,
194
+ loader=lambda: [
195
+ _to_sqlmesh(model, package_context) for model in path_models
196
+ ],
197
+ )
198
+ for sqlmesh_model in sqlmesh_models:
199
+ models[sqlmesh_model.fqn] = sqlmesh_model
150
200
 
151
201
  models.update(self._load_external_models(audits, cache))
152
202
 
@@ -165,7 +215,8 @@ class DbtLoader(Loader):
165
215
  for test in package.tests.values():
166
216
  logger.debug("Converting '%s' to sqlmesh format", test.name)
167
217
  try:
168
- audits[test.name] = test.to_sqlmesh(package_context)
218
+ audits[test.canonical_name] = test.to_sqlmesh(package_context)
219
+
169
220
  except BaseMissingReferenceError as e:
170
221
  ref_type = "model" if isinstance(e, MissingModelError) else "source"
171
222
  logger.warning(
@@ -186,6 +237,7 @@ class DbtLoader(Loader):
186
237
  project = Project.load(
187
238
  DbtContext(
188
239
  project_root=self.config_path,
240
+ profiles_dir=self._profiles_dir,
189
241
  target_name=target_name,
190
242
  sqlmesh_config=self.config,
191
243
  ),
sqlmesh/dbt/manifest.py CHANGED
@@ -11,7 +11,7 @@ from collections import defaultdict
11
11
  from functools import cached_property
12
12
  from pathlib import Path
13
13
 
14
- from dbt import constants as dbt_constants, flags
14
+ from dbt import flags
15
15
 
16
16
  from sqlmesh.dbt.util import DBT_VERSION
17
17
  from sqlmesh.utils.conversions import make_serializable
@@ -19,6 +19,8 @@ from sqlmesh.utils.conversions import make_serializable
19
19
  # Override the file name to prevent dbt commands from invalidating the cache.
20
20
 
21
21
  if DBT_VERSION >= (1, 6, 0):
22
+ from dbt import constants as dbt_constants
23
+
22
24
  dbt_constants.PARTIAL_PARSE_FILE_NAME = "sqlmesh_partial_parse.msgpack" # type: ignore
23
25
  else:
24
26
  from dbt.parser import manifest as dbt_manifest # type: ignore
@@ -44,10 +46,10 @@ from dbt.tracking import do_not_track
44
46
  from sqlmesh.core import constants as c
45
47
  from sqlmesh.utils.errors import SQLMeshError
46
48
  from sqlmesh.core.config import ModelDefaultsConfig
47
- from sqlmesh.dbt.basemodel import Dependencies
48
49
  from sqlmesh.dbt.builtin import BUILTIN_FILTERS, BUILTIN_GLOBALS, OVERRIDDEN_MACROS
50
+ from sqlmesh.dbt.common import Dependencies
49
51
  from sqlmesh.dbt.model import ModelConfig
50
- from sqlmesh.dbt.package import HookConfig, MacroConfig
52
+ from sqlmesh.dbt.package import HookConfig, MacroConfig, MaterializationConfig
51
53
  from sqlmesh.dbt.seed import SeedConfig
52
54
  from sqlmesh.dbt.source import SourceConfig
53
55
  from sqlmesh.dbt.target import TargetConfig
@@ -61,6 +63,7 @@ from sqlmesh.utils.jinja import (
61
63
  extract_call_names,
62
64
  jinja_call_arg_name,
63
65
  )
66
+ from sqlglot.helper import ensure_list
64
67
 
65
68
  if t.TYPE_CHECKING:
66
69
  from dbt.contracts.graph.manifest import Macro, Manifest
@@ -75,6 +78,7 @@ SeedConfigs = t.Dict[str, SeedConfig]
75
78
  SourceConfigs = t.Dict[str, SourceConfig]
76
79
  MacroConfigs = t.Dict[str, MacroConfig]
77
80
  HookConfigs = t.Dict[str, HookConfig]
81
+ MaterializationConfigs = t.Dict[str, MaterializationConfig]
78
82
 
79
83
 
80
84
  IGNORED_PACKAGES = {"elementary"}
@@ -135,6 +139,7 @@ class ManifestHelper:
135
139
 
136
140
  self._on_run_start_per_package: t.Dict[str, HookConfigs] = defaultdict(dict)
137
141
  self._on_run_end_per_package: t.Dict[str, HookConfigs] = defaultdict(dict)
142
+ self._materializations: MaterializationConfigs = {}
138
143
 
139
144
  def tests(self, package_name: t.Optional[str] = None) -> TestConfigs:
140
145
  self._load_all()
@@ -164,6 +169,10 @@ class ManifestHelper:
164
169
  self._load_all()
165
170
  return self._on_run_end_per_package[package_name or self._project_name]
166
171
 
172
+ def materializations(self) -> MaterializationConfigs:
173
+ self._load_all()
174
+ return self._materializations
175
+
167
176
  @property
168
177
  def all_macros(self) -> t.Dict[str, t.Dict[str, MacroInfo]]:
169
178
  self._load_all()
@@ -213,6 +222,7 @@ class ManifestHelper:
213
222
  self._calls = {k: (v, False) for k, v in (self._call_cache.get("") or {}).items()}
214
223
 
215
224
  self._load_macros()
225
+ self._load_materializations()
216
226
  self._load_sources()
217
227
  self._load_tests()
218
228
  self._load_models_and_seeds()
@@ -250,11 +260,14 @@ class ManifestHelper:
250
260
 
251
261
  def _load_macros(self) -> None:
252
262
  for macro in self._manifest.macros.values():
263
+ if macro.name.startswith("materialization_"):
264
+ continue
265
+
253
266
  if macro.name.startswith("test_"):
254
267
  macro.macro_sql = _convert_jinja_test_to_macro(macro.macro_sql)
255
268
 
256
269
  dependencies = Dependencies(macros=_macro_references(self._manifest, macro))
257
- if not macro.name.startswith("materialization_") and not macro.name.startswith("test_"):
270
+ if not macro.name.startswith("test_"):
258
271
  dependencies = dependencies.union(
259
272
  self._extra_dependencies(macro.macro_sql, macro.package_name)
260
273
  )
@@ -281,6 +294,32 @@ class ManifestHelper:
281
294
  if pos > 0 and name[pos + 2 :] in adapter_macro_names:
282
295
  macro_config.info.is_top_level = True
283
296
 
297
+ def _load_materializations(self) -> None:
298
+ for macro in self._manifest.macros.values():
299
+ if macro.name.startswith("materialization_"):
300
+ # Extract name and adapter ( "materialization_{name}_{adapter}" or "materialization_{name}_default")
301
+ name_parts = macro.name.split("_")
302
+ if len(name_parts) >= 3:
303
+ mat_name = "_".join(name_parts[1:-1])
304
+ adapter = name_parts[-1]
305
+
306
+ dependencies = Dependencies(macros=_macro_references(self._manifest, macro))
307
+ macro.macro_sql = _strip_jinja_materialization_tags(macro.macro_sql)
308
+ dependencies = dependencies.union(
309
+ self._extra_dependencies(macro.macro_sql, macro.package_name)
310
+ )
311
+
312
+ materialization_config = MaterializationConfig(
313
+ name=mat_name,
314
+ adapter=adapter,
315
+ definition=macro.macro_sql,
316
+ dependencies=dependencies,
317
+ path=Path(macro.original_file_path),
318
+ )
319
+
320
+ key = f"{mat_name}_{adapter}"
321
+ self._materializations[key] = materialization_config
322
+
284
323
  def _load_tests(self) -> None:
285
324
  for node in self._manifest.nodes.values():
286
325
  if node.resource_type != "test":
@@ -317,15 +356,17 @@ class ManifestHelper:
317
356
  )
318
357
 
319
358
  test_model = _test_model(node)
359
+ node_config = _node_base_config(node)
360
+ node_config["name"] = _build_test_name(node, dependencies)
320
361
 
321
362
  test = TestConfig(
322
363
  sql=sql,
323
364
  model_name=test_model,
324
365
  test_kwargs=node.test_metadata.kwargs if hasattr(node, "test_metadata") else {},
325
366
  dependencies=dependencies,
326
- **_node_base_config(node),
367
+ **node_config,
327
368
  )
328
- self._tests_per_package[node.package_name][node.name.lower()] = test
369
+ self._tests_per_package[node.package_name][node.unique_id] = test
329
370
  if test_model:
330
371
  self._tests_by_owner[test_model].append(test)
331
372
 
@@ -338,10 +379,12 @@ class ManifestHelper:
338
379
  continue
339
380
 
340
381
  macro_references = _macro_references(self._manifest, node)
341
- tests = (
382
+ all_tests = (
342
383
  self._tests_by_owner[node.name]
343
384
  + self._tests_by_owner[f"{node.package_name}.{node.name}"]
344
385
  )
386
+ # Only include non-standalone tests (tests that don't reference other models)
387
+ tests = [test for test in all_tests if not test.is_standalone]
345
388
  node_config = _node_base_config(node)
346
389
 
347
390
  node_name = node.name
@@ -354,7 +397,15 @@ class ManifestHelper:
354
397
  dependencies = Dependencies(
355
398
  macros=macro_references, refs=_refs(node), sources=_sources(node)
356
399
  )
357
- dependencies = dependencies.union(self._extra_dependencies(sql, node.package_name))
400
+ dependencies = dependencies.union(
401
+ self._extra_dependencies(sql, node.package_name, track_all_model_attrs=True)
402
+ )
403
+ for hook in [*node_config.get("pre-hook", []), *node_config.get("post-hook", [])]:
404
+ dependencies = dependencies.union(
405
+ self._extra_dependencies(
406
+ hook["sql"], node.package_name, track_all_model_attrs=True
407
+ )
408
+ )
358
409
  dependencies = dependencies.union(
359
410
  self._flatten_dependencies_from_macros(dependencies.macros, node.package_name)
360
411
  )
@@ -552,17 +603,37 @@ class ManifestHelper:
552
603
  dependencies = dependencies.union(macro_dependencies)
553
604
  return dependencies
554
605
 
555
- def _extra_dependencies(self, target: str, package: str) -> Dependencies:
556
- # We sometimes observe that the manifest doesn't capture all macros, refs, and sources within a macro.
557
- # This behavior has been observed with macros like dbt.current_timestamp(), dbt_utils.slugify(), and source().
558
- # Here we apply our custom extractor to make a best effort to supplement references captured in the manifest.
606
+ def _extra_dependencies(
607
+ self,
608
+ target: str,
609
+ package: str,
610
+ track_all_model_attrs: bool = False,
611
+ ) -> Dependencies:
612
+ """
613
+ We sometimes observe that the manifest doesn't capture all macros, refs, and sources within a macro.
614
+ This behavior has been observed with macros like dbt.current_timestamp(), dbt_utils.slugify(), and source().
615
+ Here we apply our custom extractor to make a best effort to supplement references captured in the manifest.
616
+ """
559
617
  dependencies = Dependencies()
618
+
619
+ # Whether all `model` attributes (e.g., `model.config`) should be included in the dependencies
620
+ all_model_attrs = False
621
+
560
622
  for call_name, node in extract_call_names(target, cache=self._calls):
561
623
  if call_name[0] == "config":
562
624
  continue
563
- elif isinstance(node, jinja2.nodes.Getattr):
625
+
626
+ if (
627
+ track_all_model_attrs
628
+ and not all_model_attrs
629
+ and isinstance(node, jinja2.nodes.Call)
630
+ and any(isinstance(a, jinja2.nodes.Name) and a.name == "model" for a in node.args)
631
+ ):
632
+ all_model_attrs = True
633
+
634
+ if isinstance(node, jinja2.nodes.Getattr):
564
635
  if call_name[0] == "model":
565
- dependencies.model_attrs.add(call_name[1])
636
+ dependencies.model_attrs.attrs.add(call_name[1])
566
637
  elif call_name[0] == "source":
567
638
  args = [jinja_call_arg_name(arg) for arg in node.args]
568
639
  if args and all(arg for arg in args):
@@ -606,6 +677,14 @@ class ManifestHelper:
606
677
  call_name[0], call_name[1], dependencies.macros.append
607
678
  )
608
679
 
680
+ # When `model` is referenced as-is, e.g. it's passed as an argument to a macro call like
681
+ # `{{ foo(model) }}`, we can't easily track the attributes that are actually used, because
682
+ # it may be aliased and hence tracking actual uses of `model` requires a proper data flow
683
+ # analysis. We conservatively deal with this by including all of its supported attributes
684
+ # if a standalone reference is found.
685
+ if all_model_attrs:
686
+ dependencies.model_attrs.all_attrs = True
687
+
609
688
  return dependencies
610
689
 
611
690
 
@@ -629,7 +708,7 @@ def _macro_references(
629
708
  return result
630
709
 
631
710
  for macro_node_id in node.depends_on.macros:
632
- if not macro_node_id:
711
+ if not macro_node_id or macro_node_id == "None":
633
712
  continue
634
713
 
635
714
  macro_node = manifest.macros[macro_node_id]
@@ -667,7 +746,12 @@ def _test_model(node: ManifestNode) -> t.Optional[str]:
667
746
  attached_node = getattr(node, "attached_node", None)
668
747
  if attached_node:
669
748
  pieces = attached_node.split(".")
670
- return pieces[-1] if pieces[0] in ["model", "seed"] else None
749
+ if pieces[0] in ["model", "seed"]:
750
+ # versioned models have format "model.package.model_name.v1" (4 parts)
751
+ if len(pieces) == 4:
752
+ return f"{pieces[2]}_{pieces[3]}"
753
+ return pieces[-1]
754
+ return None
671
755
 
672
756
  key_name = getattr(node, "file_key_name", None)
673
757
  if key_name:
@@ -700,3 +784,77 @@ def _convert_jinja_test_to_macro(test_jinja: str) -> str:
700
784
  macro = macro_tag + test_jinja[match.span()[-1] :]
701
785
 
702
786
  return re.sub(ENDTEST_REGEX, lambda m: m.group(0).replace("endtest", "endmacro"), macro)
787
+
788
+
789
+ def _strip_jinja_materialization_tags(materialization_jinja: str) -> str:
790
+ MATERIALIZATION_TAG_REGEX = r"\s*{%-?\s*materialization\s+[^%]*%}\s*\n?"
791
+ ENDMATERIALIZATION_REGEX = r"{%-?\s*endmaterialization\s*-?%}\s*\n?"
792
+
793
+ if not re.match(MATERIALIZATION_TAG_REGEX, materialization_jinja):
794
+ return materialization_jinja
795
+
796
+ materialization_jinja = re.sub(
797
+ MATERIALIZATION_TAG_REGEX,
798
+ "",
799
+ materialization_jinja,
800
+ flags=re.IGNORECASE,
801
+ )
802
+
803
+ materialization_jinja = re.sub(
804
+ ENDMATERIALIZATION_REGEX,
805
+ "",
806
+ materialization_jinja,
807
+ flags=re.IGNORECASE,
808
+ )
809
+
810
+ return materialization_jinja.strip()
811
+
812
+
813
+ def _build_test_name(node: ManifestNode, dependencies: Dependencies) -> str:
814
+ """
815
+ Build a user-friendly test name that includes the test's model/source, column,
816
+ and args for tests with custom user names. Needed because dbt only generates these
817
+ names for tests that do not specify the "name" field in their YAML definition.
818
+
819
+ Name structure
820
+ - Model test: [namespace]_[test name]_[model name]_[column name]__[arg values]
821
+ - Source test: [namespace]_source_[test name]_[source name]_[table name]_[column name]__[arg values]
822
+ """
823
+ # standalone test
824
+ if not hasattr(node, "test_metadata"):
825
+ return node.name
826
+
827
+ model_name = _test_model(node)
828
+ source_name = None
829
+ if not model_name and dependencies.sources:
830
+ # extract source and table names
831
+ source_parts = list(dependencies.sources)[0].split(".")
832
+ source_name = "_".join(source_parts) if len(source_parts) == 2 else source_parts[-1]
833
+ entity_name = model_name or source_name or ""
834
+ entity_name = f"_{entity_name}" if entity_name else ""
835
+
836
+ name_prefix = ""
837
+ if namespace := getattr(node.test_metadata, "namespace", None):
838
+ name_prefix += f"{namespace}_"
839
+ if source_name and not model_name:
840
+ name_prefix += "source_"
841
+
842
+ metadata_kwargs = node.test_metadata.kwargs
843
+ arg_val_parts = []
844
+ for arg, val in sorted(metadata_kwargs.items()):
845
+ if arg == "model":
846
+ continue
847
+ if isinstance(val, dict):
848
+ val = list(val.values())
849
+ val = [re.sub("[^0-9a-zA-Z_]+", "_", str(v)) for v in ensure_list(val)]
850
+ arg_val_parts.extend(val)
851
+ unique_args = "__".join(arg_val_parts) if arg_val_parts else ""
852
+ unique_args = f"_{unique_args}" if unique_args else ""
853
+
854
+ auto_name = f"{name_prefix}{node.test_metadata.name}{entity_name}{unique_args}"
855
+
856
+ if node.name == auto_name:
857
+ return node.name
858
+
859
+ custom_prefix = name_prefix if source_name and not model_name else ""
860
+ return f"{custom_prefix}{node.name}{entity_name}{unique_args}"