dvt-core 0.58.6__cp311-cp311-macosx_10_9_x86_64.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 (324) hide show
  1. dbt/__init__.py +7 -0
  2. dbt/_pydantic_shim.py +26 -0
  3. dbt/artifacts/__init__.py +0 -0
  4. dbt/artifacts/exceptions/__init__.py +1 -0
  5. dbt/artifacts/exceptions/schemas.py +31 -0
  6. dbt/artifacts/resources/__init__.py +116 -0
  7. dbt/artifacts/resources/base.py +67 -0
  8. dbt/artifacts/resources/types.py +93 -0
  9. dbt/artifacts/resources/v1/analysis.py +10 -0
  10. dbt/artifacts/resources/v1/catalog.py +23 -0
  11. dbt/artifacts/resources/v1/components.py +274 -0
  12. dbt/artifacts/resources/v1/config.py +277 -0
  13. dbt/artifacts/resources/v1/documentation.py +11 -0
  14. dbt/artifacts/resources/v1/exposure.py +51 -0
  15. dbt/artifacts/resources/v1/function.py +52 -0
  16. dbt/artifacts/resources/v1/generic_test.py +31 -0
  17. dbt/artifacts/resources/v1/group.py +21 -0
  18. dbt/artifacts/resources/v1/hook.py +11 -0
  19. dbt/artifacts/resources/v1/macro.py +29 -0
  20. dbt/artifacts/resources/v1/metric.py +172 -0
  21. dbt/artifacts/resources/v1/model.py +145 -0
  22. dbt/artifacts/resources/v1/owner.py +10 -0
  23. dbt/artifacts/resources/v1/saved_query.py +111 -0
  24. dbt/artifacts/resources/v1/seed.py +41 -0
  25. dbt/artifacts/resources/v1/semantic_layer_components.py +72 -0
  26. dbt/artifacts/resources/v1/semantic_model.py +314 -0
  27. dbt/artifacts/resources/v1/singular_test.py +14 -0
  28. dbt/artifacts/resources/v1/snapshot.py +91 -0
  29. dbt/artifacts/resources/v1/source_definition.py +84 -0
  30. dbt/artifacts/resources/v1/sql_operation.py +10 -0
  31. dbt/artifacts/resources/v1/unit_test_definition.py +77 -0
  32. dbt/artifacts/schemas/__init__.py +0 -0
  33. dbt/artifacts/schemas/base.py +191 -0
  34. dbt/artifacts/schemas/batch_results.py +24 -0
  35. dbt/artifacts/schemas/catalog/__init__.py +11 -0
  36. dbt/artifacts/schemas/catalog/v1/__init__.py +0 -0
  37. dbt/artifacts/schemas/catalog/v1/catalog.py +59 -0
  38. dbt/artifacts/schemas/freshness/__init__.py +1 -0
  39. dbt/artifacts/schemas/freshness/v3/__init__.py +0 -0
  40. dbt/artifacts/schemas/freshness/v3/freshness.py +158 -0
  41. dbt/artifacts/schemas/manifest/__init__.py +2 -0
  42. dbt/artifacts/schemas/manifest/v12/__init__.py +0 -0
  43. dbt/artifacts/schemas/manifest/v12/manifest.py +211 -0
  44. dbt/artifacts/schemas/results.py +147 -0
  45. dbt/artifacts/schemas/run/__init__.py +2 -0
  46. dbt/artifacts/schemas/run/v5/__init__.py +0 -0
  47. dbt/artifacts/schemas/run/v5/run.py +184 -0
  48. dbt/artifacts/schemas/upgrades/__init__.py +4 -0
  49. dbt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
  50. dbt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
  51. dbt/artifacts/utils/validation.py +153 -0
  52. dbt/cli/__init__.py +1 -0
  53. dbt/cli/context.py +17 -0
  54. dbt/cli/exceptions.py +57 -0
  55. dbt/cli/flags.py +560 -0
  56. dbt/cli/main.py +2403 -0
  57. dbt/cli/option_types.py +121 -0
  58. dbt/cli/options.py +80 -0
  59. dbt/cli/params.py +844 -0
  60. dbt/cli/requires.py +490 -0
  61. dbt/cli/resolvers.py +50 -0
  62. dbt/cli/types.py +40 -0
  63. dbt/clients/__init__.py +0 -0
  64. dbt/clients/checked_load.py +83 -0
  65. dbt/clients/git.py +164 -0
  66. dbt/clients/jinja.py +206 -0
  67. dbt/clients/jinja_static.py +245 -0
  68. dbt/clients/registry.py +192 -0
  69. dbt/clients/yaml_helper.py +68 -0
  70. dbt/compilation.py +876 -0
  71. dbt/compute/__init__.py +14 -0
  72. dbt/compute/engines/__init__.py +12 -0
  73. dbt/compute/engines/spark_engine.cpython-311-darwin.so +0 -0
  74. dbt/compute/engines/spark_engine.py +642 -0
  75. dbt/compute/federated_executor.cpython-311-darwin.so +0 -0
  76. dbt/compute/federated_executor.py +1080 -0
  77. dbt/compute/filter_pushdown.cpython-311-darwin.so +0 -0
  78. dbt/compute/filter_pushdown.py +273 -0
  79. dbt/compute/jar_provisioning.cpython-311-darwin.so +0 -0
  80. dbt/compute/jar_provisioning.py +255 -0
  81. dbt/compute/java_compat.cpython-311-darwin.so +0 -0
  82. dbt/compute/java_compat.py +689 -0
  83. dbt/compute/jdbc_utils.cpython-311-darwin.so +0 -0
  84. dbt/compute/jdbc_utils.py +678 -0
  85. dbt/compute/metadata/__init__.py +40 -0
  86. dbt/compute/metadata/adapters_registry.cpython-311-darwin.so +0 -0
  87. dbt/compute/metadata/adapters_registry.py +370 -0
  88. dbt/compute/metadata/registry.cpython-311-darwin.so +0 -0
  89. dbt/compute/metadata/registry.py +674 -0
  90. dbt/compute/metadata/store.cpython-311-darwin.so +0 -0
  91. dbt/compute/metadata/store.py +1499 -0
  92. dbt/compute/smart_selector.cpython-311-darwin.so +0 -0
  93. dbt/compute/smart_selector.py +377 -0
  94. dbt/compute/strategies/__init__.py +55 -0
  95. dbt/compute/strategies/base.cpython-311-darwin.so +0 -0
  96. dbt/compute/strategies/base.py +165 -0
  97. dbt/compute/strategies/dataproc.cpython-311-darwin.so +0 -0
  98. dbt/compute/strategies/dataproc.py +207 -0
  99. dbt/compute/strategies/emr.cpython-311-darwin.so +0 -0
  100. dbt/compute/strategies/emr.py +203 -0
  101. dbt/compute/strategies/local.cpython-311-darwin.so +0 -0
  102. dbt/compute/strategies/local.py +443 -0
  103. dbt/compute/strategies/standalone.cpython-311-darwin.so +0 -0
  104. dbt/compute/strategies/standalone.py +262 -0
  105. dbt/config/__init__.py +4 -0
  106. dbt/config/catalogs.py +94 -0
  107. dbt/config/compute.cpython-311-darwin.so +0 -0
  108. dbt/config/compute.py +513 -0
  109. dbt/config/dvt_profile.cpython-311-darwin.so +0 -0
  110. dbt/config/dvt_profile.py +342 -0
  111. dbt/config/profile.py +422 -0
  112. dbt/config/project.py +873 -0
  113. dbt/config/project_utils.py +28 -0
  114. dbt/config/renderer.py +231 -0
  115. dbt/config/runtime.py +553 -0
  116. dbt/config/selectors.py +208 -0
  117. dbt/config/utils.py +77 -0
  118. dbt/constants.py +28 -0
  119. dbt/context/__init__.py +0 -0
  120. dbt/context/base.py +745 -0
  121. dbt/context/configured.py +135 -0
  122. dbt/context/context_config.py +382 -0
  123. dbt/context/docs.py +82 -0
  124. dbt/context/exceptions_jinja.py +178 -0
  125. dbt/context/macro_resolver.py +195 -0
  126. dbt/context/macros.py +171 -0
  127. dbt/context/manifest.py +72 -0
  128. dbt/context/providers.py +2249 -0
  129. dbt/context/query_header.py +13 -0
  130. dbt/context/secret.py +58 -0
  131. dbt/context/target.py +74 -0
  132. dbt/contracts/__init__.py +0 -0
  133. dbt/contracts/files.py +413 -0
  134. dbt/contracts/graph/__init__.py +0 -0
  135. dbt/contracts/graph/manifest.py +1904 -0
  136. dbt/contracts/graph/metrics.py +97 -0
  137. dbt/contracts/graph/model_config.py +70 -0
  138. dbt/contracts/graph/node_args.py +42 -0
  139. dbt/contracts/graph/nodes.py +1806 -0
  140. dbt/contracts/graph/semantic_manifest.py +232 -0
  141. dbt/contracts/graph/unparsed.py +811 -0
  142. dbt/contracts/project.py +417 -0
  143. dbt/contracts/results.py +53 -0
  144. dbt/contracts/selection.py +23 -0
  145. dbt/contracts/sql.py +85 -0
  146. dbt/contracts/state.py +68 -0
  147. dbt/contracts/util.py +46 -0
  148. dbt/deprecations.py +348 -0
  149. dbt/deps/__init__.py +0 -0
  150. dbt/deps/base.py +152 -0
  151. dbt/deps/git.py +195 -0
  152. dbt/deps/local.py +79 -0
  153. dbt/deps/registry.py +130 -0
  154. dbt/deps/resolver.py +149 -0
  155. dbt/deps/tarball.py +120 -0
  156. dbt/docs/source/_ext/dbt_click.py +119 -0
  157. dbt/docs/source/conf.py +32 -0
  158. dbt/env_vars.py +64 -0
  159. dbt/event_time/event_time.py +40 -0
  160. dbt/event_time/sample_window.py +60 -0
  161. dbt/events/__init__.py +15 -0
  162. dbt/events/base_types.py +36 -0
  163. dbt/events/core_types_pb2.py +2 -0
  164. dbt/events/logging.py +108 -0
  165. dbt/events/types.py +2516 -0
  166. dbt/exceptions.py +1486 -0
  167. dbt/flags.py +89 -0
  168. dbt/graph/__init__.py +11 -0
  169. dbt/graph/cli.py +249 -0
  170. dbt/graph/graph.py +172 -0
  171. dbt/graph/queue.py +214 -0
  172. dbt/graph/selector.py +374 -0
  173. dbt/graph/selector_methods.py +975 -0
  174. dbt/graph/selector_spec.py +222 -0
  175. dbt/graph/thread_pool.py +18 -0
  176. dbt/hooks.py +21 -0
  177. dbt/include/README.md +49 -0
  178. dbt/include/__init__.py +3 -0
  179. dbt/include/data/adapters_registry.duckdb +0 -0
  180. dbt/include/data/build_registry.py +242 -0
  181. dbt/include/data/csv/adapter_queries.csv +33 -0
  182. dbt/include/data/csv/syntax_rules.csv +9 -0
  183. dbt/include/data/csv/type_mappings_bigquery.csv +28 -0
  184. dbt/include/data/csv/type_mappings_databricks.csv +30 -0
  185. dbt/include/data/csv/type_mappings_mysql.csv +40 -0
  186. dbt/include/data/csv/type_mappings_oracle.csv +30 -0
  187. dbt/include/data/csv/type_mappings_postgres.csv +56 -0
  188. dbt/include/data/csv/type_mappings_redshift.csv +33 -0
  189. dbt/include/data/csv/type_mappings_snowflake.csv +38 -0
  190. dbt/include/data/csv/type_mappings_sqlserver.csv +35 -0
  191. dbt/include/starter_project/.gitignore +4 -0
  192. dbt/include/starter_project/README.md +15 -0
  193. dbt/include/starter_project/__init__.py +3 -0
  194. dbt/include/starter_project/analyses/.gitkeep +0 -0
  195. dbt/include/starter_project/dbt_project.yml +36 -0
  196. dbt/include/starter_project/macros/.gitkeep +0 -0
  197. dbt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  198. dbt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  199. dbt/include/starter_project/models/example/schema.yml +21 -0
  200. dbt/include/starter_project/seeds/.gitkeep +0 -0
  201. dbt/include/starter_project/snapshots/.gitkeep +0 -0
  202. dbt/include/starter_project/tests/.gitkeep +0 -0
  203. dbt/internal_deprecations.py +26 -0
  204. dbt/jsonschemas/__init__.py +3 -0
  205. dbt/jsonschemas/jsonschemas.py +309 -0
  206. dbt/jsonschemas/project/0.0.110.json +4717 -0
  207. dbt/jsonschemas/project/0.0.85.json +2015 -0
  208. dbt/jsonschemas/resources/0.0.110.json +2636 -0
  209. dbt/jsonschemas/resources/0.0.85.json +2536 -0
  210. dbt/jsonschemas/resources/latest.json +6773 -0
  211. dbt/links.py +4 -0
  212. dbt/materializations/__init__.py +0 -0
  213. dbt/materializations/incremental/__init__.py +0 -0
  214. dbt/materializations/incremental/microbatch.py +236 -0
  215. dbt/mp_context.py +8 -0
  216. dbt/node_types.py +37 -0
  217. dbt/parser/__init__.py +23 -0
  218. dbt/parser/analysis.py +21 -0
  219. dbt/parser/base.py +548 -0
  220. dbt/parser/common.py +266 -0
  221. dbt/parser/docs.py +52 -0
  222. dbt/parser/fixtures.py +51 -0
  223. dbt/parser/functions.py +30 -0
  224. dbt/parser/generic_test.py +100 -0
  225. dbt/parser/generic_test_builders.py +333 -0
  226. dbt/parser/hooks.py +118 -0
  227. dbt/parser/macros.py +137 -0
  228. dbt/parser/manifest.py +2204 -0
  229. dbt/parser/models.py +573 -0
  230. dbt/parser/partial.py +1178 -0
  231. dbt/parser/read_files.py +445 -0
  232. dbt/parser/schema_generic_tests.py +422 -0
  233. dbt/parser/schema_renderer.py +111 -0
  234. dbt/parser/schema_yaml_readers.py +935 -0
  235. dbt/parser/schemas.py +1466 -0
  236. dbt/parser/search.py +149 -0
  237. dbt/parser/seeds.py +28 -0
  238. dbt/parser/singular_test.py +20 -0
  239. dbt/parser/snapshots.py +44 -0
  240. dbt/parser/sources.py +558 -0
  241. dbt/parser/sql.py +62 -0
  242. dbt/parser/unit_tests.py +621 -0
  243. dbt/plugins/__init__.py +20 -0
  244. dbt/plugins/contracts.py +9 -0
  245. dbt/plugins/exceptions.py +2 -0
  246. dbt/plugins/manager.py +163 -0
  247. dbt/plugins/manifest.py +21 -0
  248. dbt/profiler.py +20 -0
  249. dbt/py.typed +1 -0
  250. dbt/query_analyzer.cpython-311-darwin.so +0 -0
  251. dbt/query_analyzer.py +410 -0
  252. dbt/runners/__init__.py +2 -0
  253. dbt/runners/exposure_runner.py +7 -0
  254. dbt/runners/no_op_runner.py +45 -0
  255. dbt/runners/saved_query_runner.py +7 -0
  256. dbt/selected_resources.py +8 -0
  257. dbt/task/__init__.py +0 -0
  258. dbt/task/base.py +503 -0
  259. dbt/task/build.py +197 -0
  260. dbt/task/clean.py +56 -0
  261. dbt/task/clone.py +161 -0
  262. dbt/task/compile.py +150 -0
  263. dbt/task/compute.cpython-311-darwin.so +0 -0
  264. dbt/task/compute.py +458 -0
  265. dbt/task/debug.py +505 -0
  266. dbt/task/deps.py +280 -0
  267. dbt/task/docs/__init__.py +3 -0
  268. dbt/task/docs/api/__init__.py +23 -0
  269. dbt/task/docs/api/catalog.cpython-311-darwin.so +0 -0
  270. dbt/task/docs/api/catalog.py +204 -0
  271. dbt/task/docs/api/lineage.cpython-311-darwin.so +0 -0
  272. dbt/task/docs/api/lineage.py +234 -0
  273. dbt/task/docs/api/profile.cpython-311-darwin.so +0 -0
  274. dbt/task/docs/api/profile.py +204 -0
  275. dbt/task/docs/api/spark.cpython-311-darwin.so +0 -0
  276. dbt/task/docs/api/spark.py +186 -0
  277. dbt/task/docs/generate.py +947 -0
  278. dbt/task/docs/index.html +250 -0
  279. dbt/task/docs/serve.cpython-311-darwin.so +0 -0
  280. dbt/task/docs/serve.py +174 -0
  281. dbt/task/dvt_output.py +362 -0
  282. dbt/task/dvt_run.py +204 -0
  283. dbt/task/freshness.py +322 -0
  284. dbt/task/function.py +121 -0
  285. dbt/task/group_lookup.py +46 -0
  286. dbt/task/init.cpython-311-darwin.so +0 -0
  287. dbt/task/init.py +604 -0
  288. dbt/task/java.cpython-311-darwin.so +0 -0
  289. dbt/task/java.py +316 -0
  290. dbt/task/list.py +236 -0
  291. dbt/task/metadata.cpython-311-darwin.so +0 -0
  292. dbt/task/metadata.py +804 -0
  293. dbt/task/printer.py +175 -0
  294. dbt/task/profile.cpython-311-darwin.so +0 -0
  295. dbt/task/profile.py +1307 -0
  296. dbt/task/profile_serve.py +615 -0
  297. dbt/task/retract.py +438 -0
  298. dbt/task/retry.py +175 -0
  299. dbt/task/run.py +1387 -0
  300. dbt/task/run_operation.py +141 -0
  301. dbt/task/runnable.py +758 -0
  302. dbt/task/seed.py +103 -0
  303. dbt/task/show.py +149 -0
  304. dbt/task/snapshot.py +56 -0
  305. dbt/task/spark.cpython-311-darwin.so +0 -0
  306. dbt/task/spark.py +414 -0
  307. dbt/task/sql.py +110 -0
  308. dbt/task/target_sync.cpython-311-darwin.so +0 -0
  309. dbt/task/target_sync.py +766 -0
  310. dbt/task/test.py +464 -0
  311. dbt/tests/fixtures/__init__.py +1 -0
  312. dbt/tests/fixtures/project.py +620 -0
  313. dbt/tests/util.py +651 -0
  314. dbt/tracking.py +529 -0
  315. dbt/utils/__init__.py +3 -0
  316. dbt/utils/artifact_upload.py +151 -0
  317. dbt/utils/utils.py +408 -0
  318. dbt/version.py +270 -0
  319. dvt_cli/__init__.py +72 -0
  320. dvt_core-0.58.6.dist-info/METADATA +288 -0
  321. dvt_core-0.58.6.dist-info/RECORD +324 -0
  322. dvt_core-0.58.6.dist-info/WHEEL +5 -0
  323. dvt_core-0.58.6.dist-info/entry_points.txt +2 -0
  324. dvt_core-0.58.6.dist-info/top_level.txt +2 -0
