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/printer.py ADDED
@@ -0,0 +1,176 @@
1
+ from typing import Dict, Optional, Union
2
+
3
+ from dvt.artifacts.schemas.results import NodeStatus
4
+ from dvt.contracts.graph.nodes import Exposure
5
+ from dvt.events.types import (
6
+ CheckNodeTestFailure,
7
+ EndOfRunSummary,
8
+ RunResultError,
9
+ RunResultErrorNoMessage,
10
+ RunResultFailure,
11
+ RunResultWarning,
12
+ RunResultWarningMessage,
13
+ SQLCompiledPath,
14
+ StatsLine,
15
+ )
16
+ from dvt.node_types import NodeType
17
+ from dvt.task import group_lookup
18
+
19
+ from dbt_common.events.base_types import EventLevel
20
+ from dbt_common.events.format import pluralize
21
+ from dbt_common.events.functions import fire_event
22
+ from dbt_common.events.types import Formatting
23
+
24
+
25
+ def get_counts(flat_nodes) -> str:
26
+ counts: Dict[str, int] = {}
27
+
28
+ for node in flat_nodes:
29
+ t = node.resource_type
30
+
31
+ if node.resource_type == NodeType.Model:
32
+ t = "{} {}".format(node.get_materialization(), t)
33
+ elif node.resource_type == NodeType.Operation:
34
+ t = "project hook"
35
+
36
+ counts[t] = counts.get(t, 0) + 1
37
+
38
+ sorted_items = sorted(counts.items(), key=lambda x: x[0])
39
+ stat_line = ", ".join([pluralize(v, k).replace("_", " ") for k, v in sorted_items])
40
+
41
+ return stat_line
42
+
43
+
44
+ def interpret_run_result(result) -> str:
45
+ if result.status in (NodeStatus.Error, NodeStatus.Fail, NodeStatus.PartialSuccess):
46
+ return "error"
47
+ elif result.status == NodeStatus.Skipped:
48
+ return "skip"
49
+ elif result.status == NodeStatus.Warn:
50
+ return "warn"
51
+ elif result.status in (NodeStatus.Pass, NodeStatus.Success):
52
+ return "pass"
53
+ elif result.status == NodeStatus.NoOp:
54
+ return "noop"
55
+ else:
56
+ raise RuntimeError(f"unhandled result {result}")
57
+
58
+
59
+ def print_run_status_line(results) -> None:
60
+ stats = {
61
+ "error": 0,
62
+ "skip": 0,
63
+ "pass": 0,
64
+ "warn": 0,
65
+ "noop": 0,
66
+ "total": 0,
67
+ }
68
+
69
+ for r in results:
70
+ result_type = interpret_run_result(r)
71
+ stats[result_type] += 1
72
+ stats["total"] += 1
73
+
74
+ fire_event(Formatting(""))
75
+ fire_event(StatsLine(stats=stats))
76
+
77
+
78
+ def print_run_result_error(
79
+ result,
80
+ newline: bool = True,
81
+ is_warning: bool = False,
82
+ group: Optional[Dict[str, Union[str, Dict[str, str]]]] = None,
83
+ ) -> None:
84
+ # set node_info for logging events
85
+ node_info = None
86
+ if hasattr(result, "node") and result.node:
87
+ node_info = result.node.node_info
88
+ if result.status in (NodeStatus.Fail, NodeStatus.Error) or (
89
+ is_warning and result.status == NodeStatus.Warn
90
+ ):
91
+ if newline:
92
+ fire_event(Formatting(""))
93
+ if is_warning:
94
+ fire_event(
95
+ RunResultWarning(
96
+ resource_type=result.node.resource_type,
97
+ node_name=result.node.name,
98
+ path=result.node.original_file_path,
99
+ node_info=node_info,
100
+ group=group,
101
+ )
102
+ )
103
+ else:
104
+ fire_event(
105
+ RunResultFailure(
106
+ resource_type=result.node.resource_type,
107
+ node_name=result.node.name,
108
+ path=result.node.original_file_path,
109
+ node_info=node_info,
110
+ group=group,
111
+ )
112
+ )
113
+
114
+ if result.message:
115
+ if is_warning:
116
+ fire_event(RunResultWarningMessage(msg=result.message, node_info=node_info))
117
+ else:
118
+ fire_event(RunResultError(msg=result.message, node_info=node_info, group=group))
119
+ else:
120
+ fire_event(RunResultErrorNoMessage(status=result.status, node_info=node_info))
121
+
122
+ if getattr(result.node, "compiled_path", None):
123
+ fire_event(Formatting(""))
124
+ fire_event(SQLCompiledPath(path=result.node.compiled_path, node_info=node_info))
125
+
126
+ if getattr(result.node, "should_store_failures", None):
127
+ fire_event(Formatting(""))
128
+ fire_event(
129
+ CheckNodeTestFailure(relation_name=result.node.relation_name, node_info=node_info)
130
+ )
131
+ elif result.status == NodeStatus.Skipped and result.message is not None:
132
+ if newline:
133
+ fire_event(Formatting(""), level=EventLevel.DEBUG)
134
+ fire_event(RunResultError(msg=result.message), level=EventLevel.DEBUG)
135
+ elif result.message is not None:
136
+ if newline:
137
+ fire_event(Formatting(""))
138
+ fire_event(RunResultError(msg=result.message, node_info=node_info, group=group))
139
+
140
+
141
+ def print_run_end_messages(results, keyboard_interrupt: bool = False) -> None:
142
+ errors, warnings, partial_successes = [], [], []
143
+ for r in results:
144
+ if r.status in (NodeStatus.RuntimeErr, NodeStatus.Error, NodeStatus.Fail):
145
+ errors.append(r)
146
+ elif r.status == NodeStatus.Skipped and r.message:
147
+ if isinstance(r.node, Exposure):
148
+ # Don't include exposure skips in errors list
149
+ continue
150
+ else:
151
+ # This means we skipped a node because of an issue upstream, so include it as an error
152
+ errors.append(r)
153
+ elif r.status == NodeStatus.Warn:
154
+ warnings.append(r)
155
+ elif r.status == NodeStatus.PartialSuccess:
156
+ partial_successes.append(r)
157
+
158
+ fire_event(Formatting(""))
159
+ fire_event(
160
+ EndOfRunSummary(
161
+ num_errors=len(errors),
162
+ num_warnings=len(warnings),
163
+ num_partial_success=len(partial_successes),
164
+ keyboard_interrupt=keyboard_interrupt,
165
+ )
166
+ )
167
+
168
+ for error in errors:
169
+ group = group_lookup.get(error.node.unique_id) if hasattr(error, "node") else None
170
+ print_run_result_error(error, is_warning=False, group=group)
171
+
172
+ for warning in warnings:
173
+ group = group_lookup.get(warning.node.unique_id) if hasattr(warning, "node") else None
174
+ print_run_result_error(warning, is_warning=True, group=group)
175
+
176
+ print_run_status_line(results)
dvt/task/profiles.py ADDED
@@ -0,0 +1,256 @@
1
+ """
2
+ DVT Profiles Tasks
3
+
4
+ This module implements profile management commands:
5
+ - list: List all configured profiles
6
+ - show: Show details of a specific profile
7
+ - test: Test one or all profiles
8
+ """
9
+
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Any, Dict, Optional
13
+
14
+ from dvt.cli.flags import Flags
15
+ from dvt.config.profiles_v2 import ProfileRegistry, load_unified_profiles
16
+ from dvt.events.types import Note
17
+ from dvt.mp_context import get_mp_context
18
+ from dvt.task.base import BaseTask
19
+
20
+ from dbt_common.events.functions import fire_event
21
+ from dbt_common.ui import green, red, yellow
22
+
23
+
24
+ class ProfilesListTask(BaseTask):
25
+ """Task to list all configured profiles."""
26
+
27
+ def __init__(self, args: Flags) -> None:
28
+ super().__init__(args)
29
+ self.profiles_dir = args.PROFILES_DIR
30
+ self.profile_path = os.path.join(self.profiles_dir, "profiles.yml")
31
+
32
+ def run(self) -> bool:
33
+ """List all configured profiles."""
34
+ fire_event(Note(msg=""))
35
+ fire_event(Note(msg="=" * 60))
36
+ fire_event(Note(msg="DVT Profiles"))
37
+ fire_event(Note(msg="=" * 60))
38
+ fire_event(Note(msg=f"Using profiles.yml file at {self.profile_path}"))
39
+ fire_event(Note(msg=""))
40
+
41
+ # Load unified profiles
42
+ project_dir = Path(self.args.PROJECT_DIR) if self.args.PROJECT_DIR else None
43
+ unified_profiles = load_unified_profiles(project_dir)
44
+
45
+ # Create registry
46
+ registry = ProfileRegistry(unified_profiles)
47
+
48
+ # Get all profiles
49
+ all_profiles = registry.list_all_profiles()
50
+
51
+ if not all_profiles:
52
+ fire_event(Note(msg=red("No profiles found in profiles.yml")))
53
+ return False
54
+
55
+ fire_event(Note(msg=f"Found {len(all_profiles)} profile(s):\n"))
56
+
57
+ # List each profile with adapter type
58
+ for profile_name in all_profiles:
59
+ profile_config = registry.get_or_create_profile(profile_name)
60
+ if profile_config:
61
+ adapter_type = profile_config.get("type", "unknown")
62
+ fire_event(Note(msg=f" • {green(profile_name)} ({adapter_type})"))
63
+ else:
64
+ fire_event(Note(msg=f" • {red(profile_name)} (error loading)"))
65
+
66
+ fire_event(Note(msg=""))
67
+ fire_event(Note(msg=f"Use 'dvt profiles show <profile_name>' to see details"))
68
+ fire_event(Note(msg=f"Use 'dvt profiles test [profile_name]' to test connections"))
69
+ fire_event(Note(msg=""))
70
+
71
+ return True
72
+
73
+ def interpret_results(self, results) -> bool:
74
+ return results
75
+
76
+
77
+ class ProfilesShowTask(BaseTask):
78
+ """Task to show details of a specific profile."""
79
+
80
+ def __init__(self, args: Flags, profile_name: str) -> None:
81
+ super().__init__(args)
82
+ self.profile_name = profile_name
83
+ self.profiles_dir = args.PROFILES_DIR
84
+ self.profile_path = os.path.join(self.profiles_dir, "profiles.yml")
85
+
86
+ def run(self) -> bool:
87
+ """Show details of a specific profile."""
88
+ fire_event(Note(msg=""))
89
+ fire_event(Note(msg="=" * 60))
90
+ fire_event(Note(msg=f"Profile: {self.profile_name}"))
91
+ fire_event(Note(msg="=" * 60))
92
+ fire_event(Note(msg=f"Using profiles.yml file at {self.profile_path}"))
93
+ fire_event(Note(msg=""))
94
+
95
+ # Load unified profiles
96
+ project_dir = Path(self.args.PROJECT_DIR) if self.args.PROJECT_DIR else None
97
+ unified_profiles = load_unified_profiles(project_dir)
98
+
99
+ # Create registry
100
+ registry = ProfileRegistry(unified_profiles)
101
+
102
+ # Get profile config
103
+ profile_config = registry.get_or_create_profile(self.profile_name)
104
+ if not profile_config:
105
+ fire_event(Note(msg=red(f"Profile '{self.profile_name}' not found")))
106
+ fire_event(Note(msg=""))
107
+ fire_event(Note(msg="Available profiles:"))
108
+ for name in registry.list_all_profiles():
109
+ fire_event(Note(msg=f" • {name}"))
110
+ fire_event(Note(msg=""))
111
+ return False
112
+
113
+ # Display profile details
114
+ adapter_type = profile_config.get("type", "unknown")
115
+ fire_event(Note(msg=f"Adapter Type: {green(adapter_type)}"))
116
+ fire_event(Note(msg=""))
117
+ fire_event(Note(msg="Configuration:"))
118
+
119
+ # Show all config (except sensitive fields)
120
+ sensitive_fields = {"password", "token", "private_key", "api_key", "secret"}
121
+ for key, value in sorted(profile_config.items()):
122
+ if key.lower() in sensitive_fields:
123
+ fire_event(Note(msg=f" {key}: {yellow('***REDACTED***')}"))
124
+ else:
125
+ fire_event(Note(msg=f" {key}: {value}"))
126
+
127
+ fire_event(Note(msg=""))
128
+ fire_event(
129
+ Note(msg=f"Use 'dvt profiles test {self.profile_name}' to test this connection")
130
+ )
131
+ fire_event(Note(msg=""))
132
+
133
+ return True
134
+
135
+ def interpret_results(self, results) -> bool:
136
+ return results
137
+
138
+
139
+ class ProfilesTestTask(BaseTask):
140
+ """Task to test one or all profiles."""
141
+
142
+ def __init__(self, args: Flags, profile_name: Optional[str] = None) -> None:
143
+ super().__init__(args)
144
+ self.profile_name = profile_name
145
+ self.profiles_dir = args.PROFILES_DIR
146
+ self.profile_path = os.path.join(self.profiles_dir, "profiles.yml")
147
+
148
+ def run(self) -> bool:
149
+ """Test one or all profiles."""
150
+ from dvt.config import Profile
151
+ from dvt.config.renderer import ProfileRenderer
152
+
153
+ from dbt.adapters.factory import get_adapter, register_adapter
154
+
155
+ fire_event(Note(msg=""))
156
+ fire_event(Note(msg="=" * 60))
157
+ if self.profile_name:
158
+ fire_event(Note(msg=f"Testing Profile: {self.profile_name}"))
159
+ else:
160
+ fire_event(Note(msg="Testing All Profiles"))
161
+ fire_event(Note(msg="=" * 60))
162
+ fire_event(Note(msg=f"Using profiles.yml file at {self.profile_path}"))
163
+ fire_event(Note(msg=""))
164
+
165
+ # Load unified profiles
166
+ project_dir = Path(self.args.PROJECT_DIR) if self.args.PROJECT_DIR else None
167
+ unified_profiles = load_unified_profiles(project_dir)
168
+
169
+ # Create registry
170
+ registry = ProfileRegistry(unified_profiles)
171
+
172
+ # Determine which profiles to test
173
+ if self.profile_name:
174
+ profiles_to_test = [self.profile_name]
175
+ else:
176
+ profiles_to_test = registry.list_all_profiles()
177
+
178
+ if not profiles_to_test:
179
+ fire_event(Note(msg=red("No profiles to test")))
180
+ return False
181
+
182
+ # Test each profile
183
+ results = {}
184
+ for profile_name in profiles_to_test:
185
+ fire_event(Note(msg=f"Testing {profile_name}..."))
186
+
187
+ try:
188
+ # Get profile config
189
+ profile_config = registry.get_or_create_profile(profile_name)
190
+ if not profile_config:
191
+ fire_event(Note(msg=red(f" ✗ Profile '{profile_name}' not found")))
192
+ results[profile_name] = False
193
+ continue
194
+
195
+ # Show adapter type
196
+ adapter_type = profile_config.get("type", "unknown")
197
+ fire_event(Note(msg=f" Adapter: {adapter_type}"))
198
+
199
+ # Create a Profile object for testing
200
+ profile_data = {
201
+ "outputs": {
202
+ "test": profile_config,
203
+ },
204
+ "target": "test",
205
+ }
206
+
207
+ renderer = ProfileRenderer({})
208
+ test_profile = Profile.from_raw_profile_info(
209
+ raw_profile=profile_data,
210
+ profile_name=profile_name,
211
+ target_override="test",
212
+ renderer=renderer,
213
+ )
214
+
215
+ # Test connection
216
+ register_adapter(test_profile, get_mp_context())
217
+ adapter = get_adapter(test_profile)
218
+
219
+ try:
220
+ with adapter.connection_named("debug"):
221
+ adapter.debug_query()
222
+ fire_event(Note(msg=green(f" ✓ Connection successful")))
223
+ results[profile_name] = True
224
+ except Exception as exc:
225
+ fire_event(Note(msg=red(f" ✗ Connection failed: {str(exc)}")))
226
+ results[profile_name] = False
227
+
228
+ except Exception as e:
229
+ fire_event(Note(msg=red(f" ✗ Error: {str(e)}")))
230
+ results[profile_name] = False
231
+
232
+ fire_event(Note(msg=""))
233
+
234
+ # Summary
235
+ success_count = sum(1 for success in results.values() if success)
236
+ fail_count = len(results) - success_count
237
+
238
+ fire_event(Note(msg="=" * 60))
239
+ fire_event(Note(msg="Summary"))
240
+ fire_event(Note(msg="=" * 60))
241
+ fire_event(Note(msg=f"Total: {len(results)}"))
242
+ fire_event(Note(msg=green(f"Passed: {success_count}")))
243
+ if fail_count > 0:
244
+ fire_event(Note(msg=red(f"Failed: {fail_count}")))
245
+
246
+ fire_event(Note(msg=""))
247
+ for profile_name, success in results.items():
248
+ status = green("✓") if success else red("✗")
249
+ fire_event(Note(msg=f" {status} {profile_name}"))
250
+
251
+ fire_event(Note(msg=""))
252
+
253
+ return fail_count == 0
254
+
255
+ def interpret_results(self, results) -> bool:
256
+ return results
dvt/task/retry.py ADDED
@@ -0,0 +1,175 @@
1
+ from pathlib import Path
2
+
3
+ from click import get_current_context
4
+ from click.core import ParameterSource
5
+ from dvt.artifacts.schemas.results import NodeStatus
6
+ from dvt.cli.flags import Flags
7
+ from dvt.cli.types import Command as CliCommand
8
+ from dvt.config import RuntimeConfig
9
+ from dvt.constants import RUN_RESULTS_FILE_NAME
10
+ from dvt.contracts.state import load_result_state
11
+ from dvt.flags import get_flags, set_flags
12
+ from dvt.graph import GraphQueue
13
+ from dvt.parser.manifest import parse_manifest
14
+ from dvt.task.base import ConfiguredTask
15
+ from dvt.task.build import BuildTask
16
+ from dvt.task.clone import CloneTask
17
+ from dvt.task.compile import CompileTask
18
+ from dvt.task.docs.generate import GenerateTask
19
+ from dvt.task.run import RunTask
20
+ from dvt.task.run_operation import RunOperationTask
21
+ from dvt.task.seed import SeedTask
22
+ from dvt.task.snapshot import SnapshotTask
23
+ from dvt.task.test import TestTask
24
+
25
+ from dbt_common.exceptions import DbtRuntimeError
26
+
27
+ RETRYABLE_STATUSES = {
28
+ NodeStatus.Error,
29
+ NodeStatus.Fail,
30
+ NodeStatus.Skipped,
31
+ NodeStatus.RuntimeErr,
32
+ NodeStatus.PartialSuccess,
33
+ }
34
+ IGNORE_PARENT_FLAGS = {
35
+ "log_path",
36
+ "output_path",
37
+ "profiles_dir",
38
+ "profiles_dir_exists_false",
39
+ "project_dir",
40
+ "defer_state",
41
+ "deprecated_state",
42
+ "target_path",
43
+ "warn_error",
44
+ }
45
+
46
+ ALLOW_CLI_OVERRIDE_FLAGS = {"vars", "threads"}
47
+
48
+ TASK_DICT = {
49
+ "build": BuildTask,
50
+ "compile": CompileTask,
51
+ "clone": CloneTask,
52
+ "generate": GenerateTask,
53
+ "seed": SeedTask,
54
+ "snapshot": SnapshotTask,
55
+ "test": TestTask,
56
+ "run": RunTask,
57
+ "run-operation": RunOperationTask,
58
+ }
59
+
60
+ CMD_DICT = {
61
+ "build": CliCommand.BUILD,
62
+ "compile": CliCommand.COMPILE,
63
+ "clone": CliCommand.CLONE,
64
+ "generate": CliCommand.DOCS_GENERATE,
65
+ "seed": CliCommand.SEED,
66
+ "snapshot": CliCommand.SNAPSHOT,
67
+ "test": CliCommand.TEST,
68
+ "run": CliCommand.RUN,
69
+ "run-operation": CliCommand.RUN_OPERATION,
70
+ }
71
+
72
+
73
+ class RetryTask(ConfiguredTask):
74
+ def __init__(self, args: Flags, config: RuntimeConfig) -> None:
75
+ # load previous run results
76
+ state_path = args.state or config.target_path
77
+ self.previous_results = load_result_state(
78
+ Path(config.project_root) / Path(state_path) / RUN_RESULTS_FILE_NAME
79
+ )
80
+ if not self.previous_results:
81
+ raise DbtRuntimeError(
82
+ f"Could not find previous run in '{state_path}' target directory"
83
+ )
84
+ self.previous_args = self.previous_results.args
85
+ self.previous_command_name = self.previous_args.get("which")
86
+
87
+ # Reslove flags and config
88
+ if args.warn_error:
89
+ RETRYABLE_STATUSES.add(NodeStatus.Warn)
90
+
91
+ cli_command = CMD_DICT.get(self.previous_command_name) # type: ignore
92
+ # Remove these args when their default values are present, otherwise they'll raise an exception
93
+ args_to_remove = {
94
+ "show": lambda x: True,
95
+ "resource_types": lambda x: x == [],
96
+ "warn_error_options": lambda x: x == {"warn": [], "error": [], "silence": []},
97
+ }
98
+ for k, v in args_to_remove.items():
99
+ if k in self.previous_args and v(self.previous_args[k]):
100
+ del self.previous_args[k]
101
+ previous_args = {
102
+ k: v for k, v in self.previous_args.items() if k not in IGNORE_PARENT_FLAGS
103
+ }
104
+ click_context = get_current_context()
105
+ current_args = {
106
+ k: v
107
+ for k, v in args.__dict__.items()
108
+ if k in IGNORE_PARENT_FLAGS
109
+ or (
110
+ click_context.get_parameter_source(k) == ParameterSource.COMMANDLINE
111
+ and k in ALLOW_CLI_OVERRIDE_FLAGS
112
+ )
113
+ }
114
+ combined_args = {**previous_args, **current_args}
115
+ retry_flags = Flags.from_dict(cli_command, combined_args) # type: ignore
116
+ set_flags(retry_flags)
117
+ retry_config = RuntimeConfig.from_args(args=retry_flags)
118
+
119
+ # Parse manifest using resolved config/flags
120
+ manifest = parse_manifest(retry_config, False, True, retry_flags.write_json, []) # type: ignore
121
+ super().__init__(args, retry_config, manifest)
122
+ self.task_class = TASK_DICT.get(self.previous_command_name) # type: ignore
123
+
124
+ def run(self):
125
+ unique_ids = {
126
+ result.unique_id
127
+ for result in self.previous_results.results
128
+ if result.status in RETRYABLE_STATUSES
129
+ and not (
130
+ self.previous_command_name != "run-operation"
131
+ and result.unique_id.startswith("operation.")
132
+ )
133
+ }
134
+
135
+ # We need this so that re-running of a microbatch model will only rerun
136
+ # batches that previously failed. Note _explicitly_ do no pass the
137
+ # batch info if there were _no_ successful batches previously. This is
138
+ # because passing the batch info _forces_ the microbatch process into
139
+ # _incremental_ model, and it may be that we need to be in full refresh
140
+ # mode which is only handled if previous_batch_results _isn't_ passed for a node
141
+ batch_map = {
142
+ result.unique_id: result.batch_results
143
+ for result in self.previous_results.results
144
+ if result.batch_results is not None
145
+ and len(result.batch_results.successful) != 0
146
+ and len(result.batch_results.failed) > 0
147
+ and not (
148
+ self.previous_command_name != "run-operation"
149
+ and result.unique_id.startswith("operation.")
150
+ )
151
+ }
152
+
153
+ class TaskWrapper(self.task_class):
154
+ def get_graph_queue(self):
155
+ new_graph = self.graph.get_subset_graph(unique_ids)
156
+ return GraphQueue(
157
+ new_graph.graph,
158
+ self.manifest,
159
+ unique_ids,
160
+ )
161
+
162
+ task = TaskWrapper(
163
+ get_flags(),
164
+ self.config,
165
+ self.manifest,
166
+ )
167
+
168
+ if self.task_class == RunTask:
169
+ task.batch_map = batch_map
170
+
171
+ return_value = task.run()
172
+ return return_value
173
+
174
+ def interpret_results(self, *args, **kwargs):
175
+ return self.task_class.interpret_results(*args, **kwargs)