dvt-core 1.11.0b4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dvt-core might be problematic. Click here for more details.

Files changed (261) hide show
  1. dvt/__init__.py +7 -0
  2. dvt/_pydantic_shim.py +26 -0
  3. dvt/adapters/__init__.py +16 -0
  4. dvt/adapters/multi_adapter_manager.py +268 -0
  5. dvt/artifacts/__init__.py +0 -0
  6. dvt/artifacts/exceptions/__init__.py +1 -0
  7. dvt/artifacts/exceptions/schemas.py +31 -0
  8. dvt/artifacts/resources/__init__.py +116 -0
  9. dvt/artifacts/resources/base.py +68 -0
  10. dvt/artifacts/resources/types.py +93 -0
  11. dvt/artifacts/resources/v1/analysis.py +10 -0
  12. dvt/artifacts/resources/v1/catalog.py +23 -0
  13. dvt/artifacts/resources/v1/components.py +275 -0
  14. dvt/artifacts/resources/v1/config.py +282 -0
  15. dvt/artifacts/resources/v1/documentation.py +11 -0
  16. dvt/artifacts/resources/v1/exposure.py +52 -0
  17. dvt/artifacts/resources/v1/function.py +53 -0
  18. dvt/artifacts/resources/v1/generic_test.py +32 -0
  19. dvt/artifacts/resources/v1/group.py +22 -0
  20. dvt/artifacts/resources/v1/hook.py +11 -0
  21. dvt/artifacts/resources/v1/macro.py +30 -0
  22. dvt/artifacts/resources/v1/metric.py +173 -0
  23. dvt/artifacts/resources/v1/model.py +146 -0
  24. dvt/artifacts/resources/v1/owner.py +10 -0
  25. dvt/artifacts/resources/v1/saved_query.py +112 -0
  26. dvt/artifacts/resources/v1/seed.py +42 -0
  27. dvt/artifacts/resources/v1/semantic_layer_components.py +72 -0
  28. dvt/artifacts/resources/v1/semantic_model.py +315 -0
  29. dvt/artifacts/resources/v1/singular_test.py +14 -0
  30. dvt/artifacts/resources/v1/snapshot.py +92 -0
  31. dvt/artifacts/resources/v1/source_definition.py +85 -0
  32. dvt/artifacts/resources/v1/sql_operation.py +10 -0
  33. dvt/artifacts/resources/v1/unit_test_definition.py +78 -0
  34. dvt/artifacts/schemas/__init__.py +0 -0
  35. dvt/artifacts/schemas/base.py +191 -0
  36. dvt/artifacts/schemas/batch_results.py +24 -0
  37. dvt/artifacts/schemas/catalog/__init__.py +12 -0
  38. dvt/artifacts/schemas/catalog/v1/__init__.py +0 -0
  39. dvt/artifacts/schemas/catalog/v1/catalog.py +60 -0
  40. dvt/artifacts/schemas/freshness/__init__.py +1 -0
  41. dvt/artifacts/schemas/freshness/v3/__init__.py +0 -0
  42. dvt/artifacts/schemas/freshness/v3/freshness.py +159 -0
  43. dvt/artifacts/schemas/manifest/__init__.py +2 -0
  44. dvt/artifacts/schemas/manifest/v12/__init__.py +0 -0
  45. dvt/artifacts/schemas/manifest/v12/manifest.py +212 -0
  46. dvt/artifacts/schemas/results.py +148 -0
  47. dvt/artifacts/schemas/run/__init__.py +2 -0
  48. dvt/artifacts/schemas/run/v5/__init__.py +0 -0
  49. dvt/artifacts/schemas/run/v5/run.py +184 -0
  50. dvt/artifacts/schemas/upgrades/__init__.py +4 -0
  51. dvt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
  52. dvt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
  53. dvt/artifacts/utils/validation.py +153 -0
  54. dvt/cli/__init__.py +1 -0
  55. dvt/cli/context.py +16 -0
  56. dvt/cli/exceptions.py +56 -0
  57. dvt/cli/flags.py +558 -0
  58. dvt/cli/main.py +971 -0
  59. dvt/cli/option_types.py +121 -0
  60. dvt/cli/options.py +79 -0
  61. dvt/cli/params.py +803 -0
  62. dvt/cli/requires.py +478 -0
  63. dvt/cli/resolvers.py +32 -0
  64. dvt/cli/types.py +40 -0
  65. dvt/clients/__init__.py +0 -0
  66. dvt/clients/checked_load.py +82 -0
  67. dvt/clients/git.py +164 -0
  68. dvt/clients/jinja.py +206 -0
  69. dvt/clients/jinja_static.py +245 -0
  70. dvt/clients/registry.py +192 -0
  71. dvt/clients/yaml_helper.py +68 -0
  72. dvt/compilation.py +833 -0
  73. dvt/compute/__init__.py +26 -0
  74. dvt/compute/base.py +288 -0
  75. dvt/compute/engines/__init__.py +13 -0
  76. dvt/compute/engines/duckdb_engine.py +368 -0
  77. dvt/compute/engines/spark_engine.py +273 -0
  78. dvt/compute/query_analyzer.py +212 -0
  79. dvt/compute/router.py +483 -0
  80. dvt/config/__init__.py +4 -0
  81. dvt/config/catalogs.py +95 -0
  82. dvt/config/compute_config.py +406 -0
  83. dvt/config/profile.py +411 -0
  84. dvt/config/profiles_v2.py +464 -0
  85. dvt/config/project.py +893 -0
  86. dvt/config/renderer.py +232 -0
  87. dvt/config/runtime.py +491 -0
  88. dvt/config/selectors.py +209 -0
  89. dvt/config/utils.py +78 -0
  90. dvt/connectors/.gitignore +6 -0
  91. dvt/connectors/README.md +306 -0
  92. dvt/connectors/catalog.yml +217 -0
  93. dvt/connectors/download_connectors.py +300 -0
  94. dvt/constants.py +29 -0
  95. dvt/context/__init__.py +0 -0
  96. dvt/context/base.py +746 -0
  97. dvt/context/configured.py +136 -0
  98. dvt/context/context_config.py +350 -0
  99. dvt/context/docs.py +82 -0
  100. dvt/context/exceptions_jinja.py +179 -0
  101. dvt/context/macro_resolver.py +195 -0
  102. dvt/context/macros.py +171 -0
  103. dvt/context/manifest.py +73 -0
  104. dvt/context/providers.py +2198 -0
  105. dvt/context/query_header.py +14 -0
  106. dvt/context/secret.py +59 -0
  107. dvt/context/target.py +74 -0
  108. dvt/contracts/__init__.py +0 -0
  109. dvt/contracts/files.py +413 -0
  110. dvt/contracts/graph/__init__.py +0 -0
  111. dvt/contracts/graph/manifest.py +1904 -0
  112. dvt/contracts/graph/metrics.py +98 -0
  113. dvt/contracts/graph/model_config.py +71 -0
  114. dvt/contracts/graph/node_args.py +42 -0
  115. dvt/contracts/graph/nodes.py +1806 -0
  116. dvt/contracts/graph/semantic_manifest.py +233 -0
  117. dvt/contracts/graph/unparsed.py +812 -0
  118. dvt/contracts/project.py +417 -0
  119. dvt/contracts/results.py +53 -0
  120. dvt/contracts/selection.py +23 -0
  121. dvt/contracts/sql.py +86 -0
  122. dvt/contracts/state.py +69 -0
  123. dvt/contracts/util.py +46 -0
  124. dvt/deprecations.py +347 -0
  125. dvt/deps/__init__.py +0 -0
  126. dvt/deps/base.py +153 -0
  127. dvt/deps/git.py +196 -0
  128. dvt/deps/local.py +80 -0
  129. dvt/deps/registry.py +131 -0
  130. dvt/deps/resolver.py +149 -0
  131. dvt/deps/tarball.py +121 -0
  132. dvt/docs/source/_ext/dbt_click.py +118 -0
  133. dvt/docs/source/conf.py +32 -0
  134. dvt/env_vars.py +64 -0
  135. dvt/event_time/event_time.py +40 -0
  136. dvt/event_time/sample_window.py +60 -0
  137. dvt/events/__init__.py +16 -0
  138. dvt/events/base_types.py +37 -0
  139. dvt/events/core_types_pb2.py +2 -0
  140. dvt/events/logging.py +109 -0
  141. dvt/events/types.py +2534 -0
  142. dvt/exceptions.py +1487 -0
  143. dvt/flags.py +89 -0
  144. dvt/graph/__init__.py +11 -0
  145. dvt/graph/cli.py +248 -0
  146. dvt/graph/graph.py +172 -0
  147. dvt/graph/queue.py +213 -0
  148. dvt/graph/selector.py +375 -0
  149. dvt/graph/selector_methods.py +976 -0
  150. dvt/graph/selector_spec.py +223 -0
  151. dvt/graph/thread_pool.py +18 -0
  152. dvt/hooks.py +21 -0
  153. dvt/include/README.md +49 -0
  154. dvt/include/__init__.py +3 -0
  155. dvt/include/global_project.py +4 -0
  156. dvt/include/starter_project/.gitignore +4 -0
  157. dvt/include/starter_project/README.md +15 -0
  158. dvt/include/starter_project/__init__.py +3 -0
  159. dvt/include/starter_project/analyses/.gitkeep +0 -0
  160. dvt/include/starter_project/dvt_project.yml +36 -0
  161. dvt/include/starter_project/macros/.gitkeep +0 -0
  162. dvt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  163. dvt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  164. dvt/include/starter_project/models/example/schema.yml +21 -0
  165. dvt/include/starter_project/seeds/.gitkeep +0 -0
  166. dvt/include/starter_project/snapshots/.gitkeep +0 -0
  167. dvt/include/starter_project/tests/.gitkeep +0 -0
  168. dvt/internal_deprecations.py +27 -0
  169. dvt/jsonschemas/__init__.py +3 -0
  170. dvt/jsonschemas/jsonschemas.py +309 -0
  171. dvt/jsonschemas/project/0.0.110.json +4717 -0
  172. dvt/jsonschemas/project/0.0.85.json +2015 -0
  173. dvt/jsonschemas/resources/0.0.110.json +2636 -0
  174. dvt/jsonschemas/resources/0.0.85.json +2536 -0
  175. dvt/jsonschemas/resources/latest.json +6773 -0
  176. dvt/links.py +4 -0
  177. dvt/materializations/__init__.py +0 -0
  178. dvt/materializations/incremental/__init__.py +0 -0
  179. dvt/materializations/incremental/microbatch.py +235 -0
  180. dvt/mp_context.py +8 -0
  181. dvt/node_types.py +37 -0
  182. dvt/parser/__init__.py +23 -0
  183. dvt/parser/analysis.py +21 -0
  184. dvt/parser/base.py +549 -0
  185. dvt/parser/common.py +267 -0
  186. dvt/parser/docs.py +52 -0
  187. dvt/parser/fixtures.py +51 -0
  188. dvt/parser/functions.py +30 -0
  189. dvt/parser/generic_test.py +100 -0
  190. dvt/parser/generic_test_builders.py +334 -0
  191. dvt/parser/hooks.py +119 -0
  192. dvt/parser/macros.py +137 -0
  193. dvt/parser/manifest.py +2204 -0
  194. dvt/parser/models.py +574 -0
  195. dvt/parser/partial.py +1179 -0
  196. dvt/parser/read_files.py +445 -0
  197. dvt/parser/schema_generic_tests.py +423 -0
  198. dvt/parser/schema_renderer.py +111 -0
  199. dvt/parser/schema_yaml_readers.py +936 -0
  200. dvt/parser/schemas.py +1467 -0
  201. dvt/parser/search.py +149 -0
  202. dvt/parser/seeds.py +28 -0
  203. dvt/parser/singular_test.py +20 -0
  204. dvt/parser/snapshots.py +44 -0
  205. dvt/parser/sources.py +557 -0
  206. dvt/parser/sql.py +63 -0
  207. dvt/parser/unit_tests.py +622 -0
  208. dvt/plugins/__init__.py +20 -0
  209. dvt/plugins/contracts.py +10 -0
  210. dvt/plugins/exceptions.py +2 -0
  211. dvt/plugins/manager.py +164 -0
  212. dvt/plugins/manifest.py +21 -0
  213. dvt/profiler.py +20 -0
  214. dvt/py.typed +1 -0
  215. dvt/runners/__init__.py +2 -0
  216. dvt/runners/exposure_runner.py +7 -0
  217. dvt/runners/no_op_runner.py +46 -0
  218. dvt/runners/saved_query_runner.py +7 -0
  219. dvt/selected_resources.py +8 -0
  220. dvt/task/__init__.py +0 -0
  221. dvt/task/base.py +504 -0
  222. dvt/task/build.py +197 -0
  223. dvt/task/clean.py +57 -0
  224. dvt/task/clone.py +162 -0
  225. dvt/task/compile.py +151 -0
  226. dvt/task/compute.py +366 -0
  227. dvt/task/debug.py +650 -0
  228. dvt/task/deps.py +280 -0
  229. dvt/task/docs/__init__.py +3 -0
  230. dvt/task/docs/generate.py +408 -0
  231. dvt/task/docs/index.html +250 -0
  232. dvt/task/docs/serve.py +28 -0
  233. dvt/task/freshness.py +323 -0
  234. dvt/task/function.py +122 -0
  235. dvt/task/group_lookup.py +46 -0
  236. dvt/task/init.py +374 -0
  237. dvt/task/list.py +237 -0
  238. dvt/task/printer.py +176 -0
  239. dvt/task/profiles.py +256 -0
  240. dvt/task/retry.py +175 -0
  241. dvt/task/run.py +1146 -0
  242. dvt/task/run_operation.py +142 -0
  243. dvt/task/runnable.py +802 -0
  244. dvt/task/seed.py +104 -0
  245. dvt/task/show.py +150 -0
  246. dvt/task/snapshot.py +57 -0
  247. dvt/task/sql.py +111 -0
  248. dvt/task/test.py +464 -0
  249. dvt/tests/fixtures/__init__.py +1 -0
  250. dvt/tests/fixtures/project.py +620 -0
  251. dvt/tests/util.py +651 -0
  252. dvt/tracking.py +529 -0
  253. dvt/utils/__init__.py +3 -0
  254. dvt/utils/artifact_upload.py +151 -0
  255. dvt/utils/utils.py +408 -0
  256. dvt/version.py +249 -0
  257. dvt_core-1.11.0b4.dist-info/METADATA +252 -0
  258. dvt_core-1.11.0b4.dist-info/RECORD +261 -0
  259. dvt_core-1.11.0b4.dist-info/WHEEL +5 -0
  260. dvt_core-1.11.0b4.dist-info/entry_points.txt +2 -0
  261. dvt_core-1.11.0b4.dist-info/top_level.txt +1 -0
