dvt-core 0.52.2__cp310-cp310-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 (275) 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 +2039 -0
  57. dbt/cli/option_types.py +121 -0
  58. dbt/cli/options.py +80 -0
  59. dbt/cli/params.py +804 -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.py +624 -0
  74. dbt/compute/federated_executor.py +837 -0
  75. dbt/compute/filter_pushdown.cpython-310-darwin.so +0 -0
  76. dbt/compute/filter_pushdown.py +273 -0
  77. dbt/compute/jar_provisioning.cpython-310-darwin.so +0 -0
  78. dbt/compute/jar_provisioning.py +255 -0
  79. dbt/compute/java_compat.cpython-310-darwin.so +0 -0
  80. dbt/compute/java_compat.py +689 -0
  81. dbt/compute/jdbc_utils.cpython-310-darwin.so +0 -0
  82. dbt/compute/jdbc_utils.py +678 -0
  83. dbt/compute/smart_selector.cpython-310-darwin.so +0 -0
  84. dbt/compute/smart_selector.py +311 -0
  85. dbt/compute/strategies/__init__.py +54 -0
  86. dbt/compute/strategies/base.py +165 -0
  87. dbt/compute/strategies/dataproc.py +207 -0
  88. dbt/compute/strategies/emr.py +203 -0
  89. dbt/compute/strategies/local.py +364 -0
  90. dbt/compute/strategies/standalone.py +262 -0
  91. dbt/config/__init__.py +4 -0
  92. dbt/config/catalogs.py +94 -0
  93. dbt/config/compute.cpython-310-darwin.so +0 -0
  94. dbt/config/compute.py +547 -0
  95. dbt/config/dvt_profile.cpython-310-darwin.so +0 -0
  96. dbt/config/dvt_profile.py +342 -0
  97. dbt/config/profile.py +422 -0
  98. dbt/config/project.py +873 -0
  99. dbt/config/project_utils.py +28 -0
  100. dbt/config/renderer.py +231 -0
  101. dbt/config/runtime.py +553 -0
  102. dbt/config/selectors.py +208 -0
  103. dbt/config/utils.py +77 -0
  104. dbt/constants.py +28 -0
  105. dbt/context/__init__.py +0 -0
  106. dbt/context/base.py +745 -0
  107. dbt/context/configured.py +135 -0
  108. dbt/context/context_config.py +382 -0
  109. dbt/context/docs.py +82 -0
  110. dbt/context/exceptions_jinja.py +178 -0
  111. dbt/context/macro_resolver.py +195 -0
  112. dbt/context/macros.py +171 -0
  113. dbt/context/manifest.py +72 -0
  114. dbt/context/providers.py +2249 -0
  115. dbt/context/query_header.py +13 -0
  116. dbt/context/secret.py +58 -0
  117. dbt/context/target.py +74 -0
  118. dbt/contracts/__init__.py +0 -0
  119. dbt/contracts/files.py +413 -0
  120. dbt/contracts/graph/__init__.py +0 -0
  121. dbt/contracts/graph/manifest.py +1904 -0
  122. dbt/contracts/graph/metrics.py +97 -0
  123. dbt/contracts/graph/model_config.py +70 -0
  124. dbt/contracts/graph/node_args.py +42 -0
  125. dbt/contracts/graph/nodes.py +1806 -0
  126. dbt/contracts/graph/semantic_manifest.py +232 -0
  127. dbt/contracts/graph/unparsed.py +811 -0
  128. dbt/contracts/project.py +417 -0
  129. dbt/contracts/results.py +53 -0
  130. dbt/contracts/selection.py +23 -0
  131. dbt/contracts/sql.py +85 -0
  132. dbt/contracts/state.py +68 -0
  133. dbt/contracts/util.py +46 -0
  134. dbt/deprecations.py +346 -0
  135. dbt/deps/__init__.py +0 -0
  136. dbt/deps/base.py +152 -0
  137. dbt/deps/git.py +195 -0
  138. dbt/deps/local.py +79 -0
  139. dbt/deps/registry.py +130 -0
  140. dbt/deps/resolver.py +149 -0
  141. dbt/deps/tarball.py +120 -0
  142. dbt/docs/source/_ext/dbt_click.py +119 -0
  143. dbt/docs/source/conf.py +32 -0
  144. dbt/env_vars.py +64 -0
  145. dbt/event_time/event_time.py +40 -0
  146. dbt/event_time/sample_window.py +60 -0
  147. dbt/events/__init__.py +15 -0
  148. dbt/events/base_types.py +36 -0
  149. dbt/events/core_types_pb2.py +2 -0
  150. dbt/events/logging.py +108 -0
  151. dbt/events/types.py +2516 -0
  152. dbt/exceptions.py +1486 -0
  153. dbt/flags.py +89 -0
  154. dbt/graph/__init__.py +11 -0
  155. dbt/graph/cli.py +247 -0
  156. dbt/graph/graph.py +172 -0
  157. dbt/graph/queue.py +214 -0
  158. dbt/graph/selector.py +374 -0
  159. dbt/graph/selector_methods.py +975 -0
  160. dbt/graph/selector_spec.py +222 -0
  161. dbt/graph/thread_pool.py +18 -0
  162. dbt/hooks.py +21 -0
  163. dbt/include/README.md +49 -0
  164. dbt/include/__init__.py +3 -0
  165. dbt/include/starter_project/.gitignore +4 -0
  166. dbt/include/starter_project/README.md +15 -0
  167. dbt/include/starter_project/__init__.py +3 -0
  168. dbt/include/starter_project/analyses/.gitkeep +0 -0
  169. dbt/include/starter_project/dbt_project.yml +36 -0
  170. dbt/include/starter_project/macros/.gitkeep +0 -0
  171. dbt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  172. dbt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  173. dbt/include/starter_project/models/example/schema.yml +21 -0
  174. dbt/include/starter_project/seeds/.gitkeep +0 -0
  175. dbt/include/starter_project/snapshots/.gitkeep +0 -0
  176. dbt/include/starter_project/tests/.gitkeep +0 -0
  177. dbt/internal_deprecations.py +26 -0
  178. dbt/jsonschemas/__init__.py +3 -0
  179. dbt/jsonschemas/jsonschemas.py +309 -0
  180. dbt/jsonschemas/project/0.0.110.json +4717 -0
  181. dbt/jsonschemas/project/0.0.85.json +2015 -0
  182. dbt/jsonschemas/resources/0.0.110.json +2636 -0
  183. dbt/jsonschemas/resources/0.0.85.json +2536 -0
  184. dbt/jsonschemas/resources/latest.json +6773 -0
  185. dbt/links.py +4 -0
  186. dbt/materializations/__init__.py +0 -0
  187. dbt/materializations/incremental/__init__.py +0 -0
  188. dbt/materializations/incremental/microbatch.py +236 -0
  189. dbt/mp_context.py +8 -0
  190. dbt/node_types.py +37 -0
  191. dbt/parser/__init__.py +23 -0
  192. dbt/parser/analysis.py +21 -0
  193. dbt/parser/base.py +548 -0
  194. dbt/parser/common.py +266 -0
  195. dbt/parser/docs.py +52 -0
  196. dbt/parser/fixtures.py +51 -0
  197. dbt/parser/functions.py +30 -0
  198. dbt/parser/generic_test.py +100 -0
  199. dbt/parser/generic_test_builders.py +333 -0
  200. dbt/parser/hooks.py +118 -0
  201. dbt/parser/macros.py +137 -0
  202. dbt/parser/manifest.py +2204 -0
  203. dbt/parser/models.py +573 -0
  204. dbt/parser/partial.py +1178 -0
  205. dbt/parser/read_files.py +445 -0
  206. dbt/parser/schema_generic_tests.py +422 -0
  207. dbt/parser/schema_renderer.py +111 -0
  208. dbt/parser/schema_yaml_readers.py +935 -0
  209. dbt/parser/schemas.py +1466 -0
  210. dbt/parser/search.py +149 -0
  211. dbt/parser/seeds.py +28 -0
  212. dbt/parser/singular_test.py +20 -0
  213. dbt/parser/snapshots.py +44 -0
  214. dbt/parser/sources.py +558 -0
  215. dbt/parser/sql.py +62 -0
  216. dbt/parser/unit_tests.py +621 -0
  217. dbt/plugins/__init__.py +20 -0
  218. dbt/plugins/contracts.py +9 -0
  219. dbt/plugins/exceptions.py +2 -0
  220. dbt/plugins/manager.py +163 -0
  221. dbt/plugins/manifest.py +21 -0
  222. dbt/profiler.py +20 -0
  223. dbt/py.typed +1 -0
  224. dbt/query_analyzer.cpython-310-darwin.so +0 -0
  225. dbt/query_analyzer.py +410 -0
  226. dbt/runners/__init__.py +2 -0
  227. dbt/runners/exposure_runner.py +7 -0
  228. dbt/runners/no_op_runner.py +45 -0
  229. dbt/runners/saved_query_runner.py +7 -0
  230. dbt/selected_resources.py +8 -0
  231. dbt/task/__init__.py +0 -0
  232. dbt/task/base.py +503 -0
  233. dbt/task/build.py +197 -0
  234. dbt/task/clean.py +56 -0
  235. dbt/task/clone.py +161 -0
  236. dbt/task/compile.py +150 -0
  237. dbt/task/compute.py +454 -0
  238. dbt/task/debug.py +505 -0
  239. dbt/task/deps.py +280 -0
  240. dbt/task/docs/__init__.py +3 -0
  241. dbt/task/docs/generate.py +660 -0
  242. dbt/task/docs/index.html +250 -0
  243. dbt/task/docs/serve.py +29 -0
  244. dbt/task/freshness.py +322 -0
  245. dbt/task/function.py +121 -0
  246. dbt/task/group_lookup.py +46 -0
  247. dbt/task/init.py +553 -0
  248. dbt/task/java.py +316 -0
  249. dbt/task/list.py +236 -0
  250. dbt/task/printer.py +175 -0
  251. dbt/task/retry.py +175 -0
  252. dbt/task/run.py +1306 -0
  253. dbt/task/run_operation.py +141 -0
  254. dbt/task/runnable.py +758 -0
  255. dbt/task/seed.py +103 -0
  256. dbt/task/show.py +149 -0
  257. dbt/task/snapshot.py +56 -0
  258. dbt/task/spark.py +414 -0
  259. dbt/task/sql.py +110 -0
  260. dbt/task/target_sync.py +759 -0
  261. dbt/task/test.py +464 -0
  262. dbt/tests/fixtures/__init__.py +1 -0
  263. dbt/tests/fixtures/project.py +620 -0
  264. dbt/tests/util.py +651 -0
  265. dbt/tracking.py +529 -0
  266. dbt/utils/__init__.py +3 -0
  267. dbt/utils/artifact_upload.py +151 -0
  268. dbt/utils/utils.py +408 -0
  269. dbt/version.py +268 -0
  270. dvt_cli/__init__.py +72 -0
  271. dvt_core-0.52.2.dist-info/METADATA +286 -0
  272. dvt_core-0.52.2.dist-info/RECORD +275 -0
  273. dvt_core-0.52.2.dist-info/WHEEL +5 -0
  274. dvt_core-0.52.2.dist-info/entry_points.txt +2 -0
  275. dvt_core-0.52.2.dist-info/top_level.txt +2 -0