dbt/task/compute.py ADDED
@@ -0,0 +1,458 @@
1
+ """
2
+ Compute Task
3
+
4
+ Handles DVT compute engine management commands:
5
+ - test: List all compute engines with connection status
6
+ - edit: Open computes.yml in user's editor
7
+ - validate: Validate compute engine configurations
8
+
9
+ v0.5.97: Simplified CLI - removed register/remove (use dvt compute edit instead)
10
+ computes.yml with comprehensive samples replaces interactive registration.
11
+ """
12
+
13
+ import os
14
+ import subprocess
15
+ import sys
16
+ from pathlib import Path
17
+ from typing import Optional
18
+
19
+ from dbt.config.compute import ComputeRegistry, SparkPlatform, DEFAULT_COMPUTES_YAML
20
+ from dbt_common.exceptions import DbtRuntimeError
21
+
22
+
23
+ class ComputeTask:
24
+ """Task for managing DVT compute engines."""
25
+
26
+ def __init__(self, project_dir=None):
27
+ """
28
+ Initialize ComputeTask.
29
+
30
+ :param project_dir: Path to project root directory (str or Path, defaults to cwd)
31
+ """
32
+ # Convert Path to string for consistent handling
33
+ if project_dir is not None:
34
+ self.project_dir = str(project_dir)
35
+ else:
36
+ self.project_dir = str(Path.cwd())
37
+ self.registry = ComputeRegistry(self.project_dir)
38
+
39
+ def list_computes(self) -> bool:
40
+ """
41
+ List all compute engines with their names and basic info.
42
+
43
+ v0.51.1: Simple list command for quick reference.
44
+
45
+ :returns: True always (for CLI exit code)
46
+ """
47
+ clusters = self.registry.list()
48
+
49
+ if not clusters:
50
+ print("No compute engines configured.")
51
+ print("\nRun 'dvt compute edit' to configure compute engines.")
52
+ return True
53
+
54
+ print(f"\nCompute Engines ({len(clusters)} configured)")
55
+ print("-" * 40)
56
+
57
+ for cluster in clusters:
58
+ default_marker = " (default)" if cluster.name == self.registry.target_compute else ""
59
+ platform = cluster.detect_platform()
60
+ print(f" {cluster.name}{default_marker}")
61
+ print(f" Platform: {platform.value}")
62
+ if cluster.description:
63
+ print(f" Description: {cluster.description}")
64
+
65
+ print("")
66
+ print(f"Default: {self.registry.target_compute}")
67
+ print(f"Config: {self.registry.get_config_path()}")
68
+
69
+ return True
70
+
71
+ def test_single_compute(self, compute_name: str) -> bool:
72
+ """
73
+ Test a specific compute engine by name.
74
+
75
+ v0.5.99: Added single compute testing via `dvt compute test <name>`.
76
+
77
+ :param compute_name: Name of the compute engine to test
78
+ :returns: True if test passes, False otherwise
79
+ """
80
+ clusters = self.registry.list()
81
+ cluster = None
82
+
83
+ # Find the cluster by name
84
+ for c in clusters:
85
+ if c.name == compute_name:
86
+ cluster = c
87
+ break
88
+
89
+ if cluster is None:
90
+ print(f"❌ Compute engine '{compute_name}' not found.")
91
+ print(f"\nAvailable compute engines:")
92
+ for c in clusters:
93
+ default_marker = " (default)" if c.name == self.registry.target_compute else ""
94
+ print(f" - {c.name}{default_marker}")
95
+ print(f"\nRun 'dvt compute edit' to configure compute engines.")
96
+ return False
97
+
98
+ # Test the specific cluster
99
+ print(f"\n" + "=" * 70)
100
+ print(f"Testing Compute Engine: {compute_name}")
101
+ print("=" * 70)
102
+
103
+ default_marker = " (default)" if cluster.name == self.registry.target_compute else ""
104
+ platform = cluster.detect_platform()
105
+
106
+ print(f"\n{cluster.name}{default_marker}")
107
+ print(f" Type: {cluster.type}")
108
+ print(f" Platform: {platform.value}")
109
+ if cluster.description:
110
+ print(f" Description: {cluster.description}")
111
+
112
+ # Test the cluster
113
+ status, message = self._test_single_cluster(cluster)
114
+
115
+ if status == "ok":
116
+ print(f" Status: ✅ {message}")
117
+ elif status == "warning":
118
+ print(f" Status: ⚠️ {message}")
119
+ elif status == "error":
120
+ print(f" Status: ❌ {message}")
121
+
122
+ print("\n" + "-" * 70)
123
+
124
+ return status in ("ok", "warning")
125
+
126
+ def _test_single_cluster(self, cluster) -> tuple:
127
+ """
128
+ Test a single cluster and return status.
129
+
130
+ v0.5.98: Full connectivity testing with three stages:
131
+ 1. Config validation
132
+ 2. Session creation + SELECT 1 test
133
+ 3. (Optional) JDBC read test
134
+
135
+ v0.51.1: Enhanced session isolation - forcefully stop ALL Spark sessions
136
+ before and after each test to prevent config bleed between computes.
137
+
138
+ :param cluster: ComputeCluster to test
139
+ :returns: Tuple of (status, message) where status is 'ok', 'warning', or 'error'
140
+ """
141
+ platform = cluster.detect_platform()
142
+
143
+ if cluster.type == "spark":
144
+ # Stage 1: Config validation
145
+ config_result = self._validate_cluster_config(cluster, platform)
146
+ if config_result[0] == "error":
147
+ return config_result
148
+
149
+ # Stage 2: Full connectivity test (session + SQL)
150
+ try:
151
+ # v0.51.1: Force stop ALL Spark sessions before testing
152
+ # This ensures each compute engine test gets a completely fresh JVM context
153
+ self._force_stop_all_spark_sessions()
154
+
155
+ strategy = self._get_strategy_for_cluster(cluster, platform)
156
+ if strategy is None:
157
+ return config_result # Return config validation result
158
+
159
+ success, message = strategy.test_connectivity()
160
+
161
+ # v0.51.1: Force stop ALL sessions after test to not interfere with next compute
162
+ self._force_stop_all_spark_sessions()
163
+
164
+ if success:
165
+ return ("ok", message)
166
+ else:
167
+ return ("error", message)
168
+
169
+ except ImportError as e:
170
+ # Missing dependency (PySpark, databricks-connect, etc.)
171
+ return ("warning", str(e))
172
+ except AttributeError as e:
173
+ # databricks-connect may have compatibility issues with pyspark
174
+ if "Hook" in str(e) or "SparkSession" in str(e):
175
+ return ("warning", f"databricks-connect/pyspark version conflict: {str(e)[:50]}")
176
+ return ("error", f"Connectivity test failed: {str(e)}")
177
+ except Exception as e:
178
+ return ("error", f"Connectivity test failed: {str(e)}")
179
+
180
+ return ("ok", "Configuration valid")
181
+
182
+ def _force_stop_all_spark_sessions(self) -> None:
183
+ """
184
+ Force stop ALL Spark sessions to ensure complete isolation.
185
+
186
+ v0.51.1: This is critical for compute testing because:
187
+ 1. Different computes have different spark.jars.packages configs
188
+ 2. Spark's getOrCreate() returns existing session without re-applying config
189
+ 3. We need a fresh JVM context for each compute's JDBC drivers
190
+
191
+ This method:
192
+ 1. Stops active session
193
+ 2. Clears the local session cache used by LocalStrategy
194
+ 3. Forces garbage collection to release JVM resources
195
+ """
196
+ try:
197
+ from pyspark.sql import SparkSession
198
+
199
+ # Stop active session
200
+ active = SparkSession.getActiveSession()
201
+ if active:
202
+ active.stop()
203
+
204
+ # Clear local strategy cache
205
+ try:
206
+ from dbt.compute.strategies.local import _SPARK_SESSION_CACHE
207
+ _SPARK_SESSION_CACHE.clear()
208
+ except (ImportError, AttributeError):
209
+ pass
210
+
211
+ # Give JVM time to release resources
212
+ import time
213
+ time.sleep(0.5)
214
+
215
+ except ImportError:
216
+ pass # PySpark not installed
217
+ except Exception:
218
+ pass # Best effort cleanup
219
+
220
+ def _validate_cluster_config(self, cluster, platform: SparkPlatform) -> tuple:
221
+ """
222
+ Validate cluster configuration (Stage 1).
223
+
224
+ :param cluster: ComputeCluster to validate
225
+ :param platform: Detected SparkPlatform
226
+ :returns: Tuple of (status, message)
227
+ """
228
+ if platform == SparkPlatform.LOCAL:
229
+ try:
230
+ import pyspark # noqa: F401
231
+ # PySpark 4.0+ doesn't have __version__ attribute, use importlib
232
+ try:
233
+ from importlib.metadata import version
234
+ pyspark_version = version("pyspark")
235
+ except Exception:
236
+ pyspark_version = "unknown"
237
+ return ("ok", f"PySpark {pyspark_version} available")
238
+ except ImportError:
239
+ return ("error", "PySpark not installed")
240
+
241
+ elif platform == SparkPlatform.EMR:
242
+ required = ["master"]
243
+ missing = [k for k in required if k not in cluster.config]
244
+ if missing:
245
+ return ("error", f"Missing config: {', '.join(missing)}")
246
+ master = cluster.config.get("master", "")
247
+ if master.lower() != "yarn":
248
+ return ("error", f"EMR requires master='yarn', got: {master}")
249
+ return ("ok", "EMR config valid")
250
+
251
+ elif platform == SparkPlatform.DATAPROC:
252
+ required = ["project", "region", "cluster"]
253
+ missing = [k for k in required if k not in cluster.config]
254
+ if missing:
255
+ return ("error", f"Missing config: {', '.join(missing)}")
256
+ return ("ok", "Dataproc config valid")
257
+
258
+ elif platform == SparkPlatform.STANDALONE:
259
+ master = cluster.config.get("master", "")
260
+ if not master.startswith("spark://"):
261
+ return ("error", f"Standalone requires master='spark://...', got: {master}")
262
+ return ("ok", f"Standalone config valid ({master})")
263
+
264
+ else:
265
+ # External/generic
266
+ if "master" in cluster.config:
267
+ return ("ok", f"External cluster at {cluster.config['master']}")
268
+ return ("ok", "Configuration valid")
269
+
270
+ def _get_strategy_for_cluster(self, cluster, platform: SparkPlatform):
271
+ """
272
+ Get the connection strategy for a cluster.
273
+
274
+ :param cluster: ComputeCluster
275
+ :param platform: Detected SparkPlatform
276
+ :returns: BaseConnectionStrategy instance or None
277
+ """
278
+ try:
279
+ if platform == SparkPlatform.LOCAL:
280
+ from dbt.compute.strategies.local import LocalStrategy
281
+ return LocalStrategy(cluster.config, app_name=f"DVT-{cluster.name}")
282
+
283
+ elif platform == SparkPlatform.EMR:
284
+ from dbt.compute.strategies import get_emr_strategy
285
+ EMRStrategy = get_emr_strategy()
286
+ return EMRStrategy(cluster.config, app_name=f"DVT-{cluster.name}")
287
+
288
+ elif platform == SparkPlatform.DATAPROC:
289
+ from dbt.compute.strategies import get_dataproc_strategy
290
+ DataprocStrategy = get_dataproc_strategy()
291
+ return DataprocStrategy(cluster.config, app_name=f"DVT-{cluster.name}")
292
+
293
+ elif platform == SparkPlatform.STANDALONE:
294
+ from dbt.compute.strategies import get_standalone_strategy
295
+ StandaloneStrategy = get_standalone_strategy()
296
+ return StandaloneStrategy(cluster.config, app_name=f"DVT-{cluster.name}")
297
+
298
+ else:
299
+ # External - no specific strategy, skip connectivity test
300
+ return None
301
+
302
+ except ImportError as e:
303
+ raise ImportError(f"Missing dependency for {platform.value}: {str(e)}")
304
+
305
+ def edit_config(self) -> bool:
306
+ """
307
+ Open computes.yml in user's preferred editor.
308
+
309
+ Uses EDITOR environment variable, falls back to common editors.
310
+
311
+ :returns: True if editor launched successfully
312
+ """
313
+ # Ensure config exists with full template
314
+ config_path = self.registry.ensure_config_exists()
315
+
316
+ # If file doesn't have full samples, write the template
317
+ with open(config_path, "r") as f:
318
+ content = f.read()
319
+ if "DATABRICKS" not in content:
320
+ # Write full template to get all the samples
321
+ with open(config_path, "w") as f:
322
+ f.write(DEFAULT_COMPUTES_YAML)
323
+
324
+ print(f"Opening: {config_path}")
325
+ print("")
326
+ print("After editing, run 'dvt compute validate' to check syntax.")
327
+ print("")
328
+
329
+ # Get editor from environment or use defaults
330
+ editor = os.environ.get("EDITOR")
331
+ if not editor:
332
+ editor = os.environ.get("VISUAL")
333
+ if not editor:
334
+ # Try common editors
335
+ for ed in ["code", "nano", "vim", "vi", "notepad"]:
336
+ try:
337
+ subprocess.run(["which", ed], capture_output=True, check=True)
338
+ editor = ed
339
+ break
340
+ except (subprocess.CalledProcessError, FileNotFoundError):
341
+ continue
342
+
343
+ if not editor:
344
+ print(f"No editor found. Please open manually: {config_path}")
345
+ return False
346
+
347
+ try:
348
+ # Handle VS Code specially (--wait flag)
349
+ if editor in ("code", "code-insiders"):
350
+ subprocess.run([editor, "--wait", str(config_path)])
351
+ else:
352
+ subprocess.run([editor, str(config_path)])
353
+
354
+ # Reload and validate after edit
355
+ print("\nValidating changes...")
356
+ return self.validate_config()
357
+
358
+ except Exception as e:
359
+ print(f"Error opening editor: {e}", file=sys.stderr)
360
+ print(f"Please open manually: {config_path}")
361
+ return False
362
+
363
+ def validate_config(self) -> bool:
364
+ """
365
+ Validate computes.yml syntax and configuration.
366
+
367
+ :returns: True if configuration is valid
368
+ """
369
+ config_path = self.registry.get_config_path()
370
+
371
+ if not config_path.exists():
372
+ print(f"✗ Config file not found: {config_path}")
373
+ print("\nRun 'dvt compute edit' to create one.")
374
+ return False
375
+
376
+ print(f"Validating: {config_path}")
377
+ print("")
378
+
379
+ try:
380
+ # Try to load the YAML
381
+ import yaml
382
+ with open(config_path, "r") as f:
383
+ data = yaml.safe_load(f)
384
+
385
+ if not data:
386
+ print("✗ Config file is empty")
387
+ return False
388
+
389
+ errors = []
390
+ warnings = []
391
+
392
+ # Check target_compute
393
+ target = data.get("target_compute")
394
+ if not target:
395
+ errors.append("Missing 'target_compute' field")
396
+
397
+ # Check computes section
398
+ computes = data.get("computes", {})
399
+ if not computes:
400
+ errors.append("No compute engines defined in 'computes' section")
401
+ else:
402
+ # Validate each compute
403
+ for name, config in computes.items():
404
+ if config is None:
405
+ continue # Skip commented-out entries
406
+
407
+ if not isinstance(config, dict):
408
+ errors.append(f"Compute '{name}': invalid configuration (expected dict)")
409
+ continue
410
+
411
+ # Check type
412
+ compute_type = config.get("type")
413
+ if not compute_type:
414
+ errors.append(f"Compute '{name}': missing 'type' field")
415
+ elif compute_type not in ("spark",):
416
+ warnings.append(f"Compute '{name}': unknown type '{compute_type}' (only 'spark' supported)")
417
+
418
+ # Check config section
419
+ if "config" not in config:
420
+ warnings.append(f"Compute '{name}': no 'config' section (will use defaults)")
421
+
422
+ # Check target_compute references valid engine
423
+ if target and target not in computes:
424
+ errors.append(f"target_compute '{target}' not found in computes section")
425
+
426
+ # Print results
427
+ if errors:
428
+ print("Errors:")
429
+ for err in errors:
430
+ print(f" ✗ {err}")
431
+ print("")
432
+
433
+ if warnings:
434
+ print("Warnings:")
435
+ for warn in warnings:
436
+ print(f" ⚠ {warn}")
437
+ print("")
438
+
439
+ if not errors and not warnings:
440
+ print("✓ Configuration is valid")
441
+ print(f" Target compute: {target}")
442
+ print(f" Engines defined: {len([c for c in computes.values() if c])}")
443
+ return True
444
+
445
+ if not errors:
446
+ print("✓ Configuration is valid (with warnings)")
447
+ return True
448
+
449
+ return False
450
+
451
+ except yaml.YAMLError as e:
452
+ print(f"✗ YAML syntax error:")
453
+ print(f" {e}")
454
+ return False
455
+
456
+ except Exception as e:
457
+ print(f"✗ Validation failed: {e}")
458
+ return False