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/cli/main.py ADDED
@@ -0,0 +1,2403 @@
1
+ import functools
2
+ from concurrent.futures import ThreadPoolExecutor, TimeoutError as FuturesTimeoutError
3
+ from copy import copy
4
+ from dataclasses import dataclass
5
+ import re
6
+ import time
7
+ from typing import Callable, List, Optional, Union
8
+
9
+ import click
10
+ from click.exceptions import BadOptionUsage
11
+ from click.exceptions import Exit as ClickExit
12
+ from click.exceptions import NoSuchOption, UsageError
13
+
14
+ from dbt.adapters.factory import register_adapter
15
+ from dbt.artifacts.schemas.catalog import CatalogArtifact
16
+ from dbt.artifacts.schemas.run import RunExecutionResult
17
+ from dbt.cli import params as p
18
+ from dbt.cli import requires
19
+ from dbt.cli.exceptions import DbtInternalException, DbtUsageException
20
+ from dbt.cli.requires import setup_manifest
21
+ from dbt.contracts.graph.manifest import Manifest
22
+ from dbt.mp_context import get_mp_context
23
+ from dbt_common.events.base_types import EventMsg
24
+
25
+
26
+ @dataclass
27
+ class dbtRunnerResult:
28
+ """Contains the result of an invocation of the dbtRunner"""
29
+
30
+ success: bool
31
+
32
+ exception: Optional[BaseException] = None
33
+ result: Union[
34
+ bool, # debug
35
+ CatalogArtifact, # docs generate
36
+ List[str], # list/ls
37
+ Manifest, # parse
38
+ None, # clean, deps, init, source
39
+ RunExecutionResult, # build, compile, run, seed, snapshot, test, run-operation
40
+ ] = None
41
+
42
+
43
+ # Programmatic invocation
44
+ class dbtRunner:
45
+ def __init__(
46
+ self,
47
+ manifest: Optional[Manifest] = None,
48
+ callbacks: Optional[List[Callable[[EventMsg], None]]] = None,
49
+ ) -> None:
50
+ self.manifest = manifest
51
+
52
+ if callbacks is None:
53
+ callbacks = []
54
+ self.callbacks = callbacks
55
+
56
+ def invoke(self, args: List[str], **kwargs) -> dbtRunnerResult:
57
+ try:
58
+ dbt_ctx = cli.make_context(cli.name, args.copy())
59
+ dbt_ctx.obj = {
60
+ "manifest": self.manifest,
61
+ "callbacks": self.callbacks,
62
+ "dbt_runner_command_args": args,
63
+ }
64
+
65
+ for key, value in kwargs.items():
66
+ dbt_ctx.params[key] = value
67
+ # Hack to set parameter source to custom string
68
+ dbt_ctx.set_parameter_source(key, "kwargs") # type: ignore
69
+
70
+ result, success = cli.invoke(dbt_ctx)
71
+ return dbtRunnerResult(
72
+ result=result,
73
+ success=success,
74
+ )
75
+ except requires.ResultExit as e:
76
+ return dbtRunnerResult(
77
+ result=e.result,
78
+ success=False,
79
+ )
80
+ except requires.ExceptionExit as e:
81
+ return dbtRunnerResult(
82
+ exception=e.exception,
83
+ success=False,
84
+ )
85
+ except (BadOptionUsage, NoSuchOption, UsageError) as e:
86
+ return dbtRunnerResult(
87
+ exception=DbtUsageException(e.message),
88
+ success=False,
89
+ )
90
+ except ClickExit as e:
91
+ if e.exit_code == 0:
92
+ return dbtRunnerResult(success=True)
93
+ return dbtRunnerResult(
94
+ exception=DbtInternalException(f"unhandled exit code {e.exit_code}"),
95
+ success=False,
96
+ )
97
+ except BaseException as e:
98
+ return dbtRunnerResult(
99
+ exception=e,
100
+ success=False,
101
+ )
102
+
103
+
104
+ # approach from https://github.com/pallets/click/issues/108#issuecomment-280489786
105
+ def global_flags(func):
106
+ @p.cache_selected_only
107
+ @p.debug
108
+ @p.defer
109
+ @p.deprecated_defer
110
+ @p.defer_state
111
+ @p.deprecated_favor_state
112
+ @p.deprecated_print
113
+ @p.deprecated_state
114
+ @p.fail_fast
115
+ @p.favor_state
116
+ @p.indirect_selection
117
+ @p.log_cache_events
118
+ @p.log_file_max_bytes
119
+ @p.log_format
120
+ @p.log_format_file
121
+ @p.log_level
122
+ @p.log_level_file
123
+ @p.log_path
124
+ @p.macro_debugging
125
+ @p.partial_parse
126
+ @p.partial_parse_file_path
127
+ @p.partial_parse_file_diff
128
+ @p.populate_cache
129
+ @p.print
130
+ @p.printer_width
131
+ @p.profile
132
+ @p.quiet
133
+ @p.record_timing_info
134
+ @p.send_anonymous_usage_stats
135
+ @p.single_threaded
136
+ @p.show_all_deprecations
137
+ @p.state
138
+ @p.static_parser
139
+ @p.target
140
+ @p.target_compute
141
+ @p.use_colors
142
+ @p.use_colors_file
143
+ @p.use_experimental_parser
144
+ @p.version
145
+ @p.version_check
146
+ @p.warn_error
147
+ @p.warn_error_options
148
+ @p.write_json
149
+ @p.use_fast_test_edges
150
+ @p.upload_artifacts
151
+ @functools.wraps(func)
152
+ def wrapper(*args, **kwargs):
153
+ return func(*args, **kwargs)
154
+
155
+ return wrapper
156
+
157
+
158
+ # dbt
159
+ @click.group(
160
+ context_settings={"help_option_names": ["-h", "--help"]},
161
+ invoke_without_command=True,
162
+ no_args_is_help=True,
163
+ epilog="Specify one of these sub-commands and you can find more help from there.",
164
+ )
165
+ @click.pass_context
166
+ @global_flags
167
+ @p.show_resource_report
168
+ def cli(ctx, **kwargs):
169
+ """An ELT tool for managing your SQL transformations and data models.
170
+ For more documentation on these commands, visit: docs.getdbt.com
171
+ """
172
+
173
+
174
+ # dbt build
175
+ @cli.command("build")
176
+ @click.pass_context
177
+ @global_flags
178
+ @p.empty
179
+ @p.event_time_start
180
+ @p.event_time_end
181
+ @p.exclude
182
+ @p.export_saved_queries
183
+ @p.full_refresh
184
+ @p.deprecated_include_saved_query
185
+ @p.profiles_dir
186
+ @p.project_dir
187
+ @p.resource_type
188
+ @p.exclude_resource_type
189
+ @p.sample
190
+ @p.select
191
+ @p.selector
192
+ @p.show
193
+ @p.store_failures
194
+ @p.target_path
195
+ @p.threads
196
+ @p.vars
197
+ @requires.postflight
198
+ @requires.preflight
199
+ @requires.profile
200
+ @requires.project
201
+ @requires.catalogs
202
+ @requires.runtime_config
203
+ @requires.manifest
204
+ def build(ctx, **kwargs):
205
+ """Run all seeds, models, snapshots, and tests in DAG order"""
206
+ from dbt.task.build import BuildTask
207
+
208
+ task = BuildTask(
209
+ ctx.obj["flags"],
210
+ ctx.obj["runtime_config"],
211
+ ctx.obj["manifest"],
212
+ )
213
+
214
+ results = task.run()
215
+ success = task.interpret_results(results)
216
+ return results, success
217
+
218
+
219
+ # dbt clean
220
+ @cli.command("clean")
221
+ @click.pass_context
222
+ @global_flags
223
+ @p.clean_project_files_only
224
+ @p.profiles_dir
225
+ @p.project_dir
226
+ @p.target_path
227
+ @p.vars
228
+ @requires.postflight
229
+ @requires.preflight
230
+ @requires.unset_profile
231
+ @requires.project
232
+ def clean(ctx, **kwargs):
233
+ """Delete all folders in the clean-targets list (usually the dbt_packages and target directories.)"""
234
+ from dbt.task.clean import CleanTask
235
+
236
+ with CleanTask(ctx.obj["flags"], ctx.obj["project"]) as task:
237
+ results = task.run()
238
+ success = task.interpret_results(results)
239
+ return results, success
240
+
241
+
242
+ # dbt docs
243
+ @cli.group()
244
+ @click.pass_context
245
+ @global_flags
246
+ def docs(ctx, **kwargs):
247
+ """Generate or serve the documentation website for your project"""
248
+
249
+
250
+ # dbt docs generate
251
+ @docs.command("generate")
252
+ @click.pass_context
253
+ @global_flags
254
+ @p.compile_docs
255
+ @p.exclude
256
+ @p.profiles_dir
257
+ @p.project_dir
258
+ @p.select
259
+ @p.selector
260
+ @p.empty_catalog
261
+ @p.static
262
+ @p.target_path
263
+ @p.threads
264
+ @p.vars
265
+ @requires.postflight
266
+ @requires.preflight
267
+ @requires.profile
268
+ @requires.project
269
+ @requires.runtime_config
270
+ @requires.manifest(write=False)
271
+ def docs_generate(ctx, **kwargs):
272
+ """Generate the documentation website for your project"""
273
+ from dbt.task.docs.generate import GenerateTask
274
+
275
+ task = GenerateTask(
276
+ ctx.obj["flags"],
277
+ ctx.obj["runtime_config"],
278
+ ctx.obj["manifest"],
279
+ )
280
+
281
+ results = task.run()
282
+ success = task.interpret_results(results)
283
+ return results, success
284
+
285
+
286
+ # dbt docs serve
287
+ @docs.command("serve")
288
+ @click.pass_context
289
+ @global_flags
290
+ @p.browser
291
+ @p.host
292
+ @p.port
293
+ @p.profiles_dir
294
+ @p.project_dir
295
+ @p.target_path
296
+ @p.vars
297
+ @requires.postflight
298
+ @requires.preflight
299
+ @requires.profile
300
+ @requires.project
301
+ @requires.runtime_config
302
+ def docs_serve(ctx, **kwargs):
303
+ """Serve the documentation website for your project"""
304
+ from dbt.task.docs.serve import ServeTask
305
+
306
+ task = ServeTask(
307
+ ctx.obj["flags"],
308
+ ctx.obj["runtime_config"],
309
+ )
310
+
311
+ results = task.run()
312
+ success = task.interpret_results(results)
313
+ return results, success
314
+
315
+
316
+ # dbt compile
317
+ @cli.command("compile")
318
+ @click.pass_context
319
+ @global_flags
320
+ @p.exclude
321
+ @p.full_refresh
322
+ @p.show_output_format
323
+ @p.introspect
324
+ @p.profiles_dir
325
+ @p.project_dir
326
+ @p.empty
327
+ @p.select
328
+ @p.selector
329
+ @p.inline
330
+ @p.compile_inject_ephemeral_ctes
331
+ @p.target_path
332
+ @p.threads
333
+ @p.vars
334
+ @requires.postflight
335
+ @requires.preflight
336
+ @requires.profile
337
+ @requires.project
338
+ @requires.runtime_config
339
+ @requires.manifest
340
+ def compile(ctx, **kwargs):
341
+ """Generates executable SQL from source, model, test, and analysis files. Compiled SQL files are written to the
342
+ target/ directory."""
343
+ from dbt.task.compile import CompileTask
344
+
345
+ task = CompileTask(
346
+ ctx.obj["flags"],
347
+ ctx.obj["runtime_config"],
348
+ ctx.obj["manifest"],
349
+ )
350
+
351
+ results = task.run()
352
+ success = task.interpret_results(results)
353
+ return results, success
354
+
355
+
356
+ # dbt show
357
+ @cli.command("show")
358
+ @click.pass_context
359
+ @global_flags
360
+ @p.exclude
361
+ @p.full_refresh
362
+ @p.show_output_format
363
+ @p.show_limit
364
+ @p.introspect
365
+ @p.profiles_dir
366
+ @p.project_dir
367
+ @p.select
368
+ @p.selector
369
+ @p.inline
370
+ @p.inline_direct
371
+ @p.target_path
372
+ @p.threads
373
+ @p.vars
374
+ @requires.postflight
375
+ @requires.preflight
376
+ @requires.profile
377
+ @requires.project
378
+ @requires.runtime_config
379
+ def show(ctx, **kwargs):
380
+ """Generates executable SQL for a named resource or inline query, runs that SQL, and returns a preview of the
381
+ results. Does not materialize anything to the warehouse."""
382
+ from dbt.task.show import ShowTask, ShowTaskDirect
383
+
384
+ if ctx.obj["flags"].inline_direct:
385
+ # Issue the inline query directly, with no templating. Does not require
386
+ # loading the manifest.
387
+ register_adapter(ctx.obj["runtime_config"], get_mp_context())
388
+ task = ShowTaskDirect(
389
+ ctx.obj["flags"],
390
+ ctx.obj["runtime_config"],
391
+ )
392
+ else:
393
+ setup_manifest(ctx)
394
+ task = ShowTask(
395
+ ctx.obj["flags"],
396
+ ctx.obj["runtime_config"],
397
+ ctx.obj["manifest"],
398
+ )
399
+
400
+ results = task.run()
401
+ success = task.interpret_results(results)
402
+ return results, success
403
+
404
+
405
+ # dbt debug
406
+ @cli.command("debug")
407
+ @click.pass_context
408
+ @global_flags
409
+ @p.debug_connection
410
+ @p.config_dir
411
+ @p.profiles_dir_exists_false
412
+ @p.project_dir
413
+ @p.vars
414
+ @requires.postflight
415
+ @requires.preflight
416
+ def debug(ctx, **kwargs):
417
+ """Show information on the current dbt environment and check dependencies, then test the database connection. Not to be confused with the --debug option which increases verbosity."""
418
+ from dbt.task.debug import DebugTask
419
+
420
+ task = DebugTask(
421
+ ctx.obj["flags"],
422
+ )
423
+
424
+ results = task.run()
425
+ success = task.interpret_results(results)
426
+ return results, success
427
+
428
+
429
+ # dbt deps
430
+ @cli.command("deps")
431
+ @click.pass_context
432
+ @global_flags
433
+ @p.profiles_dir_exists_false
434
+ @p.project_dir
435
+ @p.vars
436
+ @p.source
437
+ @p.lock
438
+ @p.upgrade
439
+ @p.add_package
440
+ @requires.postflight
441
+ @requires.preflight
442
+ @requires.unset_profile
443
+ @requires.project
444
+ def deps(ctx, **kwargs):
445
+ """Install dbt packages specified.
446
+ In the following case, a new `package-lock.yml` will be generated and the packages are installed:
447
+ - user updated the packages.yml
448
+ - user specify the flag --update, which means for packages that are specified as a
449
+ range, dbt-core will try to install the newer version
450
+ Otherwise, deps will use `package-lock.yml` as source of truth to install packages.
451
+
452
+ There is a way to add new packages by providing an `--add-package` flag to deps command
453
+ which will allow user to specify a package they want to add in the format of packagename@version.
454
+ """
455
+ from dbt.task.deps import DepsTask
456
+
457
+ flags = ctx.obj["flags"]
458
+ if flags.ADD_PACKAGE:
459
+ if not flags.ADD_PACKAGE["version"] and flags.SOURCE != "local":
460
+ raise BadOptionUsage(
461
+ message=f"Version is required in --add-package when a package when source is {flags.SOURCE}",
462
+ option_name="--add-package",
463
+ )
464
+ with DepsTask(flags, ctx.obj["project"]) as task:
465
+ results = task.run()
466
+ success = task.interpret_results(results)
467
+ return results, success
468
+
469
+
470
+ # dbt init
471
+ @cli.command("init")
472
+ @click.pass_context
473
+ @global_flags
474
+ # for backwards compatibility, accept 'project_name' as an optional positional argument
475
+ @click.argument("project_name", required=False)
476
+ @p.profiles_dir_exists_false
477
+ @p.project_dir
478
+ @p.skip_profile_setup
479
+ @p.vars
480
+ @requires.postflight
481
+ @requires.preflight
482
+ def init(ctx, **kwargs):
483
+ """Initialize a new dbt project."""
484
+ from dbt.task.init import InitTask
485
+
486
+ with InitTask(ctx.obj["flags"]) as task:
487
+ results = task.run()
488
+ success = task.interpret_results(results)
489
+ return results, success
490
+
491
+
492
+ # dbt list
493
+ @cli.command("list")
494
+ @click.pass_context
495
+ @global_flags
496
+ @p.exclude
497
+ @p.models
498
+ @p.output
499
+ @p.output_keys
500
+ @p.profiles_dir
501
+ @p.project_dir
502
+ @p.resource_type
503
+ @p.exclude_resource_type
504
+ @p.raw_select
505
+ @p.selector
506
+ @p.target_path
507
+ @p.vars
508
+ @requires.postflight
509
+ @requires.preflight
510
+ @requires.profile
511
+ @requires.project
512
+ @requires.runtime_config
513
+ @requires.manifest
514
+ def list(ctx, **kwargs):
515
+ """List the resources in your project"""
516
+ from dbt.task.list import ListTask
517
+
518
+ task = ListTask(
519
+ ctx.obj["flags"],
520
+ ctx.obj["runtime_config"],
521
+ ctx.obj["manifest"],
522
+ )
523
+
524
+ results = task.run()
525
+ success = task.interpret_results(results)
526
+ return results, success
527
+
528
+
529
+ # Alias "list" to "ls"
530
+ ls = copy(cli.commands["list"])
531
+ ls.hidden = True
532
+ cli.add_command(ls, "ls")
533
+
534
+
535
+ # dbt parse
536
+ @cli.command("parse")
537
+ @click.pass_context
538
+ @global_flags
539
+ @p.profiles_dir
540
+ @p.project_dir
541
+ @p.target_path
542
+ @p.threads
543
+ @p.vars
544
+ @requires.postflight
545
+ @requires.preflight
546
+ @requires.profile
547
+ @requires.project
548
+ @requires.catalogs
549
+ @requires.runtime_config
550
+ @requires.manifest(write_perf_info=True)
551
+ def parse(ctx, **kwargs):
552
+ """Parses the project and provides information on performance"""
553
+ # manifest generation and writing happens in @requires.manifest
554
+ return ctx.obj["manifest"], True
555
+
556
+
557
+ # dbt run
558
+ @cli.command("run")
559
+ @click.pass_context
560
+ @global_flags
561
+ @p.exclude
562
+ @p.full_refresh
563
+ @p.profiles_dir
564
+ @p.project_dir
565
+ @p.empty
566
+ @p.event_time_start
567
+ @p.event_time_end
568
+ @p.sample
569
+ @p.select
570
+ @p.selector
571
+ @p.target_path
572
+ @p.threads
573
+ @p.vars
574
+ @requires.postflight
575
+ @requires.preflight
576
+ @requires.profile
577
+ @requires.project
578
+ @requires.catalogs
579
+ @requires.runtime_config
580
+ @requires.manifest
581
+ def run(ctx, **kwargs):
582
+ """Compile SQL and execute against the current target database.
583
+
584
+ DVT enhances dbt run with:
585
+ - Rich progress display with spinner and progress bar
586
+ - Per-model execution path display (PUSHDOWN vs FEDERATION)
587
+ - Beautiful summary panel with pass/fail/skip counts
588
+
589
+ DVT Compute Rules (automatically applied):
590
+ - Same-target models use adapter pushdown (native SQL)
591
+ - Cross-target models use Spark federation
592
+ - Compute selection: default < model config < CLI --target-compute
593
+ - Target selection: default < model config < CLI --target
594
+ """
595
+ from dbt.task.dvt_run import create_dvt_run_task
596
+
597
+ task = create_dvt_run_task(
598
+ ctx.obj["flags"],
599
+ ctx.obj["runtime_config"],
600
+ ctx.obj["manifest"],
601
+ )
602
+
603
+ results = task.run()
604
+ success = task.interpret_results(results)
605
+ return results, success
606
+
607
+
608
+ # dbt retry
609
+ @cli.command("retry")
610
+ @click.pass_context
611
+ @global_flags
612
+ @p.project_dir
613
+ @p.profiles_dir
614
+ @p.vars
615
+ @p.target_path
616
+ @p.threads
617
+ @p.full_refresh
618
+ @requires.postflight
619
+ @requires.preflight
620
+ @requires.profile
621
+ @requires.project
622
+ @requires.runtime_config
623
+ def retry(ctx, **kwargs):
624
+ """Retry the nodes that failed in the previous run."""
625
+ from dbt.task.retry import RetryTask
626
+
627
+ # Retry will parse manifest inside the task after we consolidate the flags
628
+ task = RetryTask(
629
+ ctx.obj["flags"],
630
+ ctx.obj["runtime_config"],
631
+ )
632
+
633
+ results = task.run()
634
+ success = task.interpret_results(results)
635
+ return results, success
636
+
637
+
638
+ # dbt clone
639
+ @cli.command("clone")
640
+ @click.pass_context
641
+ @global_flags
642
+ @p.exclude
643
+ @p.full_refresh
644
+ @p.profiles_dir
645
+ @p.project_dir
646
+ @p.resource_type
647
+ @p.exclude_resource_type
648
+ @p.select
649
+ @p.selector
650
+ @p.target_path
651
+ @p.threads
652
+ @p.vars
653
+ @requires.preflight
654
+ @requires.profile
655
+ @requires.project
656
+ @requires.runtime_config
657
+ @requires.manifest
658
+ @requires.postflight
659
+ def clone(ctx, **kwargs):
660
+ """Create clones of selected nodes based on their location in the manifest provided to --state."""
661
+ from dbt.task.clone import CloneTask
662
+
663
+ task = CloneTask(
664
+ ctx.obj["flags"],
665
+ ctx.obj["runtime_config"],
666
+ ctx.obj["manifest"],
667
+ )
668
+
669
+ results = task.run()
670
+ success = task.interpret_results(results)
671
+ return results, success
672
+
673
+
674
+ # dbt run operation
675
+ @cli.command("run-operation")
676
+ @click.pass_context
677
+ @global_flags
678
+ @click.argument("macro")
679
+ @p.args
680
+ @p.profiles_dir
681
+ @p.project_dir
682
+ @p.target_path
683
+ @p.threads
684
+ @p.vars
685
+ @requires.postflight
686
+ @requires.preflight
687
+ @requires.profile
688
+ @requires.project
689
+ @requires.runtime_config
690
+ @requires.manifest
691
+ def run_operation(ctx, **kwargs):
692
+ """Run the named macro with any supplied arguments."""
693
+ from dbt.task.run_operation import RunOperationTask
694
+
695
+ task = RunOperationTask(
696
+ ctx.obj["flags"],
697
+ ctx.obj["runtime_config"],
698
+ ctx.obj["manifest"],
699
+ )
700
+
701
+ results = task.run()
702
+ success = task.interpret_results(results)
703
+ return results, success
704
+
705
+
706
+ # dbt seed
707
+ @cli.command("seed")
708
+ @click.pass_context
709
+ @global_flags
710
+ @p.exclude
711
+ @p.full_refresh
712
+ @p.profiles_dir
713
+ @p.project_dir
714
+ @p.select
715
+ @p.selector
716
+ @p.show
717
+ @p.target_path
718
+ @p.threads
719
+ @p.vars
720
+ @requires.postflight
721
+ @requires.preflight
722
+ @requires.profile
723
+ @requires.project
724
+ @requires.catalogs
725
+ @requires.runtime_config
726
+ @requires.manifest
727
+ def seed(ctx, **kwargs):
728
+ """Load data from csv files into your data warehouse."""
729
+ from dbt.task.seed import SeedTask
730
+
731
+ task = SeedTask(
732
+ ctx.obj["flags"],
733
+ ctx.obj["runtime_config"],
734
+ ctx.obj["manifest"],
735
+ )
736
+ results = task.run()
737
+ success = task.interpret_results(results)
738
+ return results, success
739
+
740
+
741
+ # dbt snapshot
742
+ @cli.command("snapshot")
743
+ @click.pass_context
744
+ @global_flags
745
+ @p.empty
746
+ @p.exclude
747
+ @p.profiles_dir
748
+ @p.project_dir
749
+ @p.select
750
+ @p.selector
751
+ @p.target_path
752
+ @p.threads
753
+ @p.vars
754
+ @requires.postflight
755
+ @requires.preflight
756
+ @requires.profile
757
+ @requires.project
758
+ @requires.catalogs
759
+ @requires.runtime_config
760
+ @requires.manifest
761
+ def snapshot(ctx, **kwargs):
762
+ """Execute snapshots defined in your project"""
763
+ from dbt.task.snapshot import SnapshotTask
764
+
765
+ task = SnapshotTask(
766
+ ctx.obj["flags"],
767
+ ctx.obj["runtime_config"],
768
+ ctx.obj["manifest"],
769
+ )
770
+
771
+ results = task.run()
772
+ success = task.interpret_results(results)
773
+ return results, success
774
+
775
+
776
+ # dbt source
777
+ @cli.group()
778
+ @click.pass_context
779
+ @global_flags
780
+ def source(ctx, **kwargs):
781
+ """Manage your project's sources"""
782
+
783
+
784
+ # dbt source freshness
785
+ @source.command("freshness")
786
+ @click.pass_context
787
+ @global_flags
788
+ @p.exclude
789
+ @p.output_path # TODO: Is this ok to re-use? We have three different output params, how much can we consolidate?
790
+ @p.profiles_dir
791
+ @p.project_dir
792
+ @p.select
793
+ @p.selector
794
+ @p.target_path
795
+ @p.threads
796
+ @p.vars
797
+ @requires.postflight
798
+ @requires.preflight
799
+ @requires.profile
800
+ @requires.project
801
+ @requires.runtime_config
802
+ @requires.manifest
803
+ def freshness(ctx, **kwargs):
804
+ """check the current freshness of the project's sources"""
805
+ from dbt.task.freshness import FreshnessTask
806
+
807
+ task = FreshnessTask(
808
+ ctx.obj["flags"],
809
+ ctx.obj["runtime_config"],
810
+ ctx.obj["manifest"],
811
+ )
812
+
813
+ results = task.run()
814
+ success = task.interpret_results(results)
815
+ return results, success
816
+
817
+
818
+ # Alias "source freshness" to "snapshot-freshness"
819
+ snapshot_freshness = copy(cli.commands["source"].commands["freshness"]) # type: ignore
820
+ snapshot_freshness.hidden = True
821
+ cli.commands["source"].add_command(snapshot_freshness, "snapshot-freshness") # type: ignore
822
+
823
+
824
+ # dbt test
825
+ @cli.command("test")
826
+ @click.pass_context
827
+ @global_flags
828
+ @p.exclude
829
+ @p.resource_type
830
+ @p.exclude_resource_type
831
+ @p.profiles_dir
832
+ @p.project_dir
833
+ @p.select
834
+ @p.selector
835
+ @p.store_failures
836
+ @p.target_path
837
+ @p.threads
838
+ @p.vars
839
+ @requires.postflight
840
+ @requires.preflight
841
+ @requires.profile
842
+ @requires.project
843
+ @requires.runtime_config
844
+ @requires.manifest
845
+ def test(ctx, **kwargs):
846
+ """Runs tests on data in deployed models. Run this after `dbt run`"""
847
+ from dbt.task.test import TestTask
848
+
849
+ task = TestTask(
850
+ ctx.obj["flags"],
851
+ ctx.obj["runtime_config"],
852
+ ctx.obj["manifest"],
853
+ )
854
+
855
+ results = task.run()
856
+ success = task.interpret_results(results)
857
+ return results, success
858
+
859
+
860
+ # =============================================================================
861
+ # DVT Metadata Command Group (v0.57.0 - replaces dvt snap)
862
+ # =============================================================================
863
+
864
+ @cli.group("metadata")
865
+ def metadata():
866
+ """Manage DVT project metadata.
867
+
868
+ The metadata store captures type information for sources and models,
869
+ enabling accurate type mapping across federated queries.
870
+
871
+ Commands:
872
+ reset Clear all metadata from the store
873
+ snapshot Capture metadata for sources and models
874
+ export Display metadata in CLI
875
+ export-csv Export metadata to CSV file
876
+ export-json Export metadata to JSON file
877
+ """
878
+ pass
879
+
880
+
881
+ @metadata.command("reset")
882
+ @click.pass_context
883
+ @p.project_dir
884
+ def metadata_reset(ctx, project_dir, **kwargs):
885
+ """Clear all metadata from the store.
886
+
887
+ Removes all captured metadata including:
888
+ - Source column metadata
889
+ - Model column metadata
890
+ - Row counts
891
+ - Profile results
892
+
893
+ Example:
894
+ dvt metadata reset
895
+ """
896
+ from dbt.task.metadata import MetadataTask
897
+
898
+ class Args:
899
+ def __init__(self):
900
+ self.subcommand = 'reset'
901
+ self.project_dir = project_dir
902
+
903
+ task = MetadataTask(Args())
904
+ success, _ = task.run()
905
+ return None, success
906
+
907
+
908
+ @metadata.command("snapshot")
909
+ @click.pass_context
910
+ @p.project_dir
911
+ def metadata_snapshot(ctx, project_dir, **kwargs):
912
+ """Capture metadata for sources and models.
913
+
914
+ Reads source definitions (sources.yml) and model definitions (schema.yml)
915
+ from your project and captures column metadata into .dvt/metadata_store.duckdb.
916
+
917
+ This metadata is used by DVT to:
918
+ - Map adapter types to Spark types for federated queries
919
+ - Optimize query planning with schema information
920
+ - Generate correct Spark DDL for table creation
921
+
922
+ Examples:
923
+ dvt metadata snapshot
924
+
925
+ Note: This is automatically run on first 'dvt run' and on 'dvt run --full-refresh'.
926
+ """
927
+ from dbt.task.metadata import MetadataTask
928
+
929
+ class Args:
930
+ def __init__(self):
931
+ self.subcommand = 'snapshot'
932
+ self.project_dir = project_dir
933
+
934
+ task = MetadataTask(Args())
935
+ success, _ = task.run()
936
+ return None, success
937
+
938
+
939
+ @metadata.command("export")
940
+ @click.pass_context
941
+ @p.project_dir
942
+ def metadata_export(ctx, project_dir, **kwargs):
943
+ """Display metadata in CLI.
944
+
945
+ Shows a Rich-formatted table of all captured metadata including:
946
+ - Source/Model type
947
+ - Table names
948
+ - Column counts
949
+ - Last updated timestamp
950
+
951
+ Example:
952
+ dvt metadata export
953
+ """
954
+ from dbt.task.metadata import MetadataTask
955
+
956
+ class Args:
957
+ def __init__(self):
958
+ self.subcommand = 'export'
959
+ self.project_dir = project_dir
960
+
961
+ task = MetadataTask(Args())
962
+ success, _ = task.run()
963
+ return None, success
964
+
965
+
966
+ @metadata.command("export-csv")
967
+ @click.argument("filename", default="metadata.csv")
968
+ @click.pass_context
969
+ @p.project_dir
970
+ def metadata_export_csv(ctx, filename, project_dir, **kwargs):
971
+ """Export metadata to CSV file.
972
+
973
+ Exports all column metadata to a CSV file with columns:
974
+ type, source_name, table_name, column_name, adapter_type,
975
+ spark_type, is_nullable, is_primary_key, ordinal_position, last_refreshed
976
+
977
+ Examples:
978
+ dvt metadata export-csv # Creates metadata.csv
979
+ dvt metadata export-csv my_export.csv # Custom filename
980
+ """
981
+ from dbt.task.metadata import MetadataTask
982
+
983
+ class Args:
984
+ def __init__(self):
985
+ self.subcommand = 'export-csv'
986
+ self.project_dir = project_dir
987
+ self.filename = filename
988
+
989
+ task = MetadataTask(Args())
990
+ success, _ = task.run()
991
+ return None, success
992
+
993
+
994
+ @metadata.command("export-json")
995
+ @click.argument("filename", default="metadata.json")
996
+ @click.pass_context
997
+ @p.project_dir
998
+ def metadata_export_json(ctx, filename, project_dir, **kwargs):
999
+ """Export metadata to JSON file.
1000
+
1001
+ Exports all metadata to a JSON file with structured format:
1002
+ - sources: grouped by source name with tables and columns
1003
+ - models: grouped by model name with columns
1004
+
1005
+ Examples:
1006
+ dvt metadata export-json # Creates metadata.json
1007
+ dvt metadata export-json my_export.json # Custom filename
1008
+ """
1009
+ from dbt.task.metadata import MetadataTask
1010
+
1011
+ class Args:
1012
+ def __init__(self):
1013
+ self.subcommand = 'export-json'
1014
+ self.project_dir = project_dir
1015
+ self.filename = filename
1016
+
1017
+ task = MetadataTask(Args())
1018
+ success, _ = task.run()
1019
+ return None, success
1020
+
1021
+
1022
+ # DVT profile command group (v0.58.0) - profiling + web UI
1023
+ @cli.group("profile")
1024
+ @click.pass_context
1025
+ @global_flags
1026
+ def profile(ctx, **kwargs):
1027
+ """Profile data sources and models, or serve the profile viewer.
1028
+
1029
+ \b
1030
+ Commands:
1031
+ run - Run data profiling (default)
1032
+ serve - Start profile viewer web UI
1033
+
1034
+ \b
1035
+ Examples:
1036
+ dvt profile run # Profile all sources
1037
+ dvt profile run --mode minimal # Basic stats only
1038
+ dvt profile serve # Start web UI at http://localhost:8580
1039
+ """
1040
+ pass
1041
+
1042
+
1043
+ @profile.command("run")
1044
+ @click.pass_context
1045
+ @global_flags
1046
+ @p.exclude
1047
+ @p.profiles_dir
1048
+ @p.project_dir
1049
+ @p.select
1050
+ @p.selector
1051
+ @p.profile_mode
1052
+ @p.threads
1053
+ @p.vars
1054
+ @requires.postflight
1055
+ @requires.preflight
1056
+ @requires.profile
1057
+ @requires.project
1058
+ @requires.runtime_config
1059
+ @requires.manifest
1060
+ def profile_run(ctx, **kwargs):
1061
+ """Run data profiling on sources and models.
1062
+
1063
+ Works like 'dvt run' with DAG-based execution:
1064
+ - Respects --select and --exclude selectors
1065
+ - Respects --target and --target-compute overrides
1066
+ - Follows DVT compute rules (pushdown when possible)
1067
+
1068
+ \b
1069
+ Modes (--mode):
1070
+ - explorative: Full profiling with distributions [DEFAULT]
1071
+ - minimal: Basic stats (null%, distinct%, min/max)
1072
+ - sensitive: Redacted profiling (masks PII columns)
1073
+ - time-series: Temporal analysis
1074
+
1075
+ \b
1076
+ Examples:
1077
+ dvt profile run # Profile all sources
1078
+ dvt profile run --select "source:*" # Profile all sources
1079
+ dvt profile run --mode minimal # Basic stats only
1080
+ dvt profile run --target-compute spark # Use Spark
1081
+
1082
+ Results are saved to: .dvt/metadata_store.duckdb
1083
+ """
1084
+ from dbt.task.profile import ProfileTask
1085
+
1086
+ task = ProfileTask(
1087
+ ctx.obj["flags"],
1088
+ ctx.obj["runtime_config"],
1089
+ ctx.obj["manifest"],
1090
+ )
1091
+
1092
+ results = task.run()
1093
+ success = task.interpret_results(results)
1094
+ return results, success
1095
+
1096
+
1097
+ @profile.command("serve")
1098
+ @click.option("--port", default=8580, type=int, help="Port number for the profile viewer server")
1099
+ @click.option("--host", default="localhost", help="Host address to bind the server")
1100
+ @click.option("--no-browser", "no_browser", is_flag=True, default=False, help="Don't auto-open browser")
1101
+ @click.option("--project-dir", default=".", help="Project directory (defaults to current directory)")
1102
+ def profile_serve(port, host, no_browser, project_dir, **kwargs):
1103
+ """Start the profile viewer web UI.
1104
+
1105
+ Opens an interactive web interface to explore profiling results
1106
+ stored in .dvt/metadata_store.duckdb.
1107
+
1108
+ \b
1109
+ Features:
1110
+ - Summary statistics (tables, columns, sources, models)
1111
+ - Table browser with column details
1112
+ - Type mappings (adapter types to Spark types)
1113
+ - Beautiful dark theme UI
1114
+
1115
+ \b
1116
+ Examples:
1117
+ dvt profile serve # Start on http://localhost:8580
1118
+ dvt profile serve --port 9000 # Custom port
1119
+ dvt profile serve --no-browser # Don't auto-open browser
1120
+
1121
+ Note: Run 'dvt profile run' first to capture profiling data.
1122
+ """
1123
+ from pathlib import Path
1124
+ from dbt.task.profile_serve import serve_profile_ui
1125
+
1126
+ project_path = Path(project_dir)
1127
+ success = serve_profile_ui(
1128
+ project_dir=project_path,
1129
+ port=port,
1130
+ host=host,
1131
+ open_browser=not no_browser,
1132
+ )
1133
+ return None, success
1134
+
1135
+
1136
+ # DVT retract command - drop materialized models (v0.58.0)
1137
+ @cli.command("retract")
1138
+ @click.pass_context
1139
+ @global_flags
1140
+ @p.dry_run
1141
+ @p.exclude
1142
+ @p.profiles_dir
1143
+ @p.project_dir
1144
+ @p.select
1145
+ @p.selector
1146
+ @p.target_path
1147
+ @p.threads
1148
+ @p.vars
1149
+ @requires.postflight
1150
+ @requires.preflight
1151
+ @requires.profile
1152
+ @requires.project
1153
+ @requires.runtime_config
1154
+ @requires.manifest
1155
+ def retract(ctx, **kwargs):
1156
+ """Drop all materialized models from target databases.
1157
+
1158
+ Removes tables and views created by DVT from the target databases.
1159
+ Sources are never dropped. Use this to clean up a project or reset state.
1160
+
1161
+ \b
1162
+ WARNING: This command permanently deletes data. Use --dry-run first!
1163
+
1164
+ \b
1165
+ Examples:
1166
+ dvt retract --dry-run # Preview what would be dropped
1167
+ dvt retract # Drop all materialized models
1168
+ dvt retract --select "dim_*" # Drop matching models only
1169
+ dvt retract --exclude "fact_*" # Keep matching models
1170
+ dvt retract --target prod # Drop from specific target
1171
+ """
1172
+ from dbt.task.retract import RetractTask
1173
+
1174
+ task = RetractTask(
1175
+ ctx.obj["flags"],
1176
+ ctx.obj["runtime_config"],
1177
+ ctx.obj["manifest"],
1178
+ )
1179
+
1180
+ results = task.run()
1181
+ success = task.interpret_results(results)
1182
+ return results, success
1183
+
1184
+
1185
+ # DVT compute commands
1186
+ @cli.group()
1187
+ @click.pass_context
1188
+ @p.version
1189
+ def compute(ctx, **kwargs):
1190
+ """Manage DVT compute engines for multi-source federation.
1191
+
1192
+ Compute engines are configured in .dvt/computes.yml.
1193
+
1194
+ \b
1195
+ Commands:
1196
+ list - List all configured compute engines
1197
+ test - Test a compute engine's connectivity
1198
+ edit - Open computes.yml in your editor
1199
+ validate - Validate computes.yml syntax
1200
+
1201
+ \b
1202
+ Examples:
1203
+ dvt compute list # List all engines
1204
+ dvt compute test spark-local # Test local Spark
1205
+ dvt compute edit # Edit configuration
1206
+ """
1207
+
1208
+
1209
+ @compute.command("list")
1210
+ @click.pass_context
1211
+ @p.project_dir
1212
+ def compute_list(ctx, **kwargs):
1213
+ """List all configured compute engines.
1214
+
1215
+ Shows all compute engines defined in computes.yml with their
1216
+ platform type and description.
1217
+
1218
+ Examples:
1219
+ dvt compute list # List all compute engines
1220
+ """
1221
+ from dbt.task.compute import ComputeTask
1222
+
1223
+ task = ComputeTask(project_dir=kwargs.get("project_dir"))
1224
+ success = task.list_computes()
1225
+ return None, success
1226
+
1227
+
1228
+ @compute.command("test")
1229
+ @click.pass_context
1230
+ @click.argument("compute_name", required=True)
1231
+ @p.project_dir
1232
+ def compute_test(ctx, compute_name, **kwargs):
1233
+ """Test a compute engine's connectivity.
1234
+
1235
+ Tests the specified compute engine and shows its status.
1236
+ Use 'dvt compute list' to see all available compute engines.
1237
+
1238
+ Shows rich status symbols:
1239
+ ✅ Connected/Available
1240
+ ❌ Error/Not available
1241
+ ⚠️ Warning (missing optional dependency)
1242
+
1243
+ Examples:
1244
+ dvt compute test spark-local # Test local Spark
1245
+ dvt compute test databricks-prod # Test Databricks connectivity
1246
+ dvt compute test spark-docker # Test Docker Spark cluster
1247
+ """
1248
+ from dbt.task.compute import ComputeTask
1249
+
1250
+ task = ComputeTask(project_dir=kwargs.get("project_dir"))
1251
+ success = task.test_single_compute(compute_name)
1252
+ return None, success
1253
+
1254
+
1255
+ @compute.command("edit")
1256
+ @click.pass_context
1257
+ @p.project_dir
1258
+ def compute_edit(ctx, **kwargs):
1259
+ """Open computes.yml in your editor.
1260
+
1261
+ Opens the compute configuration file in your preferred editor.
1262
+ Uses EDITOR environment variable, or falls back to common editors
1263
+ (code, nano, vim, vi, notepad).
1264
+
1265
+ The file contains comprehensive commented samples for:
1266
+ - Local Spark (default)
1267
+ - Databricks (SQL Warehouse and Interactive Cluster)
1268
+ - AWS EMR
1269
+ - GCP Dataproc
1270
+ - Standalone Spark clusters
1271
+
1272
+ After editing, run 'dvt compute validate' to check syntax.
1273
+
1274
+ Examples:
1275
+ dvt compute edit # Open in default editor
1276
+ EDITOR=nano dvt compute edit # Use specific editor
1277
+ """
1278
+ from dbt.task.compute import ComputeTask
1279
+
1280
+ task = ComputeTask(project_dir=kwargs.get("project_dir"))
1281
+ success = task.edit_config()
1282
+ return None, success
1283
+
1284
+
1285
+ @compute.command("validate")
1286
+ @click.pass_context
1287
+ @p.project_dir
1288
+ def compute_validate(ctx, **kwargs):
1289
+ """Validate computes.yml syntax and configuration.
1290
+
1291
+ Checks the compute configuration file for:
1292
+ - Valid YAML syntax
1293
+ - Required fields (target_compute, type)
1294
+ - Valid compute engine references
1295
+ - Platform-specific configuration
1296
+
1297
+ Examples:
1298
+ dvt compute validate # Validate configuration
1299
+ """
1300
+ from dbt.task.compute import ComputeTask
1301
+
1302
+ task = ComputeTask(project_dir=kwargs.get("project_dir"))
1303
+ is_valid = task.validate_config()
1304
+ return None, is_valid
1305
+
1306
+
1307
+
1308
+
1309
+ # DVT migrate command - migrate from .dbt/ to .dvt/
1310
+ @cli.command("migrate")
1311
+ @click.pass_context
1312
+ @p.profiles_dir
1313
+ @p.project_dir
1314
+ def migrate(ctx, **kwargs):
1315
+ """Migrate configuration from .dbt/ to .dvt/ directory"""
1316
+ from pathlib import Path
1317
+ import shutil
1318
+
1319
+ home = Path.home()
1320
+
1321
+ old_dbt_dir = home / ".dbt"
1322
+ new_dvt_dir = home / ".dvt"
1323
+
1324
+ if not old_dbt_dir.exists():
1325
+ click.echo("No .dbt/ directory found - nothing to migrate")
1326
+ return True, True
1327
+
1328
+ if new_dvt_dir.exists():
1329
+ response = click.confirm(
1330
+ f".dvt/ directory already exists at {new_dvt_dir}. Overwrite files?"
1331
+ )
1332
+ if not response:
1333
+ click.echo("Migration cancelled")
1334
+ return False, False
1335
+
1336
+ # Create .dvt directory
1337
+ new_dvt_dir.mkdir(parents=True, exist_ok=True)
1338
+
1339
+ # Copy profiles.yml if it exists
1340
+ old_profiles = old_dbt_dir / "profiles.yml"
1341
+ new_profiles = new_dvt_dir / "profiles.yml"
1342
+
1343
+ migrated_files = []
1344
+
1345
+ if old_profiles.exists():
1346
+ shutil.copy2(old_profiles, new_profiles)
1347
+ migrated_files.append("profiles.yml")
1348
+ click.echo(f"✓ Copied {old_profiles} → {new_profiles}")
1349
+
1350
+ if migrated_files:
1351
+ click.echo("")
1352
+ click.echo(f"Migration complete! Migrated {len(migrated_files)} files:")
1353
+ for f in migrated_files:
1354
+ click.echo(f" - {f}")
1355
+ click.echo("")
1356
+ click.echo("Your .dbt/ directory has been preserved.")
1357
+ click.echo("DVT will now use .dvt/ for all configurations.")
1358
+ click.echo("")
1359
+ click.echo("Note: Compute engines are managed via 'dvt compute' commands.")
1360
+ else:
1361
+ click.echo("No files to migrate")
1362
+
1363
+ return True, True
1364
+
1365
+
1366
+ # DVT target (connection) management commands
1367
+ @cli.group()
1368
+ @click.pass_context
1369
+ @p.version
1370
+ def target(ctx, **kwargs):
1371
+ """Manage connection targets in profiles.yml.
1372
+
1373
+ \b
1374
+ Commands:
1375
+ list - List available targets
1376
+ test - Test connection to one or all targets
1377
+ add - Add a new target to a profile
1378
+ remove - Remove a target from a profile
1379
+ sync - Sync adapters and JDBC JARs
1380
+
1381
+ \b
1382
+ Examples:
1383
+ dvt target list # List all targets
1384
+ dvt target test # Test all connections
1385
+ dvt target test dev # Test specific target
1386
+ """
1387
+
1388
+
1389
+ @target.command("list")
1390
+ @click.option("--profile", help="Profile name to list targets from")
1391
+ @click.pass_context
1392
+ @p.profiles_dir
1393
+ @p.project_dir
1394
+ def target_list(ctx, profile, profiles_dir, project_dir, **kwargs):
1395
+ """List available targets in profiles.yml.
1396
+
1397
+ If executed from within a DVT project directory, automatically detects the profile
1398
+ from dbt_project.yml. Use --profile to override or when outside a project directory.
1399
+
1400
+ Features colored output for improved readability:
1401
+ - Cyan: Profile and target names
1402
+ - Green: Default target indicators
1403
+ - Red: Error messages
1404
+
1405
+ Examples:
1406
+ dvt target list # Auto-detect from project
1407
+ dvt target list --profile my_proj # Specific profile
1408
+ """
1409
+ from dbt.config.profile import read_profile
1410
+ from dbt.config.project_utils import get_project_profile_name
1411
+
1412
+ profiles = read_profile(profiles_dir)
1413
+
1414
+ if not profiles:
1415
+ click.echo(click.style("No profiles found in profiles.yml", fg="red"))
1416
+ ctx.exit(1)
1417
+
1418
+ # If --profile not provided, try to get from dbt_project.yml
1419
+ if not profile:
1420
+ profile = get_project_profile_name(project_dir)
1421
+ if not profile:
1422
+ # No profile specified and none found in project - show all profiles
1423
+ click.echo(click.style("Available profiles:", fg="cyan", bold=True))
1424
+ click.echo("")
1425
+ for profile_name, profile_data in profiles.items():
1426
+ if profile_data is None:
1427
+ profile_data = {}
1428
+ outputs = profile_data.get("outputs", {})
1429
+ target_count = len(outputs)
1430
+ default_target = profile_data.get(
1431
+ "default_target", profile_data.get("target", "unknown")
1432
+ )
1433
+ click.echo(f" {click.style(profile_name, fg='cyan')}")
1434
+ click.echo(
1435
+ f" Default target: {click.style(default_target, fg='green')}"
1436
+ )
1437
+ click.echo(f" Targets: {target_count}")
1438
+ click.echo("")
1439
+ return True, True
1440
+
1441
+ # Show targets for specific profile
1442
+ if profile not in profiles:
1443
+ click.echo(click.style(f"✗ Profile '{profile}' not found", fg="red"))
1444
+ ctx.exit(1)
1445
+
1446
+ profile_data = profiles[profile]
1447
+ if profile_data is None:
1448
+ click.echo(click.style(f"✗ Profile '{profile}' is empty or invalid", fg="red"))
1449
+ ctx.exit(1)
1450
+ outputs = profile_data.get("outputs", {})
1451
+
1452
+ if not outputs:
1453
+ click.echo(click.style(f"No targets found in profile '{profile}'", fg="yellow"))
1454
+ return True, True
1455
+
1456
+ default_target = profile_data.get(
1457
+ "default_target", profile_data.get("target", "unknown")
1458
+ )
1459
+
1460
+ # Always show profile name header for context
1461
+ click.echo(click.style(f"Profile: {profile}", fg="cyan", bold=True))
1462
+ click.echo(f"Default target: {click.style(default_target, fg='green')}")
1463
+ click.echo("")
1464
+ click.echo("Available targets:")
1465
+ for target_name, target_config in outputs.items():
1466
+ default_marker = (
1467
+ click.style(" (default)", fg="green")
1468
+ if target_name == default_target
1469
+ else ""
1470
+ )
1471
+ adapter_type = target_config.get("type", "unknown")
1472
+ click.echo(
1473
+ f" {click.style(target_name, fg='cyan')} ({adapter_type}){default_marker}"
1474
+ )
1475
+
1476
+ return True, True
1477
+
1478
+
1479
+ def _get_test_query(adapter_type: str) -> str:
1480
+ """Get adapter-specific test query for connection validation.
1481
+
1482
+ Args:
1483
+ adapter_type: The adapter type (postgres, snowflake, etc.)
1484
+
1485
+ Returns:
1486
+ SQL query string for testing connectivity
1487
+ """
1488
+ test_queries = {
1489
+ "postgres": "SELECT 1",
1490
+ "snowflake": "SELECT CURRENT_VERSION()",
1491
+ "bigquery": "SELECT 1",
1492
+ "redshift": "SELECT 1",
1493
+ "databricks": "SELECT 1",
1494
+ "mysql": "SELECT 1",
1495
+ "sqlserver": "SELECT 1",
1496
+ "oracle": "SELECT 1 FROM DUAL",
1497
+ "db2": "SELECT 1 FROM SYSIBM.SYSDUMMY1",
1498
+ "teradata": "SELECT 1",
1499
+ }
1500
+ return test_queries.get(adapter_type, "SELECT 1")
1501
+
1502
+
1503
+ def _get_connection_error_hint(exception: Exception, adapter_type: str) -> str:
1504
+ """Provide user-friendly hints for common connection errors.
1505
+
1506
+ Args:
1507
+ exception: The exception that was raised
1508
+ adapter_type: The adapter type being tested
1509
+
1510
+ Returns:
1511
+ A helpful error message with troubleshooting hints
1512
+ """
1513
+ error_str = str(exception).lower()
1514
+
1515
+ # Common error patterns and hints
1516
+ if "timeout" in error_str or "timed out" in error_str:
1517
+ return "Connection timeout - Check network connectivity and firewall rules"
1518
+ elif "could not connect" in error_str or "connection refused" in error_str:
1519
+ return "Connection refused - Verify host and port are correct"
1520
+ elif (
1521
+ "authentication" in error_str or "password" in error_str or "login" in error_str
1522
+ ):
1523
+ return "Authentication failed - Check username and password"
1524
+ elif "database" in error_str and "does not exist" in error_str:
1525
+ return "Database not found - Verify database name"
1526
+ elif "permission" in error_str or "access denied" in error_str:
1527
+ return "Permission denied - Check user privileges"
1528
+ elif "ssl" in error_str or "certificate" in error_str:
1529
+ return "SSL/TLS error - Check SSL configuration"
1530
+ elif "no such host" in error_str or "name resolution" in error_str:
1531
+ return "Host not found - Verify hostname is correct"
1532
+
1533
+ # Adapter-specific hints
1534
+ if adapter_type == "snowflake":
1535
+ if "account" in error_str:
1536
+ return "Invalid Snowflake account - Check account identifier format"
1537
+ elif "warehouse" in error_str:
1538
+ return "Warehouse error - Verify warehouse name and status"
1539
+ elif adapter_type == "databricks":
1540
+ if "token" in error_str:
1541
+ return "Invalid token - Check Databricks access token"
1542
+ elif "cluster" in error_str:
1543
+ return "Cluster error - Verify cluster is running and accessible"
1544
+
1545
+ return "Connection failed - See error details above"
1546
+
1547
+
1548
+ def _test_single_target(profile_data: dict, target_name: str) -> tuple[bool, str]:
1549
+ """Test connection to a single target by executing a query.
1550
+
1551
+ Args:
1552
+ profile_data: The profile dictionary containing outputs
1553
+ target_name: Name of the target to test
1554
+
1555
+ Returns:
1556
+ Tuple of (success: bool, message: str)
1557
+ """
1558
+ outputs = profile_data.get("outputs", {})
1559
+
1560
+ if target_name not in outputs:
1561
+ return False, f"Target '{target_name}' not found"
1562
+
1563
+ target_config = outputs[target_name]
1564
+ adapter_type = target_config.get("type", "unknown")
1565
+
1566
+ # Validate required fields first
1567
+ required_fields = {
1568
+ "postgres": ["host", "user", "database"],
1569
+ "snowflake": ["account", "user", "database", "warehouse"],
1570
+ "bigquery": ["project", "dataset"],
1571
+ "redshift": ["host", "user", "database"],
1572
+ "databricks": ["host", "http_path"],
1573
+ }
1574
+
1575
+ missing_fields = []
1576
+ if adapter_type in required_fields:
1577
+ for field in required_fields[adapter_type]:
1578
+ if field not in target_config:
1579
+ missing_fields.append(field)
1580
+
1581
+ if missing_fields:
1582
+ return False, f"Missing required fields: {', '.join(missing_fields)}"
1583
+
1584
+ # Import adapter and test connection
1585
+ try:
1586
+ # Get test query for this adapter
1587
+ test_query = _get_test_query(adapter_type)
1588
+
1589
+ # Use the adapter's native connection testing approach
1590
+ # Each adapter has different connection methods, so we'll use
1591
+ # a simplified approach that works across adapters
1592
+
1593
+ if adapter_type == "postgres":
1594
+ try:
1595
+ import psycopg2
1596
+
1597
+ # Build connection string
1598
+ conn_params = {
1599
+ "host": target_config.get("host", "localhost"),
1600
+ "port": target_config.get("port", 5432),
1601
+ "database": target_config.get(
1602
+ "database", target_config.get("dbname")
1603
+ ),
1604
+ "user": target_config.get("user"),
1605
+ "password": target_config.get("password"),
1606
+ }
1607
+ # Test connection
1608
+ conn = psycopg2.connect(**conn_params, connect_timeout=10)
1609
+ cursor = conn.cursor()
1610
+ cursor.execute(test_query)
1611
+ cursor.fetchone()
1612
+ cursor.close()
1613
+ conn.close()
1614
+ return True, "Connection successful"
1615
+ except ImportError:
1616
+ return (
1617
+ False,
1618
+ "psycopg2 not installed - Run 'pip install psycopg2-binary' or 'pip install dbt-postgres'",
1619
+ )
1620
+ except Exception as e:
1621
+ hint = _get_connection_error_hint(e, adapter_type)
1622
+ return False, f"Connection failed: {str(e)}\nHint: {hint}"
1623
+
1624
+ elif adapter_type == "snowflake":
1625
+ try:
1626
+ import snowflake.connector
1627
+
1628
+ # Build connection params
1629
+ conn_params = {
1630
+ "account": target_config.get("account"),
1631
+ "user": target_config.get("user"),
1632
+ "password": target_config.get("password"),
1633
+ "database": target_config.get("database"),
1634
+ "warehouse": target_config.get("warehouse"),
1635
+ "schema": target_config.get("schema", "PUBLIC"),
1636
+ }
1637
+ # Test connection
1638
+ conn = snowflake.connector.connect(**conn_params, login_timeout=10)
1639
+ cursor = conn.cursor()
1640
+ cursor.execute(test_query)
1641
+ cursor.fetchone()
1642
+ cursor.close()
1643
+ conn.close()
1644
+ return True, "Connection successful"
1645
+ except ImportError:
1646
+ return (
1647
+ False,
1648
+ "snowflake-connector-python not installed - Run 'pip install snowflake-connector-python' or 'pip install dbt-snowflake'",
1649
+ )
1650
+ except Exception as e:
1651
+ hint = _get_connection_error_hint(e, adapter_type)
1652
+ return False, f"Connection failed: {str(e)}\nHint: {hint}"
1653
+
1654
+ elif adapter_type == "bigquery":
1655
+ try:
1656
+ from google.cloud import bigquery
1657
+
1658
+ # Build client
1659
+ project = target_config.get("project")
1660
+ client = bigquery.Client(project=project)
1661
+ # Test connection with simple query
1662
+ query_job = client.query(test_query)
1663
+ query_job.result(timeout=10)
1664
+ return True, "Connection successful"
1665
+ except ImportError:
1666
+ return (
1667
+ False,
1668
+ "google-cloud-bigquery not installed - Run 'pip install google-cloud-bigquery' or 'pip install dbt-bigquery'",
1669
+ )
1670
+ except Exception as e:
1671
+ hint = _get_connection_error_hint(e, adapter_type)
1672
+ return False, f"Connection failed: {str(e)}\nHint: {hint}"
1673
+
1674
+ elif adapter_type == "redshift":
1675
+ try:
1676
+ import psycopg2
1677
+
1678
+ # Redshift uses postgres protocol
1679
+ conn_params = {
1680
+ "host": target_config.get("host"),
1681
+ "port": target_config.get("port", 5439),
1682
+ "database": target_config.get("database"),
1683
+ "user": target_config.get("user"),
1684
+ "password": target_config.get("password"),
1685
+ }
1686
+ # Test connection
1687
+ conn = psycopg2.connect(**conn_params, connect_timeout=10)
1688
+ cursor = conn.cursor()
1689
+ cursor.execute(test_query)
1690
+ cursor.fetchone()
1691
+ cursor.close()
1692
+ conn.close()
1693
+ return True, "Connection successful"
1694
+ except ImportError:
1695
+ return (
1696
+ False,
1697
+ "psycopg2 not installed - Run 'pip install psycopg2-binary' or 'pip install dbt-redshift'",
1698
+ )
1699
+ except Exception as e:
1700
+ hint = _get_connection_error_hint(e, adapter_type)
1701
+ return False, f"Connection failed: {str(e)}\nHint: {hint}"
1702
+
1703
+ elif adapter_type == "databricks":
1704
+ try:
1705
+ from databricks import sql
1706
+
1707
+ # Build connection params
1708
+ conn_params = {
1709
+ "server_hostname": target_config.get("host", "").replace(
1710
+ "https://", ""
1711
+ ),
1712
+ "http_path": target_config.get("http_path"),
1713
+ "access_token": target_config.get("token"),
1714
+ }
1715
+ # Test connection
1716
+ conn = sql.connect(**conn_params)
1717
+ cursor = conn.cursor()
1718
+ cursor.execute(test_query)
1719
+ cursor.fetchone()
1720
+ cursor.close()
1721
+ conn.close()
1722
+ return True, "Connection successful"
1723
+ except ImportError:
1724
+ return (
1725
+ False,
1726
+ "databricks-sql-connector not installed - Run 'pip install databricks-sql-connector' or 'pip install dbt-databricks'",
1727
+ )
1728
+ except Exception as e:
1729
+ hint = _get_connection_error_hint(e, adapter_type)
1730
+ return False, f"Connection failed: {str(e)}\nHint: {hint}"
1731
+
1732
+ else:
1733
+ # For unsupported adapter types, just return configuration validation
1734
+ return (
1735
+ True,
1736
+ "Configuration valid (network testing not available for this adapter type)",
1737
+ )
1738
+
1739
+ except Exception as e:
1740
+ hint = _get_connection_error_hint(e, adapter_type)
1741
+ return False, f"Unexpected error: {str(e)}\nHint: {hint}"
1742
+
1743
+
1744
+ def _test_target_with_timeout(
1745
+ profile_data: dict, target_name: str, timeout: int = 30
1746
+ ) -> tuple[bool, str]:
1747
+ """Test target connection with timeout protection.
1748
+
1749
+ Args:
1750
+ profile_data: The profile dictionary
1751
+ target_name: Name of the target to test
1752
+ timeout: Timeout in seconds (default 30)
1753
+
1754
+ Returns:
1755
+ Tuple of (success: bool, message: str)
1756
+ """
1757
+ with ThreadPoolExecutor(max_workers=1) as executor:
1758
+ future = executor.submit(_test_single_target, profile_data, target_name)
1759
+ try:
1760
+ success, message = future.result(timeout=timeout)
1761
+ return success, message
1762
+ except FuturesTimeoutError:
1763
+ return False, f"Connection test timed out after {timeout} seconds"
1764
+ except Exception as e:
1765
+ return False, f"Unexpected error during connection test: {str(e)}"
1766
+
1767
+
1768
+ @target.command("test")
1769
+ @click.argument("target_name", required=False, default=None)
1770
+ @click.option("--profile", help="Profile name (defaults to project profile)")
1771
+ @click.option(
1772
+ "--timeout",
1773
+ type=int,
1774
+ default=30,
1775
+ help="Connection timeout in seconds (default: 30)",
1776
+ )
1777
+ @click.pass_context
1778
+ @p.profiles_dir
1779
+ @p.project_dir
1780
+ def target_test(
1781
+ ctx, target_name, profile, timeout, profiles_dir, project_dir, **kwargs
1782
+ ):
1783
+ """Test connection to one or all targets.
1784
+
1785
+ When TARGET_NAME is provided: Tests connection to a specific target
1786
+ When TARGET_NAME is omitted: Tests all targets in the profile
1787
+
1788
+ This command now performs REAL connection testing by executing a simple query
1789
+ against the target database. It validates both configuration AND network connectivity.
1790
+
1791
+ Features colored output and proper exit codes:
1792
+ - Exit code 0: All connections succeeded
1793
+ - Exit code 1: One or more connections failed
1794
+ - Green checkmarks (✓): Success
1795
+ - Red X marks (✗): Errors
1796
+
1797
+ Examples:
1798
+ dvt target test # Test ALL targets (auto-detect profile)
1799
+ dvt target test dev # Test specific target (auto-detect profile)
1800
+ dvt target test prod --profile my_proj # Test specific target (explicit profile)
1801
+ dvt target test --profile my_proj # Test all targets in profile
1802
+ dvt target test dev --timeout 60 # Custom timeout
1803
+
1804
+ Performance:
1805
+ - Tests run with configurable timeout (default 30s)
1806
+ - Provides helpful error hints for common connection issues
1807
+ - Shows detailed connection information on success
1808
+ """
1809
+ from dbt.config.profile import read_profile
1810
+ from dbt.config.project_utils import get_project_profile_name
1811
+
1812
+ profiles = read_profile(profiles_dir)
1813
+
1814
+ # Determine which profile to use
1815
+ if not profile:
1816
+ # Try to get from dbt_project.yml first
1817
+ profile = get_project_profile_name(project_dir)
1818
+
1819
+ if not profile:
1820
+ # If testing single target without profile, search all profiles
1821
+ if target_name:
1822
+ for prof_name, prof_data in profiles.items():
1823
+ outputs = prof_data.get("outputs", {}) if prof_data else {}
1824
+ if target_name in outputs:
1825
+ profile = prof_name
1826
+ break
1827
+
1828
+ if not profile:
1829
+ click.echo(
1830
+ click.style(
1831
+ "✗ Error: Could not determine profile. Use --profile flag.", fg="red"
1832
+ )
1833
+ )
1834
+ ctx.exit(1)
1835
+
1836
+ if profile not in profiles:
1837
+ click.echo(click.style(f"✗ Profile '{profile}' not found", fg="red"))
1838
+ ctx.exit(1)
1839
+
1840
+ profile_data = profiles[profile]
1841
+ if profile_data is None:
1842
+ click.echo(click.style(f"✗ Profile '{profile}' is empty or invalid", fg="red"))
1843
+ ctx.exit(1)
1844
+ outputs = profile_data.get("outputs", {})
1845
+
1846
+ if not outputs:
1847
+ click.echo(click.style(f"No targets found in profile '{profile}'", fg="yellow"))
1848
+ ctx.exit(0)
1849
+
1850
+ # CASE 1: Test specific target
1851
+ if target_name:
1852
+ if target_name not in outputs:
1853
+ click.echo(
1854
+ click.style(
1855
+ f"✗ Target '{target_name}' not found in profile '{profile}'",
1856
+ fg="red",
1857
+ )
1858
+ )
1859
+ ctx.exit(1)
1860
+
1861
+ target_config = outputs[target_name]
1862
+ adapter_type = target_config.get("type", "unknown")
1863
+
1864
+ click.echo(
1865
+ f"Testing connection: {click.style(target_name, fg='cyan')} ({adapter_type})"
1866
+ )
1867
+
1868
+ # Show connection details FIRST (like dbt debug)
1869
+ if "host" in target_config:
1870
+ click.echo(f" host: {target_config['host']}")
1871
+ if "port" in target_config:
1872
+ click.echo(f" port: {target_config['port']}")
1873
+ if "account" in target_config:
1874
+ click.echo(f" account: {target_config['account']}")
1875
+ if "database" in target_config or "dbname" in target_config:
1876
+ db = target_config.get("database") or target_config.get("dbname")
1877
+ click.echo(f" database: {db}")
1878
+ if "warehouse" in target_config:
1879
+ click.echo(f" warehouse: {target_config['warehouse']}")
1880
+ if "schema" in target_config:
1881
+ click.echo(f" schema: {target_config['schema']}")
1882
+ if "project" in target_config:
1883
+ click.echo(f" project: {target_config['project']}")
1884
+
1885
+ # Test connection with timeout
1886
+ success, message = _test_target_with_timeout(profile_data, target_name, timeout)
1887
+
1888
+ # Show test result (like dbt debug)
1889
+ if success:
1890
+ click.echo(
1891
+ f" Connection test: {click.style('[OK connection ok]', fg='green')}"
1892
+ )
1893
+ ctx.exit(0)
1894
+ else:
1895
+ click.echo(f" Connection test: {click.style('[ERROR]', fg='red')}")
1896
+ click.echo(f" {message}")
1897
+ ctx.exit(1)
1898
+
1899
+ # CASE 2: Test all targets in profile
1900
+ else:
1901
+ total_targets = len(outputs)
1902
+ click.echo(
1903
+ f"Testing all connections in profile {click.style(profile, fg='cyan')}...\n"
1904
+ )
1905
+
1906
+ # Test each target with progress indicators
1907
+ passed_count = 0
1908
+ failed_count = 0
1909
+ target_index = 1
1910
+
1911
+ for tgt_name, target_config in outputs.items():
1912
+ adapter_type = target_config.get("type", "unknown")
1913
+
1914
+ # Progress indicator
1915
+ progress = click.style(f"[{target_index}/{total_targets}]", fg="yellow")
1916
+ click.echo(
1917
+ f"{progress} Testing connection: {click.style(tgt_name, fg='cyan')} ({adapter_type})"
1918
+ )
1919
+
1920
+ # Show connection details FIRST (like dbt debug)
1921
+ if "host" in target_config:
1922
+ click.echo(f" host: {target_config['host']}")
1923
+ if "port" in target_config:
1924
+ click.echo(f" port: {target_config['port']}")
1925
+ if "account" in target_config:
1926
+ click.echo(f" account: {target_config['account']}")
1927
+ if "database" in target_config or "dbname" in target_config:
1928
+ db = target_config.get("database") or target_config.get("dbname")
1929
+ click.echo(f" database: {db}")
1930
+ if "warehouse" in target_config:
1931
+ click.echo(f" warehouse: {target_config['warehouse']}")
1932
+ if "schema" in target_config:
1933
+ click.echo(f" schema: {target_config['schema']}")
1934
+ if "project" in target_config:
1935
+ click.echo(f" project: {target_config['project']}")
1936
+
1937
+ # Test connection
1938
+ success, message = _test_target_with_timeout(
1939
+ profile_data, tgt_name, timeout
1940
+ )
1941
+
1942
+ # Show test result (like dbt debug)
1943
+ if success:
1944
+ click.echo(
1945
+ f" Connection test: {click.style('[OK connection ok]', fg='green')}"
1946
+ )
1947
+ passed_count += 1
1948
+ else:
1949
+ click.echo(f" Connection test: {click.style('[ERROR]', fg='red')}")
1950
+ click.echo(f" {message}")
1951
+ failed_count += 1
1952
+
1953
+ click.echo("")
1954
+ target_index += 1
1955
+
1956
+ # Summary line
1957
+ click.echo("─" * 60)
1958
+ if failed_count == 0:
1959
+ summary = click.style(
1960
+ f"✓ All {passed_count} connection tests passed", fg="green", bold=True
1961
+ )
1962
+ click.echo(summary)
1963
+ ctx.exit(0)
1964
+ else:
1965
+ passed_str = click.style(f"{passed_count} passed", fg="green")
1966
+ failed_str = click.style(f"{failed_count} failed", fg="red")
1967
+ summary = f"✗ {passed_str}, {failed_str}"
1968
+ click.echo(summary)
1969
+ ctx.exit(1)
1970
+
1971
+
1972
+ @target.command("add")
1973
+ @click.argument("target_name")
1974
+ @click.option("--profile", required=True, help="Profile name to add target to")
1975
+ @click.option(
1976
+ "--type",
1977
+ "adapter_type",
1978
+ required=True,
1979
+ help="Adapter type (postgres, snowflake, etc)",
1980
+ )
1981
+ @click.option("--host", help="Database host")
1982
+ @click.option("--port", type=int, help="Database port")
1983
+ @click.option("--user", help="Database user")
1984
+ @click.option("--password", help="Database password")
1985
+ @click.option("--database", help="Database name")
1986
+ @click.option("--schema", help="Default schema")
1987
+ @click.option("--threads", type=int, default=4, help="Number of threads")
1988
+ @click.option("--set-default", is_flag=True, help="Set as default target for profile")
1989
+ @click.pass_context
1990
+ @p.profiles_dir
1991
+ def target_add(
1992
+ ctx,
1993
+ target_name,
1994
+ profile,
1995
+ adapter_type,
1996
+ host,
1997
+ port,
1998
+ user,
1999
+ password,
2000
+ database,
2001
+ schema,
2002
+ threads,
2003
+ set_default,
2004
+ profiles_dir,
2005
+ **kwargs,
2006
+ ):
2007
+ """Add a new target to a profile in profiles.yml"""
2008
+ import yaml
2009
+ from pathlib import Path
2010
+
2011
+ profiles_file = Path(profiles_dir) / "profiles.yml"
2012
+
2013
+ if not profiles_file.exists():
2014
+ click.echo(f"✗ profiles.yml not found at {profiles_file}")
2015
+ return False, False
2016
+
2017
+ with open(profiles_file, "r") as f:
2018
+ profiles = yaml.safe_load(f) or {}
2019
+
2020
+ if profile not in profiles:
2021
+ click.echo(f"✗ Profile '{profile}' not found in profiles.yml")
2022
+ return False, False
2023
+
2024
+ profile_data = profiles[profile]
2025
+ if profile_data is None:
2026
+ click.echo(f"✗ Profile '{profile}' is empty or invalid")
2027
+ return False, False
2028
+
2029
+ # Get or create outputs dict (standard dbt format)
2030
+ if "outputs" not in profile_data:
2031
+ profile_data["outputs"] = {}
2032
+
2033
+ outputs = profile_data["outputs"]
2034
+
2035
+ # Check if target already exists
2036
+ if target_name in outputs:
2037
+ if not click.confirm(f"Target '{target_name}' already exists. Overwrite?"):
2038
+ return False, False
2039
+
2040
+ # Build target config
2041
+ target_config = {"type": adapter_type}
2042
+
2043
+ if host:
2044
+ target_config["host"] = host
2045
+ if port:
2046
+ target_config["port"] = port
2047
+ if user:
2048
+ target_config["user"] = user
2049
+ if password:
2050
+ target_config["password"] = password
2051
+ if database:
2052
+ target_config["database"] = database
2053
+ if schema:
2054
+ target_config["schema"] = schema
2055
+ if threads:
2056
+ target_config["threads"] = threads
2057
+
2058
+ # Add target to outputs
2059
+ outputs[target_name] = target_config
2060
+
2061
+ # Set as default if requested
2062
+ if set_default:
2063
+ profile_data["target"] = target_name
2064
+
2065
+ # Write back to profiles.yml
2066
+ with open(profiles_file, "w") as f:
2067
+ yaml.dump(profiles, f, default_flow_style=False, sort_keys=False)
2068
+
2069
+ click.echo(f"✓ Added target '{target_name}' to profile '{profile}'")
2070
+ if set_default:
2071
+ click.echo(f" Set as default target")
2072
+
2073
+ return True, True
2074
+
2075
+
2076
+ @target.command("sync")
2077
+ @click.option("--profile", help="Profile name (defaults to project profile)")
2078
+ @click.option("--clean", is_flag=True, help="Remove adapters not needed by profiles.yml")
2079
+ @click.option("--dry-run", is_flag=True, help="Show what would be done without making changes")
2080
+ @click.pass_context
2081
+ @p.profiles_dir
2082
+ @p.project_dir
2083
+ def target_sync(ctx, profile, clean, dry_run, profiles_dir, project_dir, **kwargs):
2084
+ """Sync adapters and JDBC JARs based on profiles.yml connections.
2085
+
2086
+ Scans your profiles.yml to find all connection types, then:
2087
+ - Installs required dbt adapters via pip
2088
+ - Updates JDBC JARs for Spark federation
2089
+ - Optionally removes unused adapters (with --clean)
2090
+
2091
+ Examples:
2092
+
2093
+ dvt target sync # Sync for current project
2094
+ dvt target sync --profile my_project # Sync specific profile
2095
+ dvt target sync --dry-run # Show what would happen
2096
+ dvt target sync --clean # Also remove unused adapters
2097
+ """
2098
+ from dbt.task.target_sync import TargetSyncTask
2099
+
2100
+ task = TargetSyncTask(
2101
+ project_dir=project_dir,
2102
+ profiles_dir=profiles_dir,
2103
+ profile_name=profile,
2104
+ )
2105
+ success = task.sync(verbose=True, clean=clean, dry_run=dry_run)
2106
+ return None, success
2107
+
2108
+
2109
+ @target.command("remove")
2110
+ @click.argument("target_name")
2111
+ @click.option("--profile", required=True, help="Profile name to remove target from")
2112
+ @click.pass_context
2113
+ @p.profiles_dir
2114
+ def target_remove(ctx, target_name, profile, profiles_dir, **kwargs):
2115
+ """Remove a target from a profile in profiles.yml"""
2116
+ import yaml
2117
+ from pathlib import Path
2118
+
2119
+ profiles_file = Path(profiles_dir) / "profiles.yml"
2120
+
2121
+ if not profiles_file.exists():
2122
+ click.echo(f"✗ profiles.yml not found at {profiles_file}")
2123
+ return False, False
2124
+
2125
+ with open(profiles_file, "r") as f:
2126
+ profiles = yaml.safe_load(f) or {}
2127
+
2128
+ if profile not in profiles:
2129
+ click.echo(f"✗ Profile '{profile}' not found in profiles.yml")
2130
+ return False, False
2131
+
2132
+ profile_data = profiles[profile]
2133
+ if profile_data is None:
2134
+ click.echo(f"✗ Profile '{profile}' is empty or invalid")
2135
+ return False, False
2136
+ outputs = profile_data.get("outputs", {})
2137
+
2138
+ if target_name not in outputs:
2139
+ click.echo(f"✗ Target '{target_name}' not found in profile '{profile}'")
2140
+ return False, False
2141
+
2142
+ # Remove the target
2143
+ del outputs[target_name]
2144
+
2145
+ # Check if this was the default target
2146
+ default_target = profile_data.get("target")
2147
+ if default_target == target_name:
2148
+ # Set new default to first available target
2149
+ if outputs:
2150
+ new_default = list(outputs.keys())[0]
2151
+ profile_data["target"] = new_default
2152
+ click.echo(
2153
+ f" Note: '{target_name}' was the default target, changed to '{new_default}'"
2154
+ )
2155
+ else:
2156
+ click.echo(f" Warning: No targets remaining in profile '{profile}'")
2157
+
2158
+ # Write back to profiles.yml
2159
+ with open(profiles_file, "w") as f:
2160
+ yaml.dump(profiles, f, default_flow_style=False, sort_keys=False)
2161
+
2162
+ click.echo(f"✓ Removed target '{target_name}' from profile '{profile}'")
2163
+
2164
+ return True, True
2165
+
2166
+
2167
+ # DVT java commands for Java management
2168
+ @cli.group()
2169
+ @click.pass_context
2170
+ @p.version
2171
+ def java(ctx, **kwargs):
2172
+ """Manage Java installations for PySpark.
2173
+
2174
+ Java is required for Spark compute engines.
2175
+
2176
+ \b
2177
+ Compatibility:
2178
+ PySpark 4.0.x -> Java 17 or 21
2179
+ PySpark 3.5.x -> Java 8, 11, or 17
2180
+ PySpark 3.3-3.4 -> Java 8 or 11
2181
+
2182
+ \b
2183
+ Commands:
2184
+ check - Check Java compatibility with PySpark
2185
+ search - Find all Java installations
2186
+ set - Select and configure JAVA_HOME
2187
+ install - Show installation guide
2188
+
2189
+ \b
2190
+ Examples:
2191
+ dvt java check # Check compatibility
2192
+ dvt java search # Find installations
2193
+ dvt java set # Configure JAVA_HOME
2194
+ """
2195
+
2196
+
2197
+ @java.command("check")
2198
+ @click.pass_context
2199
+ def java_check(ctx, **kwargs):
2200
+ """Check Java installation and PySpark compatibility.
2201
+
2202
+ Shows current Java version, installed PySpark version, and whether
2203
+ they are compatible. Provides guidance if there's a mismatch.
2204
+
2205
+ Exit codes:
2206
+ 0 - Java and PySpark are compatible
2207
+ 1 - Java/PySpark mismatch or not found
2208
+ """
2209
+ from dbt.task.java import JavaTask
2210
+
2211
+ task = JavaTask()
2212
+ is_compatible = task.check()
2213
+ ctx.exit(0 if is_compatible else 1)
2214
+
2215
+
2216
+ @java.command("search")
2217
+ @click.pass_context
2218
+ def java_search(ctx, **kwargs):
2219
+ """Find all Java installations on the system.
2220
+
2221
+ Searches common installation locations for Java on your OS:
2222
+
2223
+ \b
2224
+ macOS: /Library/Java/JavaVirtualMachines, Homebrew, SDKMAN
2225
+ Linux: /usr/lib/jvm, /opt/java, update-alternatives, SDKMAN
2226
+ Windows: Program Files, Registry, Scoop, Chocolatey
2227
+
2228
+ Shows Java version, vendor, and compatibility with installed PySpark.
2229
+ """
2230
+ from dbt.task.java import JavaTask
2231
+
2232
+ task = JavaTask()
2233
+ installations = task.search()
2234
+ ctx.exit(0 if installations else 1)
2235
+
2236
+
2237
+ @java.command("set")
2238
+ @click.pass_context
2239
+ def java_set(ctx, **kwargs):
2240
+ """Interactively select and set JAVA_HOME.
2241
+
2242
+ Shows all found Java installations with compatibility indicators,
2243
+ lets you choose one, and updates your shell configuration file
2244
+ (.zshrc, .bashrc, etc.) to persist JAVA_HOME.
2245
+
2246
+ After setting, restart your terminal or run 'source ~/.zshrc'
2247
+ (or equivalent) for changes to take effect.
2248
+ """
2249
+ from dbt.task.java import JavaTask
2250
+
2251
+ task = JavaTask()
2252
+ success = task.set_java_home()
2253
+ ctx.exit(0 if success else 1)
2254
+
2255
+
2256
+ @java.command("install")
2257
+ @click.pass_context
2258
+ def java_install(ctx, **kwargs):
2259
+ """Show Java installation guide for your platform.
2260
+
2261
+ Provides platform-specific installation instructions based on
2262
+ your installed PySpark version. Includes options for:
2263
+
2264
+ \b
2265
+ macOS: Homebrew, SDKMAN, manual download
2266
+ Linux: apt-get, dnf, pacman, SDKMAN
2267
+ Windows: Winget, Chocolatey, Scoop, manual download
2268
+ """
2269
+ from dbt.task.java import JavaTask
2270
+
2271
+ task = JavaTask()
2272
+ task.install_guide()
2273
+ ctx.exit(0)
2274
+
2275
+
2276
+ # DVT spark commands for Spark/PySpark management
2277
+ @cli.group()
2278
+ @click.pass_context
2279
+ @p.version
2280
+ def spark(ctx, **kwargs):
2281
+ """Manage PySpark installations and cluster compatibility.
2282
+
2283
+ PySpark is used by DVT for federated query execution.
2284
+
2285
+ \b
2286
+ Commands:
2287
+ check - Check PySpark/Java status
2288
+ set-version - Install specific PySpark version
2289
+ match-cluster - Match PySpark to cluster version
2290
+ versions - Show compatibility matrix
2291
+
2292
+ \b
2293
+ Examples:
2294
+ dvt spark check # Check status
2295
+ dvt spark set-version # Install PySpark
2296
+ dvt spark match-cluster spark # Match to cluster
2297
+ dvt spark versions # Show matrix
2298
+ """
2299
+
2300
+
2301
+ @spark.command("check")
2302
+ @click.pass_context
2303
+ def spark_check(ctx, **kwargs):
2304
+ """Check PySpark installation and Java compatibility.
2305
+
2306
+ Shows:
2307
+ - Installed PySpark version and requirements
2308
+ - Current Java version
2309
+ - Compatibility status
2310
+
2311
+ Exit codes:
2312
+ 0 - PySpark installed and Java compatible
2313
+ 1 - PySpark not installed or Java incompatible
2314
+ """
2315
+ from dbt.task.spark import SparkTask
2316
+
2317
+ task = SparkTask()
2318
+ is_ok = task.check()
2319
+ ctx.exit(0 if is_ok else 1)
2320
+
2321
+
2322
+ @spark.command("set-version")
2323
+ @click.pass_context
2324
+ def spark_set_version(ctx, **kwargs):
2325
+ """Interactively select and install a PySpark version.
2326
+
2327
+ Presents available PySpark versions with their Java requirements.
2328
+ Shows compatibility indicators based on your current Java.
2329
+ Installs the selected version via pip.
2330
+
2331
+ Available versions:
2332
+ \b
2333
+ PySpark 4.0.x - Latest, requires Java 17+
2334
+ PySpark 3.5.x - Stable, Java 8/11/17
2335
+ PySpark 3.4.x - Java 8/11/17
2336
+ PySpark 3.3.x - Java 8/11
2337
+ PySpark 3.2.x - Java 8/11
2338
+
2339
+ After installing, check Java compatibility with 'dvt java check'.
2340
+ """
2341
+ from dbt.task.spark import SparkTask
2342
+
2343
+ task = SparkTask()
2344
+ success = task.set_version()
2345
+ ctx.exit(0 if success else 1)
2346
+
2347
+
2348
+ @spark.command("match-cluster")
2349
+ @click.argument("compute_name")
2350
+ @click.pass_context
2351
+ def spark_match_cluster(ctx, compute_name, **kwargs):
2352
+ """Detect cluster Spark version and check PySpark compatibility.
2353
+
2354
+ Connects to the specified compute engine from computes.yml,
2355
+ detects its Spark version, and compares with locally installed
2356
+ PySpark. Provides recommendations if versions don't match.
2357
+
2358
+ IMPORTANT: PySpark version must match the cluster's Spark version
2359
+ (same major.minor). A mismatch can cause runtime errors.
2360
+
2361
+ Arguments:
2362
+ COMPUTE_NAME: Name of compute engine in computes.yml
2363
+
2364
+ Examples:
2365
+
2366
+ \b
2367
+ dvt spark match-cluster spark-docker
2368
+ dvt spark match-cluster spark-local
2369
+ dvt spark match-cluster databricks-prod
2370
+ """
2371
+ from dbt.task.spark import SparkTask
2372
+
2373
+ task = SparkTask()
2374
+ is_match = task.match_cluster(compute_name)
2375
+ ctx.exit(0 if is_match else 1)
2376
+
2377
+
2378
+ @spark.command("versions")
2379
+ @click.pass_context
2380
+ def spark_versions(ctx, **kwargs):
2381
+ """Display PySpark/Java compatibility matrix.
2382
+
2383
+ Shows all available PySpark versions with their Java requirements,
2384
+ marks the currently installed version, and shows your current
2385
+ Java installation.
2386
+ """
2387
+ from dbt.task.spark import SparkTask
2388
+
2389
+ task = SparkTask()
2390
+ task.show_versions()
2391
+ ctx.exit(0)
2392
+
2393
+
2394
+ # Register DVT command groups with main CLI
2395
+ cli.add_command(compute)
2396
+ cli.add_command(target)
2397
+ cli.add_command(java)
2398
+ cli.add_command(spark)
2399
+
2400
+
2401
+ # Support running as a module (python -m dbt.cli.main)
2402
+ if __name__ == "__main__":
2403
+ cli()