dbt/task/init.py ADDED
@@ -0,0 +1,553 @@
1
+ import copy
2
+ import os
3
+ import re
4
+ import shutil
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import click
9
+ import yaml
10
+
11
+ import dbt.config
12
+ import dbt_common.clients.system
13
+ from dbt.adapters.factory import get_include_paths, load_plugin
14
+ from dbt.config.profile import read_profile
15
+ from dbt.contracts.util import Identifier as ProjectName
16
+ from dbt.events.types import (
17
+ ConfigFolderDirectory,
18
+ InvalidProfileTemplateYAML,
19
+ NoSampleProfileFound,
20
+ ProfileWrittenWithProjectTemplateYAML,
21
+ ProfileWrittenWithSample,
22
+ ProfileWrittenWithTargetTemplateYAML,
23
+ ProjectCreated,
24
+ ProjectNameAlreadyExists,
25
+ SettingUpProfile,
26
+ StarterProjectPath,
27
+ )
28
+ from dbt.flags import get_flags
29
+ from dbt.task.base import BaseTask, move_to_nearest_project_dir
30
+ from dbt.version import _get_adapter_plugin_names
31
+ from dbt_common.events.functions import fire_event
32
+ from dbt_common.exceptions import DbtRuntimeError
33
+
34
+ DOCS_URL = "https://docs.getdbt.com/docs/configure-your-profile"
35
+ SLACK_URL = "https://community.getdbt.com/"
36
+
37
+ # This file is not needed for the starter project but exists for finding the resource path
38
+ IGNORE_FILES = ["__init__.py", "__pycache__"]
39
+
40
+
41
+ # https://click.palletsprojects.com/en/8.0.x/api/#types
42
+ # click v7.0 has UNPROCESSED, STRING, INT, FLOAT, BOOL, and UUID available.
43
+ click_type_mapping = {
44
+ "string": click.STRING,
45
+ "int": click.INT,
46
+ "float": click.FLOAT,
47
+ "bool": click.BOOL,
48
+ None: None,
49
+ }
50
+
51
+
52
+ class InitTask(BaseTask):
53
+ def copy_starter_repo(self, project_name: str) -> None:
54
+ # Lazy import to avoid ModuleNotFoundError
55
+ from dbt.include.starter_project import (
56
+ PACKAGE_PATH as starter_project_directory,
57
+ )
58
+
59
+ fire_event(StarterProjectPath(dir=starter_project_directory))
60
+ shutil.copytree(
61
+ starter_project_directory, project_name, ignore=shutil.ignore_patterns(*IGNORE_FILES)
62
+ )
63
+
64
+ def create_profiles_dir(self, profiles_dir: str) -> bool:
65
+ """Create the user's profiles directory if it doesn't already exist."""
66
+ profiles_path = Path(profiles_dir)
67
+ if not profiles_path.exists():
68
+ fire_event(ConfigFolderDirectory(dir=str(profiles_dir)))
69
+ dbt_common.clients.system.make_directory(profiles_dir)
70
+ return True
71
+ return False
72
+
73
+ def create_profile_from_sample(self, adapter: str, profile_name: str):
74
+ """Create a profile entry using the adapter's sample_profiles.yml
75
+
76
+ Renames the profile in sample_profiles.yml to match that of the project."""
77
+ # Line below raises an exception if the specified adapter is not found
78
+ load_plugin(adapter)
79
+ adapter_path = get_include_paths(adapter)[0]
80
+ sample_profiles_path = adapter_path / "sample_profiles.yml"
81
+
82
+ if not sample_profiles_path.exists():
83
+ fire_event(NoSampleProfileFound(adapter=adapter))
84
+ else:
85
+ with open(sample_profiles_path, "r") as f:
86
+ sample_profile = f.read()
87
+ sample_profile_name = list(yaml.safe_load(sample_profile).keys())[0]
88
+ # Use a regex to replace the name of the sample_profile with
89
+ # that of the project without losing any comments from the sample
90
+ sample_profile = re.sub(f"^{sample_profile_name}:", f"{profile_name}:", sample_profile)
91
+ profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
92
+ if profiles_filepath.exists():
93
+ with open(profiles_filepath, "a") as f:
94
+ f.write("\n" + sample_profile)
95
+ else:
96
+ with open(profiles_filepath, "w") as f:
97
+ f.write(sample_profile)
98
+ fire_event(
99
+ ProfileWrittenWithSample(name=profile_name, path=str(profiles_filepath))
100
+ )
101
+
102
+ def generate_target_from_input(self, profile_template: dict, target: dict = {}) -> dict:
103
+ """Generate a target configuration from profile_template and user input."""
104
+ profile_template_local = copy.deepcopy(profile_template)
105
+ for key, value in profile_template_local.items():
106
+ if key.startswith("_choose"):
107
+ choice_type = key[8:].replace("_", " ")
108
+ option_list = list(value.keys())
109
+ prompt_msg = (
110
+ "\n".join([f"[{n + 1}] {v}" for n, v in enumerate(option_list)])
111
+ + f"\nDesired {choice_type} option (enter a number)"
112
+ )
113
+ numeric_choice = click.prompt(prompt_msg, type=click.INT)
114
+ choice = option_list[numeric_choice - 1]
115
+ # Complete the chosen option's values in a recursive call
116
+ target = self.generate_target_from_input(
117
+ profile_template_local[key][choice], target
118
+ )
119
+ else:
120
+ if key.startswith("_fixed"):
121
+ # _fixed prefixed keys are not presented to the user
122
+ target[key[7:]] = value
123
+ else:
124
+ hide_input = value.get("hide_input", False)
125
+ default = value.get("default", None)
126
+ hint = value.get("hint", None)
127
+ type = click_type_mapping[value.get("type", None)]
128
+ text = key + (f" ({hint})" if hint else "")
129
+ target[key] = click.prompt(
130
+ text, default=default, hide_input=hide_input, type=type
131
+ )
132
+ return target
133
+
134
+ def get_profile_name_from_current_project(self) -> str:
135
+ """Reads dbt_project.yml in the current directory to retrieve the
136
+ profile name.
137
+ """
138
+ with open("dbt_project.yml") as f:
139
+ dbt_project = yaml.safe_load(f)
140
+ return dbt_project["profile"]
141
+
142
+ def write_profile(self, profile: dict, profile_name: str):
143
+ """Given a profile, write it to the current project's profiles.yml.
144
+ This will overwrite any profile with a matching name."""
145
+ # Create the profile directory if it doesn't exist
146
+ profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
147
+
148
+ profiles = {profile_name: profile}
149
+
150
+ if profiles_filepath.exists():
151
+ with open(profiles_filepath, "r") as f:
152
+ profiles = yaml.safe_load(f) or {}
153
+ profiles[profile_name] = profile
154
+
155
+ # Write the profiles dictionary to a brand-new or pre-existing file
156
+ with open(profiles_filepath, "w") as f:
157
+ yaml.dump(profiles, f)
158
+
159
+ def create_profile_from_profile_template(self, profile_template: dict, profile_name: str):
160
+ """Create and write a profile using the supplied profile_template."""
161
+ initial_target = profile_template.get("fixed", {})
162
+ prompts = profile_template.get("prompts", {})
163
+ target = self.generate_target_from_input(prompts, initial_target)
164
+ target_name = target.pop("target", "dev")
165
+ profile = {"outputs": {target_name: target}, "target": target_name}
166
+ self.write_profile(profile, profile_name)
167
+
168
+ def create_profile_from_target(self, adapter: str, profile_name: str):
169
+ """Create a profile without defaults using target's profile_template.yml if available, or
170
+ sample_profiles.yml as a fallback."""
171
+ # Line below raises an exception if the specified adapter is not found
172
+ load_plugin(adapter)
173
+ adapter_path = get_include_paths(adapter)[0]
174
+ profile_template_path = adapter_path / "profile_template.yml"
175
+
176
+ if profile_template_path.exists():
177
+ with open(profile_template_path) as f:
178
+ profile_template = yaml.safe_load(f)
179
+ self.create_profile_from_profile_template(profile_template, profile_name)
180
+ profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
181
+ fire_event(
182
+ ProfileWrittenWithTargetTemplateYAML(
183
+ name=profile_name, path=str(profiles_filepath)
184
+ )
185
+ )
186
+ else:
187
+ # For adapters without a profile_template.yml defined, fallback on
188
+ # sample_profiles.yml
189
+ self.create_profile_from_sample(adapter, profile_name)
190
+
191
+ def check_if_profile_exists(self, profile_name: str) -> bool:
192
+ """
193
+ Validate that the specified profile exists. Can't use the regular profile validation
194
+ routine because it assumes the project file exists
195
+ """
196
+ profiles_dir = get_flags().PROFILES_DIR
197
+ raw_profiles = read_profile(profiles_dir)
198
+ return profile_name in raw_profiles
199
+
200
+ def check_if_can_write_profile(self, profile_name: Optional[str] = None) -> bool:
201
+ """Using either a provided profile name or that specified in dbt_project.yml,
202
+ check if the profile already exists in profiles.yml, and if so ask the
203
+ user whether to proceed and overwrite it."""
204
+ profiles_file = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
205
+ if not profiles_file.exists():
206
+ return True
207
+ profile_name = profile_name or self.get_profile_name_from_current_project()
208
+ with open(profiles_file, "r") as f:
209
+ profiles = yaml.safe_load(f) or {}
210
+ if profile_name in profiles.keys():
211
+ # Profile already exists, just skip profile setup
212
+ click.echo(f"Profile '{profile_name}' already exists in {profiles_file}, skipping profile setup.")
213
+ return False
214
+ else:
215
+ return True
216
+
217
+ def create_profile_using_project_profile_template(self, profile_name):
218
+ """Create a profile using the project's profile_template.yml"""
219
+ with open("profile_template.yml") as f:
220
+ profile_template = yaml.safe_load(f)
221
+ self.create_profile_from_profile_template(profile_template, profile_name)
222
+ profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
223
+ fire_event(
224
+ ProfileWrittenWithProjectTemplateYAML(name=profile_name, path=str(profiles_filepath))
225
+ )
226
+
227
+ def ask_for_adapter_choice(self) -> str:
228
+ """Ask the user which adapter (database) they'd like to use."""
229
+ available_adapters = list(_get_adapter_plugin_names())
230
+
231
+ if not available_adapters:
232
+ raise dbt.exceptions.NoAdaptersAvailableError()
233
+
234
+ prompt_msg = (
235
+ "Which database would you like to use?\n"
236
+ + "\n".join([f"[{n + 1}] {v}" for n, v in enumerate(available_adapters)])
237
+ + "\n\n(Don't see the one you want? https://docs.getdbt.com/docs/available-adapters)"
238
+ + "\n\nEnter a number"
239
+ )
240
+ numeric_choice = click.prompt(prompt_msg, type=click.INT)
241
+ return available_adapters[numeric_choice - 1]
242
+
243
+ def setup_profile(self, profile_name: str) -> None:
244
+ """Set up a new profile for a project"""
245
+ fire_event(SettingUpProfile())
246
+ if not self.check_if_can_write_profile(profile_name=profile_name):
247
+ return
248
+ # If a profile_template.yml exists in the project root, that effectively
249
+ # overrides the profile_template.yml for the given target.
250
+ profile_template_path = Path("profile_template.yml")
251
+ if profile_template_path.exists():
252
+ try:
253
+ # This relies on a valid profile_template.yml from the user,
254
+ # so use a try: except to fall back to the default on failure
255
+ self.create_profile_using_project_profile_template(profile_name)
256
+ return
257
+ except Exception:
258
+ fire_event(InvalidProfileTemplateYAML())
259
+ adapter = self.ask_for_adapter_choice()
260
+ self.create_profile_from_target(adapter, profile_name=profile_name)
261
+
262
+ def get_adapter_metadata(self) -> dict:
263
+ """Get categorized adapter information with descriptions - COMPREHENSIVE."""
264
+ return {
265
+ "Cloud Data Warehouses": {
266
+ "snowflake": {"name": "Snowflake", "desc": "Cloud data warehouse", "jdbc": True},
267
+ "bigquery": {"name": "Google BigQuery", "desc": "Serverless warehouse", "jdbc": True},
268
+ "databricks": {"name": "Databricks", "desc": "Lakehouse platform", "jdbc": True},
269
+ "redshift": {"name": "Amazon Redshift", "desc": "AWS warehouse", "jdbc": True},
270
+ "firebolt": {"name": "Firebolt", "desc": "Cloud DW for analytics", "jdbc": True},
271
+ },
272
+ "Microsoft Ecosystem": {
273
+ "fabric": {"name": "Microsoft Fabric", "desc": "Unified analytics", "jdbc": True},
274
+ "synapse": {"name": "Azure Synapse", "desc": "Analytics service", "jdbc": True},
275
+ "sqlserver": {"name": "SQL Server", "desc": "Enterprise RDBMS", "jdbc": True},
276
+ },
277
+ "Enterprise Data Warehouses": {
278
+ "teradata": {"name": "Teradata", "desc": "Enterprise warehouse", "jdbc": True},
279
+ "oracle": {"name": "Oracle Database", "desc": "Enterprise RDBMS", "jdbc": True},
280
+ "db2": {"name": "IBM DB2", "desc": "IBM database system", "jdbc": True},
281
+ "exasol": {"name": "Exasol", "desc": "In-memory analytics", "jdbc": True},
282
+ "vertica": {"name": "Vertica", "desc": "Columnar analytics", "jdbc": True},
283
+ },
284
+ "SQL Engines & Query Platforms": {
285
+ "spark": {"name": "Apache Spark", "desc": "Unified analytics", "jdbc": True},
286
+ "trino": {"name": "Trino", "desc": "Distributed SQL engine", "jdbc": True},
287
+ "presto": {"name": "Presto", "desc": "Meta's query engine", "jdbc": True},
288
+ "athena": {"name": "Amazon Athena", "desc": "Query S3 data", "jdbc": True},
289
+ "dremio": {"name": "Dremio", "desc": "Data lakehouse platform", "jdbc": True},
290
+ "hive": {"name": "Apache Hive", "desc": "Hadoop data warehouse", "jdbc": True},
291
+ "impala": {"name": "Cloudera Impala", "desc": "MPP SQL engine", "jdbc": True},
292
+ "glue": {"name": "AWS Glue", "desc": "Serverless ETL", "jdbc": True},
293
+ },
294
+ "Open Source Databases": {
295
+ "postgres": {"name": "PostgreSQL", "desc": "Popular open-source DB", "jdbc": True},
296
+ "mysql": {"name": "MySQL", "desc": "World's most popular DB", "jdbc": True},
297
+ "mariadb": {"name": "MariaDB", "desc": "MySQL fork", "jdbc": True},
298
+ "sqlite": {"name": "SQLite", "desc": "Embedded database", "jdbc": False},
299
+ "duckdb": {"name": "DuckDB", "desc": "In-process OLAP", "jdbc": False},
300
+ "cratedb": {"name": "CrateDB", "desc": "Distributed SQL", "jdbc": True},
301
+ },
302
+ "OLAP & Analytics Databases": {
303
+ "clickhouse": {"name": "ClickHouse", "desc": "Fast OLAP database", "jdbc": True},
304
+ "starrocks": {"name": "StarRocks", "desc": "MPP analytics", "jdbc": True},
305
+ "doris": {"name": "Apache Doris", "desc": "Real-time analytics", "jdbc": True},
306
+ "greenplum": {"name": "Greenplum", "desc": "MPP database", "jdbc": True},
307
+ "monetdb": {"name": "MonetDB", "desc": "Columnar database", "jdbc": True},
308
+ },
309
+ "Time-Series & Streaming": {
310
+ "timescaledb": {"name": "TimescaleDB", "desc": "PostgreSQL for time-series", "jdbc": True},
311
+ "questdb": {"name": "QuestDB", "desc": "Fast time-series", "jdbc": True},
312
+ "materialize": {"name": "Materialize", "desc": "Streaming SQL", "jdbc": True},
313
+ "rockset": {"name": "Rockset", "desc": "Real-time analytics", "jdbc": True},
314
+ },
315
+ "Data Lakes & Modern Formats": {
316
+ "iceberg": {"name": "Apache Iceberg", "desc": "Table format", "jdbc": True},
317
+ },
318
+ "Specialized & Emerging": {
319
+ "singlestore": {"name": "SingleStore", "desc": "Real-time analytics", "jdbc": True},
320
+ "neo4j": {"name": "Neo4j", "desc": "Graph database", "jdbc": True},
321
+ "mindsdb": {"name": "MindsDB", "desc": "ML database", "jdbc": True},
322
+ },
323
+ }
324
+
325
+ def ask_for_adapter_choice_enhanced(self, prompt_prefix: str = "") -> str:
326
+ """Enhanced adapter selection with categories."""
327
+ metadata = self.get_adapter_metadata()
328
+ menu_lines = []
329
+ adapter_list = []
330
+ counter = 1
331
+
332
+ for category, adapters in metadata.items():
333
+ menu_lines.append(f"\n{category}:")
334
+ for adapter_key, info in adapters.items():
335
+ jdbc = " [JDBC]" if info["jdbc"] else ""
336
+ menu_lines.append(f" [{counter}] {info['name']}{jdbc} - {info['desc']}")
337
+ adapter_list.append(adapter_key)
338
+ counter += 1
339
+
340
+ prompt_msg = (
341
+ f"{prompt_prefix}" + "\n".join(menu_lines) +
342
+ "\n\nAll adapters support Spark JDBC federation\nEnter a number"
343
+ )
344
+
345
+ numeric_choice = click.prompt(prompt_msg, type=click.INT)
346
+ return adapter_list[numeric_choice - 1]
347
+
348
+ def ask_for_multi_connection_setup(self) -> bool:
349
+ """Ask if user wants multi-connection setup."""
350
+ msg = (
351
+ "\nDVT supports multi-source data federation.\n"
352
+ "Set up multiple database connections?\n"
353
+ " [1] Yes - Multiple connections (recommended for federation)\n"
354
+ " [2] No - Single connection (can add more later)\n"
355
+ "\nEnter a number"
356
+ )
357
+ choice = click.prompt(msg, type=click.INT, default=1)
358
+ return choice == 1
359
+
360
+ def setup_multi_connection_profile(self, profile_name: str) -> int:
361
+ """
362
+ Set up profile with multiple connections interactively.
363
+ Returns number of connections configured.
364
+ """
365
+ fire_event(SettingUpProfile())
366
+
367
+ if not self.check_if_can_write_profile(profile_name=profile_name):
368
+ return 0
369
+
370
+ # Ask how many
371
+ num = click.prompt(
372
+ "\nHow many database connections? (1-10)",
373
+ type=click.INT,
374
+ default=2
375
+ )
376
+ num = max(1, min(10, num))
377
+
378
+ outputs = {}
379
+
380
+ for i in range(num):
381
+ click.echo(f"\n--- Connection {i+1}/{num} ---")
382
+
383
+ adapter = self.ask_for_adapter_choice_enhanced(
384
+ prompt_prefix=f"\nSelect database type for connection {i+1}:\n"
385
+ )
386
+
387
+ default_name = f"{adapter}_{i+1}" if i > 0 else adapter
388
+ conn_name = click.prompt(
389
+ f"Connection name",
390
+ default=default_name
391
+ )
392
+
393
+ # Use adapter's profile template for prompts
394
+ load_plugin(adapter)
395
+ adapter_path = get_include_paths(adapter)[0]
396
+ template_path = adapter_path / "profile_template.yml"
397
+
398
+ if template_path.exists():
399
+ with open(template_path) as f:
400
+ template = yaml.safe_load(f)
401
+ prompts = template.get("prompts", {})
402
+ fixed = template.get("fixed", {})
403
+ target_config = self.generate_target_from_input(prompts, fixed)
404
+ target_config.pop("target", None)
405
+ outputs[conn_name] = target_config
406
+ else:
407
+ outputs[conn_name] = {"type": adapter}
408
+
409
+ # Set default target
410
+ output_names = list(outputs.keys())
411
+ default_target = output_names[0]
412
+
413
+ if len(output_names) > 1:
414
+ click.echo("\nAvailable connections:")
415
+ for idx, name in enumerate(output_names):
416
+ click.echo(f" [{idx+1}] {name}")
417
+
418
+ default_choice = click.prompt(
419
+ "\nDefault target? (enter number)",
420
+ type=click.INT,
421
+ default=1
422
+ )
423
+ default_target = output_names[max(0, min(default_choice - 1, len(output_names) - 1))]
424
+
425
+ # Write profile
426
+ profile = {"outputs": outputs, "target": default_target}
427
+ self.write_profile(profile, profile_name)
428
+
429
+ profiles_path = Path(get_flags().PROFILES_DIR) / "profiles.yml"
430
+ fire_event(ProfileWrittenWithTargetTemplateYAML(name=profile_name, path=str(profiles_path)))
431
+
432
+ return len(outputs)
433
+
434
+ def show_next_steps(self, project_name: str, num_connections: int) -> None:
435
+ """Show helpful next steps."""
436
+ click.echo("\n" + "=" * 60)
437
+ click.echo("šŸŽ‰ DVT project initialized successfully!")
438
+ click.echo("=" * 60)
439
+ click.echo(f"\nProject: {project_name}")
440
+ click.echo(f"Connections: {num_connections} configured")
441
+ click.echo("\nšŸš€ Next Steps:")
442
+ click.echo(f" 1. cd {project_name}")
443
+ click.echo(" 2. Edit models/example/my_first_dbt_model.sql")
444
+ click.echo(" 3. dvt run")
445
+ click.echo(" 4. dvt test")
446
+ click.echo("\nšŸ”— Useful Commands:")
447
+ click.echo(" dvt target list # List connections")
448
+ click.echo(" dvt target test-all # Test all connections")
449
+ click.echo(" dvt compute list # Show Spark config")
450
+ click.echo(" dvt --help # See all commands")
451
+ click.echo("\n" + "=" * 60)
452
+
453
+ def get_valid_project_name(self) -> str:
454
+ """Returns a valid project name, either from CLI arg or user prompt."""
455
+
456
+ # Lazy import to avoid ModuleNotFoundError
457
+ from dbt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
458
+
459
+ name = self.args.project_name
460
+ internal_package_names = {GLOBAL_PROJECT_NAME}
461
+ available_adapters = list(_get_adapter_plugin_names())
462
+ for adapter_name in available_adapters:
463
+ internal_package_names.update(f"dbt_{adapter_name}")
464
+ while not ProjectName.is_valid(name) or name in internal_package_names:
465
+ if name:
466
+ click.echo(name + " is not a valid project name.")
467
+ name = click.prompt("Enter a name for your project (letters, digits, underscore)")
468
+
469
+ return name
470
+
471
+ def create_new_project(self, project_name: str, profile_name: str):
472
+ self.copy_starter_repo(project_name)
473
+ os.chdir(project_name)
474
+ with open("dbt_project.yml", "r") as f:
475
+ content = f"{f.read()}".format(project_name=project_name, profile_name=profile_name)
476
+ with open("dbt_project.yml", "w") as f:
477
+ f.write(content)
478
+
479
+ # Create project-level .dvt/jdbc_jars/ directory
480
+ from dbt.config.compute import ComputeRegistry
481
+ ComputeRegistry.ensure_jdbc_jars_dir(".")
482
+
483
+ fire_event(
484
+ ProjectCreated(
485
+ project_name=project_name,
486
+ docs_url=DOCS_URL,
487
+ slack_url=SLACK_URL,
488
+ )
489
+ )
490
+
491
+ def run(self):
492
+ """Entry point for the init task."""
493
+ profiles_dir = get_flags().PROFILES_DIR
494
+ self.create_profiles_dir(profiles_dir)
495
+
496
+ try:
497
+ move_to_nearest_project_dir(self.args.project_dir)
498
+ in_project = True
499
+ except dbt_common.exceptions.DbtRuntimeError:
500
+ in_project = False
501
+
502
+ if in_project:
503
+ # If --profile was specified, it means use an existing profile, which is not
504
+ # applicable to this case
505
+ if self.args.profile:
506
+ raise DbtRuntimeError(
507
+ msg="Can not init existing project with specified profile, edit dbt_project.yml instead"
508
+ )
509
+
510
+ # Ensure project-level .dvt/jdbc_jars/ directory exists
511
+ from dbt.config.compute import ComputeRegistry
512
+ ComputeRegistry.ensure_jdbc_jars_dir(".")
513
+
514
+ # When dbt init is run inside an existing project,
515
+ # just setup the user's profile.
516
+ if not self.args.skip_profile_setup:
517
+ # Get profile name from dbt_project.yml
518
+ profile_name = self.get_profile_name_from_current_project()
519
+ self.setup_profile(profile_name)
520
+ else:
521
+ # When dbt init is run outside of an existing project,
522
+ # create a new project and set up the user's profile.
523
+ project_name = self.get_valid_project_name()
524
+ project_path = Path(project_name)
525
+ if project_path.exists():
526
+ fire_event(ProjectNameAlreadyExists(name=project_name))
527
+ return
528
+
529
+ # If the user specified an existing profile to use, use it instead of generating a new one
530
+ user_profile_name = self.args.profile
531
+ if user_profile_name:
532
+ if not self.check_if_profile_exists(user_profile_name):
533
+ raise DbtRuntimeError(
534
+ msg="Could not find profile named '{}'".format(user_profile_name)
535
+ )
536
+ self.create_new_project(project_name, user_profile_name)
537
+ self.show_next_steps(project_name, 1)
538
+ else:
539
+ profile_name = project_name
540
+ # Create the profile after creating the project to avoid leaving a random profile
541
+ # if the former fails.
542
+ self.create_new_project(project_name, profile_name)
543
+
544
+ # DVT v0.5.1: Enhanced multi-connection init wizard
545
+ if not self.args.skip_profile_setup:
546
+ # Ask about multi-connection setup
547
+ if self.ask_for_multi_connection_setup():
548
+ num_conn = self.setup_multi_connection_profile(profile_name)
549
+ else:
550
+ self.setup_profile(profile_name)
551
+ num_conn = 1
552
+
553
+ self.show_next_steps(project_name, num_conn)