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.
- dvt/__init__.py +7 -0
- dvt/_pydantic_shim.py +26 -0
- dvt/adapters/__init__.py +16 -0
- dvt/adapters/multi_adapter_manager.py +268 -0
- dvt/artifacts/__init__.py +0 -0
- dvt/artifacts/exceptions/__init__.py +1 -0
- dvt/artifacts/exceptions/schemas.py +31 -0
- dvt/artifacts/resources/__init__.py +116 -0
- dvt/artifacts/resources/base.py +68 -0
- dvt/artifacts/resources/types.py +93 -0
- dvt/artifacts/resources/v1/analysis.py +10 -0
- dvt/artifacts/resources/v1/catalog.py +23 -0
- dvt/artifacts/resources/v1/components.py +275 -0
- dvt/artifacts/resources/v1/config.py +282 -0
- dvt/artifacts/resources/v1/documentation.py +11 -0
- dvt/artifacts/resources/v1/exposure.py +52 -0
- dvt/artifacts/resources/v1/function.py +53 -0
- dvt/artifacts/resources/v1/generic_test.py +32 -0
- dvt/artifacts/resources/v1/group.py +22 -0
- dvt/artifacts/resources/v1/hook.py +11 -0
- dvt/artifacts/resources/v1/macro.py +30 -0
- dvt/artifacts/resources/v1/metric.py +173 -0
- dvt/artifacts/resources/v1/model.py +146 -0
- dvt/artifacts/resources/v1/owner.py +10 -0
- dvt/artifacts/resources/v1/saved_query.py +112 -0
- dvt/artifacts/resources/v1/seed.py +42 -0
- dvt/artifacts/resources/v1/semantic_layer_components.py +72 -0
- dvt/artifacts/resources/v1/semantic_model.py +315 -0
- dvt/artifacts/resources/v1/singular_test.py +14 -0
- dvt/artifacts/resources/v1/snapshot.py +92 -0
- dvt/artifacts/resources/v1/source_definition.py +85 -0
- dvt/artifacts/resources/v1/sql_operation.py +10 -0
- dvt/artifacts/resources/v1/unit_test_definition.py +78 -0
- dvt/artifacts/schemas/__init__.py +0 -0
- dvt/artifacts/schemas/base.py +191 -0
- dvt/artifacts/schemas/batch_results.py +24 -0
- dvt/artifacts/schemas/catalog/__init__.py +12 -0
- dvt/artifacts/schemas/catalog/v1/__init__.py +0 -0
- dvt/artifacts/schemas/catalog/v1/catalog.py +60 -0
- dvt/artifacts/schemas/freshness/__init__.py +1 -0
- dvt/artifacts/schemas/freshness/v3/__init__.py +0 -0
- dvt/artifacts/schemas/freshness/v3/freshness.py +159 -0
- dvt/artifacts/schemas/manifest/__init__.py +2 -0
- dvt/artifacts/schemas/manifest/v12/__init__.py +0 -0
- dvt/artifacts/schemas/manifest/v12/manifest.py +212 -0
- dvt/artifacts/schemas/results.py +148 -0
- dvt/artifacts/schemas/run/__init__.py +2 -0
- dvt/artifacts/schemas/run/v5/__init__.py +0 -0
- dvt/artifacts/schemas/run/v5/run.py +184 -0
- dvt/artifacts/schemas/upgrades/__init__.py +4 -0
- dvt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
- dvt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
- dvt/artifacts/utils/validation.py +153 -0
- dvt/cli/__init__.py +1 -0
- dvt/cli/context.py +16 -0
- dvt/cli/exceptions.py +56 -0
- dvt/cli/flags.py +558 -0
- dvt/cli/main.py +971 -0
- dvt/cli/option_types.py +121 -0
- dvt/cli/options.py +79 -0
- dvt/cli/params.py +803 -0
- dvt/cli/requires.py +478 -0
- dvt/cli/resolvers.py +32 -0
- dvt/cli/types.py +40 -0
- dvt/clients/__init__.py +0 -0
- dvt/clients/checked_load.py +82 -0
- dvt/clients/git.py +164 -0
- dvt/clients/jinja.py +206 -0
- dvt/clients/jinja_static.py +245 -0
- dvt/clients/registry.py +192 -0
- dvt/clients/yaml_helper.py +68 -0
- dvt/compilation.py +833 -0
- dvt/compute/__init__.py +26 -0
- dvt/compute/base.py +288 -0
- dvt/compute/engines/__init__.py +13 -0
- dvt/compute/engines/duckdb_engine.py +368 -0
- dvt/compute/engines/spark_engine.py +273 -0
- dvt/compute/query_analyzer.py +212 -0
- dvt/compute/router.py +483 -0
- dvt/config/__init__.py +4 -0
- dvt/config/catalogs.py +95 -0
- dvt/config/compute_config.py +406 -0
- dvt/config/profile.py +411 -0
- dvt/config/profiles_v2.py +464 -0
- dvt/config/project.py +893 -0
- dvt/config/renderer.py +232 -0
- dvt/config/runtime.py +491 -0
- dvt/config/selectors.py +209 -0
- dvt/config/utils.py +78 -0
- dvt/connectors/.gitignore +6 -0
- dvt/connectors/README.md +306 -0
- dvt/connectors/catalog.yml +217 -0
- dvt/connectors/download_connectors.py +300 -0
- dvt/constants.py +29 -0
- dvt/context/__init__.py +0 -0
- dvt/context/base.py +746 -0
- dvt/context/configured.py +136 -0
- dvt/context/context_config.py +350 -0
- dvt/context/docs.py +82 -0
- dvt/context/exceptions_jinja.py +179 -0
- dvt/context/macro_resolver.py +195 -0
- dvt/context/macros.py +171 -0
- dvt/context/manifest.py +73 -0
- dvt/context/providers.py +2198 -0
- dvt/context/query_header.py +14 -0
- dvt/context/secret.py +59 -0
- dvt/context/target.py +74 -0
- dvt/contracts/__init__.py +0 -0
- dvt/contracts/files.py +413 -0
- dvt/contracts/graph/__init__.py +0 -0
- dvt/contracts/graph/manifest.py +1904 -0
- dvt/contracts/graph/metrics.py +98 -0
- dvt/contracts/graph/model_config.py +71 -0
- dvt/contracts/graph/node_args.py +42 -0
- dvt/contracts/graph/nodes.py +1806 -0
- dvt/contracts/graph/semantic_manifest.py +233 -0
- dvt/contracts/graph/unparsed.py +812 -0
- dvt/contracts/project.py +417 -0
- dvt/contracts/results.py +53 -0
- dvt/contracts/selection.py +23 -0
- dvt/contracts/sql.py +86 -0
- dvt/contracts/state.py +69 -0
- dvt/contracts/util.py +46 -0
- dvt/deprecations.py +347 -0
- dvt/deps/__init__.py +0 -0
- dvt/deps/base.py +153 -0
- dvt/deps/git.py +196 -0
- dvt/deps/local.py +80 -0
- dvt/deps/registry.py +131 -0
- dvt/deps/resolver.py +149 -0
- dvt/deps/tarball.py +121 -0
- dvt/docs/source/_ext/dbt_click.py +118 -0
- dvt/docs/source/conf.py +32 -0
- dvt/env_vars.py +64 -0
- dvt/event_time/event_time.py +40 -0
- dvt/event_time/sample_window.py +60 -0
- dvt/events/__init__.py +16 -0
- dvt/events/base_types.py +37 -0
- dvt/events/core_types_pb2.py +2 -0
- dvt/events/logging.py +109 -0
- dvt/events/types.py +2534 -0
- dvt/exceptions.py +1487 -0
- dvt/flags.py +89 -0
- dvt/graph/__init__.py +11 -0
- dvt/graph/cli.py +248 -0
- dvt/graph/graph.py +172 -0
- dvt/graph/queue.py +213 -0
- dvt/graph/selector.py +375 -0
- dvt/graph/selector_methods.py +976 -0
- dvt/graph/selector_spec.py +223 -0
- dvt/graph/thread_pool.py +18 -0
- dvt/hooks.py +21 -0
- dvt/include/README.md +49 -0
- dvt/include/__init__.py +3 -0
- dvt/include/global_project.py +4 -0
- dvt/include/starter_project/.gitignore +4 -0
- dvt/include/starter_project/README.md +15 -0
- dvt/include/starter_project/__init__.py +3 -0
- dvt/include/starter_project/analyses/.gitkeep +0 -0
- dvt/include/starter_project/dvt_project.yml +36 -0
- dvt/include/starter_project/macros/.gitkeep +0 -0
- dvt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
- dvt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
- dvt/include/starter_project/models/example/schema.yml +21 -0
- dvt/include/starter_project/seeds/.gitkeep +0 -0
- dvt/include/starter_project/snapshots/.gitkeep +0 -0
- dvt/include/starter_project/tests/.gitkeep +0 -0
- dvt/internal_deprecations.py +27 -0
- dvt/jsonschemas/__init__.py +3 -0
- dvt/jsonschemas/jsonschemas.py +309 -0
- dvt/jsonschemas/project/0.0.110.json +4717 -0
- dvt/jsonschemas/project/0.0.85.json +2015 -0
- dvt/jsonschemas/resources/0.0.110.json +2636 -0
- dvt/jsonschemas/resources/0.0.85.json +2536 -0
- dvt/jsonschemas/resources/latest.json +6773 -0
- dvt/links.py +4 -0
- dvt/materializations/__init__.py +0 -0
- dvt/materializations/incremental/__init__.py +0 -0
- dvt/materializations/incremental/microbatch.py +235 -0
- dvt/mp_context.py +8 -0
- dvt/node_types.py +37 -0
- dvt/parser/__init__.py +23 -0
- dvt/parser/analysis.py +21 -0
- dvt/parser/base.py +549 -0
- dvt/parser/common.py +267 -0
- dvt/parser/docs.py +52 -0
- dvt/parser/fixtures.py +51 -0
- dvt/parser/functions.py +30 -0
- dvt/parser/generic_test.py +100 -0
- dvt/parser/generic_test_builders.py +334 -0
- dvt/parser/hooks.py +119 -0
- dvt/parser/macros.py +137 -0
- dvt/parser/manifest.py +2204 -0
- dvt/parser/models.py +574 -0
- dvt/parser/partial.py +1179 -0
- dvt/parser/read_files.py +445 -0
- dvt/parser/schema_generic_tests.py +423 -0
- dvt/parser/schema_renderer.py +111 -0
- dvt/parser/schema_yaml_readers.py +936 -0
- dvt/parser/schemas.py +1467 -0
- dvt/parser/search.py +149 -0
- dvt/parser/seeds.py +28 -0
- dvt/parser/singular_test.py +20 -0
- dvt/parser/snapshots.py +44 -0
- dvt/parser/sources.py +557 -0
- dvt/parser/sql.py +63 -0
- dvt/parser/unit_tests.py +622 -0
- dvt/plugins/__init__.py +20 -0
- dvt/plugins/contracts.py +10 -0
- dvt/plugins/exceptions.py +2 -0
- dvt/plugins/manager.py +164 -0
- dvt/plugins/manifest.py +21 -0
- dvt/profiler.py +20 -0
- dvt/py.typed +1 -0
- dvt/runners/__init__.py +2 -0
- dvt/runners/exposure_runner.py +7 -0
- dvt/runners/no_op_runner.py +46 -0
- dvt/runners/saved_query_runner.py +7 -0
- dvt/selected_resources.py +8 -0
- dvt/task/__init__.py +0 -0
- dvt/task/base.py +504 -0
- dvt/task/build.py +197 -0
- dvt/task/clean.py +57 -0
- dvt/task/clone.py +162 -0
- dvt/task/compile.py +151 -0
- dvt/task/compute.py +366 -0
- dvt/task/debug.py +650 -0
- dvt/task/deps.py +280 -0
- dvt/task/docs/__init__.py +3 -0
- dvt/task/docs/generate.py +408 -0
- dvt/task/docs/index.html +250 -0
- dvt/task/docs/serve.py +28 -0
- dvt/task/freshness.py +323 -0
- dvt/task/function.py +122 -0
- dvt/task/group_lookup.py +46 -0
- dvt/task/init.py +374 -0
- dvt/task/list.py +237 -0
- dvt/task/printer.py +176 -0
- dvt/task/profiles.py +256 -0
- dvt/task/retry.py +175 -0
- dvt/task/run.py +1146 -0
- dvt/task/run_operation.py +142 -0
- dvt/task/runnable.py +802 -0
- dvt/task/seed.py +104 -0
- dvt/task/show.py +150 -0
- dvt/task/snapshot.py +57 -0
- dvt/task/sql.py +111 -0
- dvt/task/test.py +464 -0
- dvt/tests/fixtures/__init__.py +1 -0
- dvt/tests/fixtures/project.py +620 -0
- dvt/tests/util.py +651 -0
- dvt/tracking.py +529 -0
- dvt/utils/__init__.py +3 -0
- dvt/utils/artifact_upload.py +151 -0
- dvt/utils/utils.py +408 -0
- dvt/version.py +249 -0
- dvt_core-1.11.0b4.dist-info/METADATA +252 -0
- dvt_core-1.11.0b4.dist-info/RECORD +261 -0
- dvt_core-1.11.0b4.dist-info/WHEEL +5 -0
- dvt_core-1.11.0b4.dist-info/entry_points.txt +2 -0
- 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)
|