dvt/task/debug.py ADDED
@@ -0,0 +1,650 @@
1
+ # coding=utf-8
2
+ import importlib
3
+ import os
4
+ import platform
5
+ import sys
6
+ from collections import namedtuple
7
+ from enum import Flag
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional, Tuple
10
+
11
+ import dvt.exceptions
12
+ from dvt.artifacts.schemas.results import RunStatus
13
+ from dvt.cli.flags import Flags
14
+ from dvt.clients.yaml_helper import load_yaml_text
15
+ from dvt.config import PartialProject, Profile, Project
16
+ from dvt.config.renderer import DbtProjectYamlRenderer, ProfileRenderer
17
+ from dvt.events.types import DebugCmdOut, DebugCmdResult, OpenCommand
18
+ from dvt.links import ProfileConfigDocs
19
+ from dvt.mp_context import get_mp_context
20
+ from dvt.task.base import BaseTask, get_nearest_project_dir
21
+ from dvt.version import get_installed_version
22
+
23
+ import dbt_common.clients.system
24
+ import dbt_common.exceptions
25
+ from dbt.adapters.factory import get_adapter, register_adapter
26
+ from dbt_common.events.format import pluralize
27
+ from dbt_common.events.functions import fire_event
28
+ from dbt_common.ui import green, red
29
+
30
+ ONLY_PROFILE_MESSAGE = """
31
+ A `dbt_project.yml` file was not found in this directory.
32
+ Using the only profile `{}`.
33
+ """.lstrip()
34
+
35
+ MULTIPLE_PROFILE_MESSAGE = """
36
+ A `dbt_project.yml` file was not found in this directory.
37
+ dbt found the following profiles:
38
+ {}
39
+
40
+ To debug one of these profiles, run:
41
+ dbt debug --profile [profile-name]
42
+ """.lstrip()
43
+
44
+ COULD_NOT_CONNECT_MESSAGE = """
45
+ dbt was unable to connect to the specified database.
46
+ The database returned the following error:
47
+
48
+ >{err}
49
+
50
+ Check your database credentials and try again. For more information, visit:
51
+ {url}
52
+ """.lstrip()
53
+
54
+ MISSING_PROFILE_MESSAGE = """
55
+ dbt looked for a profiles.yml file in {path}, but did
56
+ not find one. For more information on configuring your profile, consult the
57
+ documentation:
58
+
59
+ {url}
60
+ """.lstrip()
61
+
62
+ FILE_NOT_FOUND = "file not found"
63
+
64
+
65
+ SubtaskStatus = namedtuple(
66
+ "SubtaskStatus", ["log_msg", "run_status", "details", "summary_message"]
67
+ )
68
+
69
+
70
+ class DebugRunStatus(Flag):
71
+ SUCCESS = True
72
+ FAIL = False
73
+
74
+
75
+ class DebugTask(BaseTask):
76
+ def __init__(self, args: Flags) -> None:
77
+ super().__init__(args)
78
+ self.profiles_dir = args.PROFILES_DIR
79
+ self.profile_path = os.path.join(self.profiles_dir, "profiles.yml")
80
+ try:
81
+ self.project_dir = get_nearest_project_dir(self.args.project_dir)
82
+ except dbt_common.exceptions.DbtBaseException:
83
+ # we probably couldn't find a project directory. Set project dir
84
+ # to whatever was given, or default to the current directory.
85
+ if args.project_dir:
86
+ self.project_dir = args.project_dir
87
+ else:
88
+ self.project_dir = Path.cwd()
89
+ self.project_path = os.path.join(self.project_dir, "dbt_project.yml")
90
+ self.cli_vars: Dict[str, Any] = args.vars
91
+
92
+ # set by _load_*
93
+ self.profile: Optional[Profile] = None
94
+ self.raw_profile_data: Optional[Dict[str, Any]] = None
95
+ self.profile_name: Optional[str] = None
96
+
97
+ def run(self) -> bool:
98
+ # WARN: this is a legacy workflow that is not compatible with other runtime flags
99
+ if self.args.config_dir:
100
+ fire_event(
101
+ OpenCommand(
102
+ open_cmd=dbt_common.clients.system.open_dir_cmd(),
103
+ profiles_dir=str(self.profiles_dir),
104
+ )
105
+ )
106
+ return DebugRunStatus.SUCCESS.value
107
+
108
+ # DVT extension: --all-profiles flag to test all profiles
109
+ if getattr(self.args, "all_profiles", False):
110
+ return self._run_all_profiles_debug()
111
+
112
+ version: str = get_installed_version().to_version_string(skip_matcher=True)
113
+ fire_event(DebugCmdOut(msg="dbt version: {}".format(version)))
114
+ fire_event(DebugCmdOut(msg="python version: {}".format(sys.version.split()[0])))
115
+ fire_event(DebugCmdOut(msg="python path: {}".format(sys.executable)))
116
+ fire_event(DebugCmdOut(msg="os info: {}".format(platform.platform())))
117
+
118
+ # Load profile if possible, then load adapter info (which requires the profile)
119
+ load_profile_status: SubtaskStatus = self._load_profile()
120
+ fire_event(DebugCmdOut(msg="Using profiles dir at {}".format(self.profiles_dir)))
121
+ fire_event(DebugCmdOut(msg="Using profiles.yml file at {}".format(self.profile_path)))
122
+ fire_event(DebugCmdOut(msg="Using dbt_project.yml file at {}".format(self.project_path)))
123
+ if load_profile_status.run_status == RunStatus.Success:
124
+ if self.profile is None:
125
+ raise dbt_common.exceptions.DbtInternalError(
126
+ "Profile should not be None if loading profile completed"
127
+ )
128
+ else:
129
+ adapter_type: str = self.profile.credentials.type
130
+
131
+ adapter_version: str = self._read_adapter_version(
132
+ f"dbt.adapters.{adapter_type}.__version__"
133
+ )
134
+ fire_event(DebugCmdOut(msg="adapter type: {}".format(adapter_type)))
135
+ fire_event(DebugCmdOut(msg="adapter version: {}".format(adapter_version)))
136
+
137
+ # Get project loaded to do additional checks
138
+ load_project_status: SubtaskStatus = self._load_project()
139
+
140
+ dependencies_statuses: List[SubtaskStatus] = []
141
+ if self.args.connection:
142
+ fire_event(DebugCmdOut(msg="Skipping steps before connection verification"))
143
+ else:
144
+ # this job's status not logged since already accounted for in _load_* commands
145
+ self.test_configuration(load_profile_status.log_msg, load_project_status.log_msg)
146
+ dependencies_statuses = self.test_dependencies()
147
+
148
+ # Test connection
149
+ connection_status = self.test_connection()
150
+
151
+ # Log messages from any fails
152
+ all_statuses: List[SubtaskStatus] = [
153
+ load_profile_status,
154
+ load_project_status,
155
+ *dependencies_statuses,
156
+ connection_status,
157
+ ]
158
+ all_failing_statuses: List[SubtaskStatus] = list(
159
+ filter(lambda status: status.run_status == RunStatus.Error, all_statuses)
160
+ )
161
+
162
+ failure_count: int = len(all_failing_statuses)
163
+ if failure_count > 0:
164
+ fire_event(DebugCmdResult(msg=red(f"{(pluralize(failure_count, 'check'))} failed:")))
165
+ for status in all_failing_statuses:
166
+ fire_event(DebugCmdResult(msg=f"{status.summary_message}\n"))
167
+ return DebugRunStatus.FAIL.value
168
+ else:
169
+ fire_event(DebugCmdResult(msg=green("All checks passed!")))
170
+ return DebugRunStatus.SUCCESS.value
171
+
172
+ # ==============================
173
+ # Override for elsewhere in core
174
+ # ==============================
175
+
176
+ def interpret_results(self, results):
177
+ return results
178
+
179
+ # ===============
180
+ # Loading profile
181
+ # ===============
182
+
183
+ def _load_profile(self) -> SubtaskStatus:
184
+ """
185
+ Side effects: load self.profile
186
+ load self.target_name
187
+ load self.raw_profile_data
188
+ """
189
+ if not os.path.exists(self.profile_path):
190
+ return SubtaskStatus(
191
+ log_msg=red("ERROR not found"),
192
+ run_status=RunStatus.Error,
193
+ details=FILE_NOT_FOUND,
194
+ summary_message=MISSING_PROFILE_MESSAGE.format(
195
+ path=self.profile_path, url=ProfileConfigDocs
196
+ ),
197
+ )
198
+
199
+ raw_profile_data = load_yaml_text(
200
+ dbt_common.clients.system.load_file_contents(self.profile_path)
201
+ )
202
+ if isinstance(raw_profile_data, dict):
203
+ self.raw_profile_data = raw_profile_data
204
+
205
+ profile_errors = []
206
+ profile_names, summary_message = self._choose_profile_names()
207
+ renderer = ProfileRenderer(self.cli_vars)
208
+ for profile_name in profile_names:
209
+ try:
210
+ profile: Profile = Profile.render(
211
+ renderer,
212
+ profile_name,
213
+ self.args.profile,
214
+ self.args.target,
215
+ # TODO: Generalize safe access to flags.THREADS:
216
+ # https://github.com/dbt-labs/dbt-core/issues/6259
217
+ getattr(self.args, "threads", None),
218
+ )
219
+ except dbt_common.exceptions.DbtConfigError as exc:
220
+ profile_errors.append(str(exc))
221
+ else:
222
+ if len(profile_names) == 1:
223
+ # if a profile was specified, set it on the task
224
+ self.target_name = self._choose_target_name(profile_name)
225
+ self.profile = profile
226
+
227
+ if profile_errors:
228
+ details = "\n\n".join(profile_errors)
229
+ return SubtaskStatus(
230
+ log_msg=red("ERROR invalid"),
231
+ run_status=RunStatus.Error,
232
+ details=details,
233
+ summary_message=(
234
+ summary_message + f"Profile loading failed for the following reason:"
235
+ f"\n{details}"
236
+ f"\n"
237
+ ),
238
+ )
239
+ else:
240
+ return SubtaskStatus(
241
+ log_msg=green("OK found and valid"),
242
+ run_status=RunStatus.Success,
243
+ details="",
244
+ summary_message="Profile is valid",
245
+ )
246
+
247
+ def _choose_profile_names(self) -> Tuple[List[str], str]:
248
+ project_profile: Optional[str] = None
249
+ if os.path.exists(self.project_path):
250
+ try:
251
+ partial = PartialProject.from_project_root(
252
+ os.path.dirname(self.project_path),
253
+ verify_version=bool(self.args.VERSION_CHECK),
254
+ )
255
+ renderer = DbtProjectYamlRenderer(None, self.cli_vars)
256
+ project_profile = partial.render_profile_name(renderer)
257
+ except dbt.exceptions.DbtProjectError:
258
+ pass
259
+
260
+ args_profile: Optional[str] = getattr(self.args, "profile", None)
261
+
262
+ try:
263
+ return [Profile.pick_profile_name(args_profile, project_profile)], ""
264
+ except dbt_common.exceptions.DbtConfigError:
265
+ pass
266
+ # try to guess
267
+
268
+ profiles = []
269
+ if self.raw_profile_data:
270
+ profiles = [k for k in self.raw_profile_data if k != "config"]
271
+ if project_profile is None:
272
+ summary_message = "Could not load dbt_project.yml\n"
273
+ elif len(profiles) == 0:
274
+ summary_message = "The profiles.yml has no profiles\n"
275
+ elif len(profiles) == 1:
276
+ summary_message = ONLY_PROFILE_MESSAGE.format(profiles[0])
277
+ else:
278
+ summary_message = MULTIPLE_PROFILE_MESSAGE.format(
279
+ "\n".join(" - {}".format(o) for o in profiles)
280
+ )
281
+ return profiles, summary_message
282
+
283
+ def _read_adapter_version(self, module) -> str:
284
+ """read the version out of a standard adapter file"""
285
+ try:
286
+ version = importlib.import_module(module).version
287
+ except ModuleNotFoundError:
288
+ version = red("ERROR not found")
289
+ except Exception as exc:
290
+ version = red("ERROR {}".format(exc))
291
+ raise dbt.exceptions.DbtInternalError(
292
+ f"Error when reading adapter version from {module}: {exc}"
293
+ )
294
+
295
+ return version
296
+
297
+ def _choose_target_name(self, profile_name: str):
298
+ has_raw_profile = (
299
+ self.raw_profile_data is not None and profile_name in self.raw_profile_data
300
+ )
301
+
302
+ if not has_raw_profile:
303
+ return None
304
+
305
+ # mypy appeasement, we checked just above
306
+ assert self.raw_profile_data is not None
307
+ raw_profile = self.raw_profile_data[profile_name]
308
+
309
+ renderer = ProfileRenderer(self.cli_vars)
310
+
311
+ target_name, _ = Profile.render_profile(
312
+ raw_profile=raw_profile,
313
+ profile_name=profile_name,
314
+ target_override=getattr(self.args, "target", None),
315
+ renderer=renderer,
316
+ )
317
+ return target_name
318
+
319
+ # ===============
320
+ # Loading project
321
+ # ===============
322
+
323
+ def _load_project(self) -> SubtaskStatus:
324
+ """
325
+ Side effect: load self.project
326
+ """
327
+ if not os.path.exists(self.project_path):
328
+ return SubtaskStatus(
329
+ log_msg=red("ERROR not found"),
330
+ run_status=RunStatus.Error,
331
+ details=FILE_NOT_FOUND,
332
+ summary_message=(
333
+ f"Project loading failed for the following reason:"
334
+ f"\n project path <{self.project_path}> not found"
335
+ ),
336
+ )
337
+
338
+ renderer = DbtProjectYamlRenderer(self.profile, self.cli_vars)
339
+
340
+ try:
341
+ self.project = Project.from_project_root(
342
+ str(self.project_dir),
343
+ renderer,
344
+ verify_version=self.args.VERSION_CHECK,
345
+ )
346
+ except dbt_common.exceptions.DbtConfigError as exc:
347
+ return SubtaskStatus(
348
+ log_msg=red("ERROR invalid"),
349
+ run_status=RunStatus.Error,
350
+ details=str(exc),
351
+ summary_message=(
352
+ f"Project loading failed for the following reason:" f"\n{str(exc)}" f"\n"
353
+ ),
354
+ )
355
+ else:
356
+ return SubtaskStatus(
357
+ log_msg=green("OK found and valid"),
358
+ run_status=RunStatus.Success,
359
+ details="",
360
+ summary_message="Project is valid",
361
+ )
362
+
363
+ def _profile_found(self) -> str:
364
+ if not self.raw_profile_data:
365
+ return red("ERROR not found")
366
+ assert self.raw_profile_data is not None
367
+ if self.profile_name in self.raw_profile_data:
368
+ return green("OK found")
369
+ else:
370
+ return red("ERROR not found")
371
+
372
+ def _target_found(self) -> str:
373
+ requirements = self.raw_profile_data and self.profile_name and self.target_name
374
+ if not requirements:
375
+ return red("ERROR not found")
376
+ # mypy appeasement, we checked just above
377
+ assert self.raw_profile_data is not None
378
+ assert self.profile_name is not None
379
+ assert self.target_name is not None
380
+ if self.profile_name not in self.raw_profile_data:
381
+ return red("ERROR not found")
382
+ profiles = self.raw_profile_data[self.profile_name]["outputs"]
383
+ if self.target_name not in profiles:
384
+ return red("ERROR not found")
385
+ else:
386
+ return green("OK found")
387
+
388
+ # ============
389
+ # Config tests
390
+ # ============
391
+
392
+ def test_git(self) -> SubtaskStatus:
393
+ try:
394
+ dbt_common.clients.system.run_cmd(os.getcwd(), ["git", "--help"])
395
+ except dbt_common.exceptions.ExecutableError as exc:
396
+ return SubtaskStatus(
397
+ log_msg=red("ERROR"),
398
+ run_status=RunStatus.Error,
399
+ details="git error",
400
+ summary_message="Error from git --help: {!s}".format(exc),
401
+ )
402
+ else:
403
+ return SubtaskStatus(
404
+ log_msg=green("OK found"),
405
+ run_status=RunStatus.Success,
406
+ details="",
407
+ summary_message="git is installed and on the path",
408
+ )
409
+
410
+ def test_dependencies(self) -> List[SubtaskStatus]:
411
+ fire_event(DebugCmdOut(msg="Required dependencies:"))
412
+
413
+ git_test_status = self.test_git()
414
+ fire_event(DebugCmdResult(msg=f" - git [{git_test_status.log_msg}]\n"))
415
+
416
+ return [git_test_status]
417
+
418
+ def test_configuration(self, profile_status_msg, project_status_msg):
419
+ fire_event(DebugCmdOut(msg="Configuration:"))
420
+ fire_event(DebugCmdOut(msg=f" profiles.yml file [{profile_status_msg}]"))
421
+ fire_event(DebugCmdOut(msg=f" dbt_project.yml file [{project_status_msg}]"))
422
+
423
+ # skip profile stuff if we can't find a profile name
424
+ if self.profile_name is not None:
425
+ fire_event(
426
+ DebugCmdOut(
427
+ msg=" profile: {} [{}]\n".format(self.profile_name, self._profile_found())
428
+ )
429
+ )
430
+ fire_event(
431
+ DebugCmdOut(
432
+ msg=" target: {} [{}]\n".format(self.target_name, self._target_found())
433
+ )
434
+ )
435
+
436
+ # ===============
437
+ # Connection test
438
+ # ===============
439
+
440
+ @staticmethod
441
+ def attempt_connection(profile) -> Optional[str]:
442
+ """Return a string containing the error message, or None if there was no error."""
443
+ register_adapter(profile, get_mp_context())
444
+ adapter = get_adapter(profile)
445
+ try:
446
+ with adapter.connection_named("debug"):
447
+ # is defined in adapter class
448
+ adapter.debug_query()
449
+ except Exception as exc:
450
+ return COULD_NOT_CONNECT_MESSAGE.format(
451
+ err=str(exc),
452
+ url=ProfileConfigDocs,
453
+ )
454
+ return None
455
+
456
+ def test_connection(self) -> SubtaskStatus:
457
+ if self.profile is None:
458
+ fire_event(DebugCmdOut(msg="Connection test skipped since no profile was found"))
459
+ return SubtaskStatus(
460
+ log_msg=red("SKIPPED"),
461
+ run_status=RunStatus.Skipped,
462
+ details="No profile found",
463
+ summary_message="Connection test skipped since no profile was found",
464
+ )
465
+
466
+ fire_event(DebugCmdOut(msg="Connection:"))
467
+ for k, v in self.profile.credentials.connection_info():
468
+ fire_event(DebugCmdOut(msg=f" {k}: {v}"))
469
+
470
+ connection_result = self.attempt_connection(self.profile)
471
+ if connection_result is None:
472
+ status = SubtaskStatus(
473
+ log_msg=green("OK connection ok"),
474
+ run_status=RunStatus.Success,
475
+ details="",
476
+ summary_message="Connection test passed",
477
+ )
478
+ else:
479
+ status = SubtaskStatus(
480
+ log_msg=red("ERROR"),
481
+ run_status=RunStatus.Error,
482
+ details="Failure in connecting to db",
483
+ summary_message=connection_result,
484
+ )
485
+ fire_event(DebugCmdOut(msg=f" Connection test: [{status.log_msg}]\n"))
486
+ return status
487
+
488
+ @classmethod
489
+ def validate_connection(cls, target_dict) -> None:
490
+ """Validate a connection dictionary. On error, raises a DbtConfigError."""
491
+ target_name = "test"
492
+ # make a fake profile that we can parse
493
+ profile_data = {
494
+ "outputs": {
495
+ target_name: target_dict,
496
+ },
497
+ }
498
+ # this will raise a DbtConfigError on failure
499
+ profile = Profile.from_raw_profile_info(
500
+ raw_profile=profile_data,
501
+ profile_name="",
502
+ target_override=target_name,
503
+ renderer=ProfileRenderer({}),
504
+ )
505
+ result = cls.attempt_connection(profile)
506
+ if result is not None:
507
+ raise dbt.exceptions.DbtProfileError(result, result_type="connection_failure")
508
+
509
+ # ===========================
510
+ # DVT extension: --all-profiles
511
+ # ===========================
512
+
513
+ def _run_all_profiles_debug(self) -> bool:
514
+ """
515
+ Run debug tests for all configured profiles.
516
+
517
+ This is a DVT extension that tests all profiles in profiles.yml,
518
+ not just the project's default profile.
519
+ """
520
+ from pathlib import Path
521
+
522
+ from dvt.config.profiles_v2 import ProfileRegistry, load_unified_profiles
523
+
524
+ version: str = get_installed_version().to_version_string(skip_matcher=True)
525
+ fire_event(DebugCmdOut(msg="=" * 60))
526
+ fire_event(DebugCmdOut(msg="DVT Multi-Profile Debug"))
527
+ fire_event(DebugCmdOut(msg="=" * 60))
528
+ fire_event(DebugCmdOut(msg="dbt version: {}".format(version)))
529
+ fire_event(DebugCmdOut(msg="python version: {}".format(sys.version.split()[0])))
530
+ fire_event(DebugCmdOut(msg="python path: {}".format(sys.executable)))
531
+ fire_event(DebugCmdOut(msg="os info: {}".format(platform.platform())))
532
+ fire_event(DebugCmdOut(msg=""))
533
+ fire_event(DebugCmdOut(msg="Using profiles dir at {}".format(self.profiles_dir)))
534
+ fire_event(DebugCmdOut(msg="Using profiles.yml file at {}".format(self.profile_path)))
535
+ fire_event(DebugCmdOut(msg=""))
536
+
537
+ # Load unified profiles
538
+ project_dir = Path(self.project_dir) if self.project_dir else None
539
+ unified_profiles = load_unified_profiles(project_dir)
540
+
541
+ # Create registry
542
+ registry = ProfileRegistry(unified_profiles)
543
+
544
+ # Get all profiles
545
+ all_profiles = registry.list_all_profiles()
546
+
547
+ if not all_profiles:
548
+ fire_event(DebugCmdOut(msg=red("No profiles found in profiles.yml")))
549
+ return DebugRunStatus.FAIL.value
550
+
551
+ fire_event(DebugCmdOut(msg=f"Found {len(all_profiles)} profile(s):"))
552
+ for profile_name in all_profiles:
553
+ fire_event(DebugCmdOut(msg=f" - {profile_name}"))
554
+ fire_event(DebugCmdOut(msg=""))
555
+
556
+ # Test each profile
557
+ results = {}
558
+ for profile_name in all_profiles:
559
+ fire_event(DebugCmdOut(msg="=" * 60))
560
+ fire_event(DebugCmdOut(msg=f"Testing profile: {profile_name}"))
561
+ fire_event(DebugCmdOut(msg="=" * 60))
562
+
563
+ try:
564
+ # Get profile config
565
+ profile_config = registry.get_or_create_profile(profile_name)
566
+ if not profile_config:
567
+ fire_event(DebugCmdOut(msg=red(f" Profile '{profile_name}' not found")))
568
+ results[profile_name] = False
569
+ continue
570
+
571
+ # Show profile info
572
+ adapter_type = profile_config.get("type", "unknown")
573
+ fire_event(DebugCmdOut(msg=f" Adapter type: {adapter_type}"))
574
+
575
+ # Show connection info (without sensitive data)
576
+ fire_event(DebugCmdOut(msg=" Connection info:"))
577
+ for key, value in profile_config.items():
578
+ if key not in ("type", "threads", "password", "token", "private_key"):
579
+ fire_event(DebugCmdOut(msg=f" {key}: {value}"))
580
+
581
+ # Test connection
582
+ fire_event(DebugCmdOut(msg=" Testing connection..."))
583
+
584
+ # Create a minimal Profile object for testing
585
+ try:
586
+ # Create profile data structure
587
+ profile_data = {
588
+ "outputs": {
589
+ "test": profile_config,
590
+ },
591
+ "target": "test",
592
+ }
593
+
594
+ # Render profile
595
+ renderer = ProfileRenderer(self.cli_vars)
596
+ test_profile = Profile.from_raw_profile_info(
597
+ raw_profile=profile_data,
598
+ profile_name=profile_name,
599
+ target_override="test",
600
+ renderer=renderer,
601
+ )
602
+
603
+ # Attempt connection
604
+ connection_result = self.attempt_connection(test_profile)
605
+
606
+ if connection_result is None:
607
+ fire_event(DebugCmdOut(msg=green(" ✓ Connection successful")))
608
+ results[profile_name] = True
609
+ else:
610
+ fire_event(DebugCmdOut(msg=red(" ✗ Connection failed")))
611
+ fire_event(DebugCmdOut(msg=f" Error: {connection_result}"))
612
+ results[profile_name] = False
613
+
614
+ except Exception as e:
615
+ fire_event(DebugCmdOut(msg=red(" ✗ Connection test failed")))
616
+ fire_event(DebugCmdOut(msg=f" Error: {str(e)}"))
617
+ results[profile_name] = False
618
+
619
+ except Exception as e:
620
+ fire_event(DebugCmdOut(msg=red(f" Error testing profile: {str(e)}")))
621
+ results[profile_name] = False
622
+
623
+ fire_event(DebugCmdOut(msg=""))
624
+
625
+ # Summary
626
+ fire_event(DebugCmdOut(msg="=" * 60))
627
+ fire_event(DebugCmdOut(msg="Summary"))
628
+ fire_event(DebugCmdOut(msg="=" * 60))
629
+
630
+ success_count = sum(1 for success in results.values() if success)
631
+ fail_count = len(results) - success_count
632
+
633
+ fire_event(DebugCmdOut(msg=f"Total profiles tested: {len(results)}"))
634
+ fire_event(DebugCmdOut(msg=green(f"Successful: {success_count}")))
635
+ if fail_count > 0:
636
+ fire_event(DebugCmdOut(msg=red(f"Failed: {fail_count}")))
637
+
638
+ fire_event(DebugCmdOut(msg=""))
639
+ fire_event(DebugCmdOut(msg="Profile results:"))
640
+ for profile_name, success in results.items():
641
+ status = green("✓") if success else red("✗")
642
+ fire_event(DebugCmdOut(msg=f" {status} {profile_name}"))
643
+
644
+ # Return overall success
645
+ if fail_count == 0:
646
+ fire_event(DebugCmdResult(msg=green("\nAll profiles passed!")))
647
+ return DebugRunStatus.SUCCESS.value
648
+ else:
649
+ fire_event(DebugCmdResult(msg=red(f"\n{pluralize(fail_count, 'profile')} failed")))
650
+ return DebugRunStatus.FAIL.value