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