dvt-core 0.58.6__cp311-cp311-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.
- 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 +2403 -0
- dbt/cli/option_types.py +121 -0
- dbt/cli/options.py +80 -0
- dbt/cli/params.py +844 -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.cpython-311-darwin.so +0 -0
- dbt/compute/engines/spark_engine.py +642 -0
- dbt/compute/federated_executor.cpython-311-darwin.so +0 -0
- dbt/compute/federated_executor.py +1080 -0
- dbt/compute/filter_pushdown.cpython-311-darwin.so +0 -0
- dbt/compute/filter_pushdown.py +273 -0
- dbt/compute/jar_provisioning.cpython-311-darwin.so +0 -0
- dbt/compute/jar_provisioning.py +255 -0
- dbt/compute/java_compat.cpython-311-darwin.so +0 -0
- dbt/compute/java_compat.py +689 -0
- dbt/compute/jdbc_utils.cpython-311-darwin.so +0 -0
- dbt/compute/jdbc_utils.py +678 -0
- dbt/compute/metadata/__init__.py +40 -0
- dbt/compute/metadata/adapters_registry.cpython-311-darwin.so +0 -0
- dbt/compute/metadata/adapters_registry.py +370 -0
- dbt/compute/metadata/registry.cpython-311-darwin.so +0 -0
- dbt/compute/metadata/registry.py +674 -0
- dbt/compute/metadata/store.cpython-311-darwin.so +0 -0
- dbt/compute/metadata/store.py +1499 -0
- dbt/compute/smart_selector.cpython-311-darwin.so +0 -0
- dbt/compute/smart_selector.py +377 -0
- dbt/compute/strategies/__init__.py +55 -0
- dbt/compute/strategies/base.cpython-311-darwin.so +0 -0
- dbt/compute/strategies/base.py +165 -0
- dbt/compute/strategies/dataproc.cpython-311-darwin.so +0 -0
- dbt/compute/strategies/dataproc.py +207 -0
- dbt/compute/strategies/emr.cpython-311-darwin.so +0 -0
- dbt/compute/strategies/emr.py +203 -0
- dbt/compute/strategies/local.cpython-311-darwin.so +0 -0
- dbt/compute/strategies/local.py +443 -0
- dbt/compute/strategies/standalone.cpython-311-darwin.so +0 -0
- dbt/compute/strategies/standalone.py +262 -0
- dbt/config/__init__.py +4 -0
- dbt/config/catalogs.py +94 -0
- dbt/config/compute.cpython-311-darwin.so +0 -0
- dbt/config/compute.py +513 -0
- dbt/config/dvt_profile.cpython-311-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 +348 -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 +249 -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/data/adapters_registry.duckdb +0 -0
- dbt/include/data/build_registry.py +242 -0
- dbt/include/data/csv/adapter_queries.csv +33 -0
- dbt/include/data/csv/syntax_rules.csv +9 -0
- dbt/include/data/csv/type_mappings_bigquery.csv +28 -0
- dbt/include/data/csv/type_mappings_databricks.csv +30 -0
- dbt/include/data/csv/type_mappings_mysql.csv +40 -0
- dbt/include/data/csv/type_mappings_oracle.csv +30 -0
- dbt/include/data/csv/type_mappings_postgres.csv +56 -0
- dbt/include/data/csv/type_mappings_redshift.csv +33 -0
- dbt/include/data/csv/type_mappings_snowflake.csv +38 -0
- dbt/include/data/csv/type_mappings_sqlserver.csv +35 -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-311-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.cpython-311-darwin.so +0 -0
- dbt/task/compute.py +458 -0
- dbt/task/debug.py +505 -0
- dbt/task/deps.py +280 -0
- dbt/task/docs/__init__.py +3 -0
- dbt/task/docs/api/__init__.py +23 -0
- dbt/task/docs/api/catalog.cpython-311-darwin.so +0 -0
- dbt/task/docs/api/catalog.py +204 -0
- dbt/task/docs/api/lineage.cpython-311-darwin.so +0 -0
- dbt/task/docs/api/lineage.py +234 -0
- dbt/task/docs/api/profile.cpython-311-darwin.so +0 -0
- dbt/task/docs/api/profile.py +204 -0
- dbt/task/docs/api/spark.cpython-311-darwin.so +0 -0
- dbt/task/docs/api/spark.py +186 -0
- dbt/task/docs/generate.py +947 -0
- dbt/task/docs/index.html +250 -0
- dbt/task/docs/serve.cpython-311-darwin.so +0 -0
- dbt/task/docs/serve.py +174 -0
- dbt/task/dvt_output.py +362 -0
- dbt/task/dvt_run.py +204 -0
- dbt/task/freshness.py +322 -0
- dbt/task/function.py +121 -0
- dbt/task/group_lookup.py +46 -0
- dbt/task/init.cpython-311-darwin.so +0 -0
- dbt/task/init.py +604 -0
- dbt/task/java.cpython-311-darwin.so +0 -0
- dbt/task/java.py +316 -0
- dbt/task/list.py +236 -0
- dbt/task/metadata.cpython-311-darwin.so +0 -0
- dbt/task/metadata.py +804 -0
- dbt/task/printer.py +175 -0
- dbt/task/profile.cpython-311-darwin.so +0 -0
- dbt/task/profile.py +1307 -0
- dbt/task/profile_serve.py +615 -0
- dbt/task/retract.py +438 -0
- dbt/task/retry.py +175 -0
- dbt/task/run.py +1387 -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.cpython-311-darwin.so +0 -0
- dbt/task/spark.py +414 -0
- dbt/task/sql.py +110 -0
- dbt/task/target_sync.cpython-311-darwin.so +0 -0
- dbt/task/target_sync.py +766 -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 +270 -0
- dvt_cli/__init__.py +72 -0
- dvt_core-0.58.6.dist-info/METADATA +288 -0
- dvt_core-0.58.6.dist-info/RECORD +324 -0
- dvt_core-0.58.6.dist-info/WHEEL +5 -0
- dvt_core-0.58.6.dist-info/entry_points.txt +2 -0
- dvt_core-0.58.6.dist-info/top_level.txt +2 -0
dbt/cli/main.py
ADDED
|
@@ -0,0 +1,2403 @@
|
|
|
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
|
+
|
|
584
|
+
DVT enhances dbt run with:
|
|
585
|
+
- Rich progress display with spinner and progress bar
|
|
586
|
+
- Per-model execution path display (PUSHDOWN vs FEDERATION)
|
|
587
|
+
- Beautiful summary panel with pass/fail/skip counts
|
|
588
|
+
|
|
589
|
+
DVT Compute Rules (automatically applied):
|
|
590
|
+
- Same-target models use adapter pushdown (native SQL)
|
|
591
|
+
- Cross-target models use Spark federation
|
|
592
|
+
- Compute selection: default < model config < CLI --target-compute
|
|
593
|
+
- Target selection: default < model config < CLI --target
|
|
594
|
+
"""
|
|
595
|
+
from dbt.task.dvt_run import create_dvt_run_task
|
|
596
|
+
|
|
597
|
+
task = create_dvt_run_task(
|
|
598
|
+
ctx.obj["flags"],
|
|
599
|
+
ctx.obj["runtime_config"],
|
|
600
|
+
ctx.obj["manifest"],
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
results = task.run()
|
|
604
|
+
success = task.interpret_results(results)
|
|
605
|
+
return results, success
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
# dbt retry
|
|
609
|
+
@cli.command("retry")
|
|
610
|
+
@click.pass_context
|
|
611
|
+
@global_flags
|
|
612
|
+
@p.project_dir
|
|
613
|
+
@p.profiles_dir
|
|
614
|
+
@p.vars
|
|
615
|
+
@p.target_path
|
|
616
|
+
@p.threads
|
|
617
|
+
@p.full_refresh
|
|
618
|
+
@requires.postflight
|
|
619
|
+
@requires.preflight
|
|
620
|
+
@requires.profile
|
|
621
|
+
@requires.project
|
|
622
|
+
@requires.runtime_config
|
|
623
|
+
def retry(ctx, **kwargs):
|
|
624
|
+
"""Retry the nodes that failed in the previous run."""
|
|
625
|
+
from dbt.task.retry import RetryTask
|
|
626
|
+
|
|
627
|
+
# Retry will parse manifest inside the task after we consolidate the flags
|
|
628
|
+
task = RetryTask(
|
|
629
|
+
ctx.obj["flags"],
|
|
630
|
+
ctx.obj["runtime_config"],
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
results = task.run()
|
|
634
|
+
success = task.interpret_results(results)
|
|
635
|
+
return results, success
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
# dbt clone
|
|
639
|
+
@cli.command("clone")
|
|
640
|
+
@click.pass_context
|
|
641
|
+
@global_flags
|
|
642
|
+
@p.exclude
|
|
643
|
+
@p.full_refresh
|
|
644
|
+
@p.profiles_dir
|
|
645
|
+
@p.project_dir
|
|
646
|
+
@p.resource_type
|
|
647
|
+
@p.exclude_resource_type
|
|
648
|
+
@p.select
|
|
649
|
+
@p.selector
|
|
650
|
+
@p.target_path
|
|
651
|
+
@p.threads
|
|
652
|
+
@p.vars
|
|
653
|
+
@requires.preflight
|
|
654
|
+
@requires.profile
|
|
655
|
+
@requires.project
|
|
656
|
+
@requires.runtime_config
|
|
657
|
+
@requires.manifest
|
|
658
|
+
@requires.postflight
|
|
659
|
+
def clone(ctx, **kwargs):
|
|
660
|
+
"""Create clones of selected nodes based on their location in the manifest provided to --state."""
|
|
661
|
+
from dbt.task.clone import CloneTask
|
|
662
|
+
|
|
663
|
+
task = CloneTask(
|
|
664
|
+
ctx.obj["flags"],
|
|
665
|
+
ctx.obj["runtime_config"],
|
|
666
|
+
ctx.obj["manifest"],
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
results = task.run()
|
|
670
|
+
success = task.interpret_results(results)
|
|
671
|
+
return results, success
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
# dbt run operation
|
|
675
|
+
@cli.command("run-operation")
|
|
676
|
+
@click.pass_context
|
|
677
|
+
@global_flags
|
|
678
|
+
@click.argument("macro")
|
|
679
|
+
@p.args
|
|
680
|
+
@p.profiles_dir
|
|
681
|
+
@p.project_dir
|
|
682
|
+
@p.target_path
|
|
683
|
+
@p.threads
|
|
684
|
+
@p.vars
|
|
685
|
+
@requires.postflight
|
|
686
|
+
@requires.preflight
|
|
687
|
+
@requires.profile
|
|
688
|
+
@requires.project
|
|
689
|
+
@requires.runtime_config
|
|
690
|
+
@requires.manifest
|
|
691
|
+
def run_operation(ctx, **kwargs):
|
|
692
|
+
"""Run the named macro with any supplied arguments."""
|
|
693
|
+
from dbt.task.run_operation import RunOperationTask
|
|
694
|
+
|
|
695
|
+
task = RunOperationTask(
|
|
696
|
+
ctx.obj["flags"],
|
|
697
|
+
ctx.obj["runtime_config"],
|
|
698
|
+
ctx.obj["manifest"],
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
results = task.run()
|
|
702
|
+
success = task.interpret_results(results)
|
|
703
|
+
return results, success
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
# dbt seed
|
|
707
|
+
@cli.command("seed")
|
|
708
|
+
@click.pass_context
|
|
709
|
+
@global_flags
|
|
710
|
+
@p.exclude
|
|
711
|
+
@p.full_refresh
|
|
712
|
+
@p.profiles_dir
|
|
713
|
+
@p.project_dir
|
|
714
|
+
@p.select
|
|
715
|
+
@p.selector
|
|
716
|
+
@p.show
|
|
717
|
+
@p.target_path
|
|
718
|
+
@p.threads
|
|
719
|
+
@p.vars
|
|
720
|
+
@requires.postflight
|
|
721
|
+
@requires.preflight
|
|
722
|
+
@requires.profile
|
|
723
|
+
@requires.project
|
|
724
|
+
@requires.catalogs
|
|
725
|
+
@requires.runtime_config
|
|
726
|
+
@requires.manifest
|
|
727
|
+
def seed(ctx, **kwargs):
|
|
728
|
+
"""Load data from csv files into your data warehouse."""
|
|
729
|
+
from dbt.task.seed import SeedTask
|
|
730
|
+
|
|
731
|
+
task = SeedTask(
|
|
732
|
+
ctx.obj["flags"],
|
|
733
|
+
ctx.obj["runtime_config"],
|
|
734
|
+
ctx.obj["manifest"],
|
|
735
|
+
)
|
|
736
|
+
results = task.run()
|
|
737
|
+
success = task.interpret_results(results)
|
|
738
|
+
return results, success
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
# dbt snapshot
|
|
742
|
+
@cli.command("snapshot")
|
|
743
|
+
@click.pass_context
|
|
744
|
+
@global_flags
|
|
745
|
+
@p.empty
|
|
746
|
+
@p.exclude
|
|
747
|
+
@p.profiles_dir
|
|
748
|
+
@p.project_dir
|
|
749
|
+
@p.select
|
|
750
|
+
@p.selector
|
|
751
|
+
@p.target_path
|
|
752
|
+
@p.threads
|
|
753
|
+
@p.vars
|
|
754
|
+
@requires.postflight
|
|
755
|
+
@requires.preflight
|
|
756
|
+
@requires.profile
|
|
757
|
+
@requires.project
|
|
758
|
+
@requires.catalogs
|
|
759
|
+
@requires.runtime_config
|
|
760
|
+
@requires.manifest
|
|
761
|
+
def snapshot(ctx, **kwargs):
|
|
762
|
+
"""Execute snapshots defined in your project"""
|
|
763
|
+
from dbt.task.snapshot import SnapshotTask
|
|
764
|
+
|
|
765
|
+
task = SnapshotTask(
|
|
766
|
+
ctx.obj["flags"],
|
|
767
|
+
ctx.obj["runtime_config"],
|
|
768
|
+
ctx.obj["manifest"],
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
results = task.run()
|
|
772
|
+
success = task.interpret_results(results)
|
|
773
|
+
return results, success
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
# dbt source
|
|
777
|
+
@cli.group()
|
|
778
|
+
@click.pass_context
|
|
779
|
+
@global_flags
|
|
780
|
+
def source(ctx, **kwargs):
|
|
781
|
+
"""Manage your project's sources"""
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
# dbt source freshness
|
|
785
|
+
@source.command("freshness")
|
|
786
|
+
@click.pass_context
|
|
787
|
+
@global_flags
|
|
788
|
+
@p.exclude
|
|
789
|
+
@p.output_path # TODO: Is this ok to re-use? We have three different output params, how much can we consolidate?
|
|
790
|
+
@p.profiles_dir
|
|
791
|
+
@p.project_dir
|
|
792
|
+
@p.select
|
|
793
|
+
@p.selector
|
|
794
|
+
@p.target_path
|
|
795
|
+
@p.threads
|
|
796
|
+
@p.vars
|
|
797
|
+
@requires.postflight
|
|
798
|
+
@requires.preflight
|
|
799
|
+
@requires.profile
|
|
800
|
+
@requires.project
|
|
801
|
+
@requires.runtime_config
|
|
802
|
+
@requires.manifest
|
|
803
|
+
def freshness(ctx, **kwargs):
|
|
804
|
+
"""check the current freshness of the project's sources"""
|
|
805
|
+
from dbt.task.freshness import FreshnessTask
|
|
806
|
+
|
|
807
|
+
task = FreshnessTask(
|
|
808
|
+
ctx.obj["flags"],
|
|
809
|
+
ctx.obj["runtime_config"],
|
|
810
|
+
ctx.obj["manifest"],
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
results = task.run()
|
|
814
|
+
success = task.interpret_results(results)
|
|
815
|
+
return results, success
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
# Alias "source freshness" to "snapshot-freshness"
|
|
819
|
+
snapshot_freshness = copy(cli.commands["source"].commands["freshness"]) # type: ignore
|
|
820
|
+
snapshot_freshness.hidden = True
|
|
821
|
+
cli.commands["source"].add_command(snapshot_freshness, "snapshot-freshness") # type: ignore
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
# dbt test
|
|
825
|
+
@cli.command("test")
|
|
826
|
+
@click.pass_context
|
|
827
|
+
@global_flags
|
|
828
|
+
@p.exclude
|
|
829
|
+
@p.resource_type
|
|
830
|
+
@p.exclude_resource_type
|
|
831
|
+
@p.profiles_dir
|
|
832
|
+
@p.project_dir
|
|
833
|
+
@p.select
|
|
834
|
+
@p.selector
|
|
835
|
+
@p.store_failures
|
|
836
|
+
@p.target_path
|
|
837
|
+
@p.threads
|
|
838
|
+
@p.vars
|
|
839
|
+
@requires.postflight
|
|
840
|
+
@requires.preflight
|
|
841
|
+
@requires.profile
|
|
842
|
+
@requires.project
|
|
843
|
+
@requires.runtime_config
|
|
844
|
+
@requires.manifest
|
|
845
|
+
def test(ctx, **kwargs):
|
|
846
|
+
"""Runs tests on data in deployed models. Run this after `dbt run`"""
|
|
847
|
+
from dbt.task.test import TestTask
|
|
848
|
+
|
|
849
|
+
task = TestTask(
|
|
850
|
+
ctx.obj["flags"],
|
|
851
|
+
ctx.obj["runtime_config"],
|
|
852
|
+
ctx.obj["manifest"],
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
results = task.run()
|
|
856
|
+
success = task.interpret_results(results)
|
|
857
|
+
return results, success
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
# =============================================================================
|
|
861
|
+
# DVT Metadata Command Group (v0.57.0 - replaces dvt snap)
|
|
862
|
+
# =============================================================================
|
|
863
|
+
|
|
864
|
+
@cli.group("metadata")
|
|
865
|
+
def metadata():
|
|
866
|
+
"""Manage DVT project metadata.
|
|
867
|
+
|
|
868
|
+
The metadata store captures type information for sources and models,
|
|
869
|
+
enabling accurate type mapping across federated queries.
|
|
870
|
+
|
|
871
|
+
Commands:
|
|
872
|
+
reset Clear all metadata from the store
|
|
873
|
+
snapshot Capture metadata for sources and models
|
|
874
|
+
export Display metadata in CLI
|
|
875
|
+
export-csv Export metadata to CSV file
|
|
876
|
+
export-json Export metadata to JSON file
|
|
877
|
+
"""
|
|
878
|
+
pass
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
@metadata.command("reset")
|
|
882
|
+
@click.pass_context
|
|
883
|
+
@p.project_dir
|
|
884
|
+
def metadata_reset(ctx, project_dir, **kwargs):
|
|
885
|
+
"""Clear all metadata from the store.
|
|
886
|
+
|
|
887
|
+
Removes all captured metadata including:
|
|
888
|
+
- Source column metadata
|
|
889
|
+
- Model column metadata
|
|
890
|
+
- Row counts
|
|
891
|
+
- Profile results
|
|
892
|
+
|
|
893
|
+
Example:
|
|
894
|
+
dvt metadata reset
|
|
895
|
+
"""
|
|
896
|
+
from dbt.task.metadata import MetadataTask
|
|
897
|
+
|
|
898
|
+
class Args:
|
|
899
|
+
def __init__(self):
|
|
900
|
+
self.subcommand = 'reset'
|
|
901
|
+
self.project_dir = project_dir
|
|
902
|
+
|
|
903
|
+
task = MetadataTask(Args())
|
|
904
|
+
success, _ = task.run()
|
|
905
|
+
return None, success
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
@metadata.command("snapshot")
|
|
909
|
+
@click.pass_context
|
|
910
|
+
@p.project_dir
|
|
911
|
+
def metadata_snapshot(ctx, project_dir, **kwargs):
|
|
912
|
+
"""Capture metadata for sources and models.
|
|
913
|
+
|
|
914
|
+
Reads source definitions (sources.yml) and model definitions (schema.yml)
|
|
915
|
+
from your project and captures column metadata into .dvt/metadata_store.duckdb.
|
|
916
|
+
|
|
917
|
+
This metadata is used by DVT to:
|
|
918
|
+
- Map adapter types to Spark types for federated queries
|
|
919
|
+
- Optimize query planning with schema information
|
|
920
|
+
- Generate correct Spark DDL for table creation
|
|
921
|
+
|
|
922
|
+
Examples:
|
|
923
|
+
dvt metadata snapshot
|
|
924
|
+
|
|
925
|
+
Note: This is automatically run on first 'dvt run' and on 'dvt run --full-refresh'.
|
|
926
|
+
"""
|
|
927
|
+
from dbt.task.metadata import MetadataTask
|
|
928
|
+
|
|
929
|
+
class Args:
|
|
930
|
+
def __init__(self):
|
|
931
|
+
self.subcommand = 'snapshot'
|
|
932
|
+
self.project_dir = project_dir
|
|
933
|
+
|
|
934
|
+
task = MetadataTask(Args())
|
|
935
|
+
success, _ = task.run()
|
|
936
|
+
return None, success
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
@metadata.command("export")
|
|
940
|
+
@click.pass_context
|
|
941
|
+
@p.project_dir
|
|
942
|
+
def metadata_export(ctx, project_dir, **kwargs):
|
|
943
|
+
"""Display metadata in CLI.
|
|
944
|
+
|
|
945
|
+
Shows a Rich-formatted table of all captured metadata including:
|
|
946
|
+
- Source/Model type
|
|
947
|
+
- Table names
|
|
948
|
+
- Column counts
|
|
949
|
+
- Last updated timestamp
|
|
950
|
+
|
|
951
|
+
Example:
|
|
952
|
+
dvt metadata export
|
|
953
|
+
"""
|
|
954
|
+
from dbt.task.metadata import MetadataTask
|
|
955
|
+
|
|
956
|
+
class Args:
|
|
957
|
+
def __init__(self):
|
|
958
|
+
self.subcommand = 'export'
|
|
959
|
+
self.project_dir = project_dir
|
|
960
|
+
|
|
961
|
+
task = MetadataTask(Args())
|
|
962
|
+
success, _ = task.run()
|
|
963
|
+
return None, success
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
@metadata.command("export-csv")
|
|
967
|
+
@click.argument("filename", default="metadata.csv")
|
|
968
|
+
@click.pass_context
|
|
969
|
+
@p.project_dir
|
|
970
|
+
def metadata_export_csv(ctx, filename, project_dir, **kwargs):
|
|
971
|
+
"""Export metadata to CSV file.
|
|
972
|
+
|
|
973
|
+
Exports all column metadata to a CSV file with columns:
|
|
974
|
+
type, source_name, table_name, column_name, adapter_type,
|
|
975
|
+
spark_type, is_nullable, is_primary_key, ordinal_position, last_refreshed
|
|
976
|
+
|
|
977
|
+
Examples:
|
|
978
|
+
dvt metadata export-csv # Creates metadata.csv
|
|
979
|
+
dvt metadata export-csv my_export.csv # Custom filename
|
|
980
|
+
"""
|
|
981
|
+
from dbt.task.metadata import MetadataTask
|
|
982
|
+
|
|
983
|
+
class Args:
|
|
984
|
+
def __init__(self):
|
|
985
|
+
self.subcommand = 'export-csv'
|
|
986
|
+
self.project_dir = project_dir
|
|
987
|
+
self.filename = filename
|
|
988
|
+
|
|
989
|
+
task = MetadataTask(Args())
|
|
990
|
+
success, _ = task.run()
|
|
991
|
+
return None, success
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
@metadata.command("export-json")
|
|
995
|
+
@click.argument("filename", default="metadata.json")
|
|
996
|
+
@click.pass_context
|
|
997
|
+
@p.project_dir
|
|
998
|
+
def metadata_export_json(ctx, filename, project_dir, **kwargs):
|
|
999
|
+
"""Export metadata to JSON file.
|
|
1000
|
+
|
|
1001
|
+
Exports all metadata to a JSON file with structured format:
|
|
1002
|
+
- sources: grouped by source name with tables and columns
|
|
1003
|
+
- models: grouped by model name with columns
|
|
1004
|
+
|
|
1005
|
+
Examples:
|
|
1006
|
+
dvt metadata export-json # Creates metadata.json
|
|
1007
|
+
dvt metadata export-json my_export.json # Custom filename
|
|
1008
|
+
"""
|
|
1009
|
+
from dbt.task.metadata import MetadataTask
|
|
1010
|
+
|
|
1011
|
+
class Args:
|
|
1012
|
+
def __init__(self):
|
|
1013
|
+
self.subcommand = 'export-json'
|
|
1014
|
+
self.project_dir = project_dir
|
|
1015
|
+
self.filename = filename
|
|
1016
|
+
|
|
1017
|
+
task = MetadataTask(Args())
|
|
1018
|
+
success, _ = task.run()
|
|
1019
|
+
return None, success
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
# DVT profile command group (v0.58.0) - profiling + web UI
|
|
1023
|
+
@cli.group("profile")
|
|
1024
|
+
@click.pass_context
|
|
1025
|
+
@global_flags
|
|
1026
|
+
def profile(ctx, **kwargs):
|
|
1027
|
+
"""Profile data sources and models, or serve the profile viewer.
|
|
1028
|
+
|
|
1029
|
+
\b
|
|
1030
|
+
Commands:
|
|
1031
|
+
run - Run data profiling (default)
|
|
1032
|
+
serve - Start profile viewer web UI
|
|
1033
|
+
|
|
1034
|
+
\b
|
|
1035
|
+
Examples:
|
|
1036
|
+
dvt profile run # Profile all sources
|
|
1037
|
+
dvt profile run --mode minimal # Basic stats only
|
|
1038
|
+
dvt profile serve # Start web UI at http://localhost:8580
|
|
1039
|
+
"""
|
|
1040
|
+
pass
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
@profile.command("run")
|
|
1044
|
+
@click.pass_context
|
|
1045
|
+
@global_flags
|
|
1046
|
+
@p.exclude
|
|
1047
|
+
@p.profiles_dir
|
|
1048
|
+
@p.project_dir
|
|
1049
|
+
@p.select
|
|
1050
|
+
@p.selector
|
|
1051
|
+
@p.profile_mode
|
|
1052
|
+
@p.threads
|
|
1053
|
+
@p.vars
|
|
1054
|
+
@requires.postflight
|
|
1055
|
+
@requires.preflight
|
|
1056
|
+
@requires.profile
|
|
1057
|
+
@requires.project
|
|
1058
|
+
@requires.runtime_config
|
|
1059
|
+
@requires.manifest
|
|
1060
|
+
def profile_run(ctx, **kwargs):
|
|
1061
|
+
"""Run data profiling on sources and models.
|
|
1062
|
+
|
|
1063
|
+
Works like 'dvt run' with DAG-based execution:
|
|
1064
|
+
- Respects --select and --exclude selectors
|
|
1065
|
+
- Respects --target and --target-compute overrides
|
|
1066
|
+
- Follows DVT compute rules (pushdown when possible)
|
|
1067
|
+
|
|
1068
|
+
\b
|
|
1069
|
+
Modes (--mode):
|
|
1070
|
+
- explorative: Full profiling with distributions [DEFAULT]
|
|
1071
|
+
- minimal: Basic stats (null%, distinct%, min/max)
|
|
1072
|
+
- sensitive: Redacted profiling (masks PII columns)
|
|
1073
|
+
- time-series: Temporal analysis
|
|
1074
|
+
|
|
1075
|
+
\b
|
|
1076
|
+
Examples:
|
|
1077
|
+
dvt profile run # Profile all sources
|
|
1078
|
+
dvt profile run --select "source:*" # Profile all sources
|
|
1079
|
+
dvt profile run --mode minimal # Basic stats only
|
|
1080
|
+
dvt profile run --target-compute spark # Use Spark
|
|
1081
|
+
|
|
1082
|
+
Results are saved to: .dvt/metadata_store.duckdb
|
|
1083
|
+
"""
|
|
1084
|
+
from dbt.task.profile import ProfileTask
|
|
1085
|
+
|
|
1086
|
+
task = ProfileTask(
|
|
1087
|
+
ctx.obj["flags"],
|
|
1088
|
+
ctx.obj["runtime_config"],
|
|
1089
|
+
ctx.obj["manifest"],
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
results = task.run()
|
|
1093
|
+
success = task.interpret_results(results)
|
|
1094
|
+
return results, success
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
@profile.command("serve")
|
|
1098
|
+
@click.option("--port", default=8580, type=int, help="Port number for the profile viewer server")
|
|
1099
|
+
@click.option("--host", default="localhost", help="Host address to bind the server")
|
|
1100
|
+
@click.option("--no-browser", "no_browser", is_flag=True, default=False, help="Don't auto-open browser")
|
|
1101
|
+
@click.option("--project-dir", default=".", help="Project directory (defaults to current directory)")
|
|
1102
|
+
def profile_serve(port, host, no_browser, project_dir, **kwargs):
|
|
1103
|
+
"""Start the profile viewer web UI.
|
|
1104
|
+
|
|
1105
|
+
Opens an interactive web interface to explore profiling results
|
|
1106
|
+
stored in .dvt/metadata_store.duckdb.
|
|
1107
|
+
|
|
1108
|
+
\b
|
|
1109
|
+
Features:
|
|
1110
|
+
- Summary statistics (tables, columns, sources, models)
|
|
1111
|
+
- Table browser with column details
|
|
1112
|
+
- Type mappings (adapter types to Spark types)
|
|
1113
|
+
- Beautiful dark theme UI
|
|
1114
|
+
|
|
1115
|
+
\b
|
|
1116
|
+
Examples:
|
|
1117
|
+
dvt profile serve # Start on http://localhost:8580
|
|
1118
|
+
dvt profile serve --port 9000 # Custom port
|
|
1119
|
+
dvt profile serve --no-browser # Don't auto-open browser
|
|
1120
|
+
|
|
1121
|
+
Note: Run 'dvt profile run' first to capture profiling data.
|
|
1122
|
+
"""
|
|
1123
|
+
from pathlib import Path
|
|
1124
|
+
from dbt.task.profile_serve import serve_profile_ui
|
|
1125
|
+
|
|
1126
|
+
project_path = Path(project_dir)
|
|
1127
|
+
success = serve_profile_ui(
|
|
1128
|
+
project_dir=project_path,
|
|
1129
|
+
port=port,
|
|
1130
|
+
host=host,
|
|
1131
|
+
open_browser=not no_browser,
|
|
1132
|
+
)
|
|
1133
|
+
return None, success
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
# DVT retract command - drop materialized models (v0.58.0)
|
|
1137
|
+
@cli.command("retract")
|
|
1138
|
+
@click.pass_context
|
|
1139
|
+
@global_flags
|
|
1140
|
+
@p.dry_run
|
|
1141
|
+
@p.exclude
|
|
1142
|
+
@p.profiles_dir
|
|
1143
|
+
@p.project_dir
|
|
1144
|
+
@p.select
|
|
1145
|
+
@p.selector
|
|
1146
|
+
@p.target_path
|
|
1147
|
+
@p.threads
|
|
1148
|
+
@p.vars
|
|
1149
|
+
@requires.postflight
|
|
1150
|
+
@requires.preflight
|
|
1151
|
+
@requires.profile
|
|
1152
|
+
@requires.project
|
|
1153
|
+
@requires.runtime_config
|
|
1154
|
+
@requires.manifest
|
|
1155
|
+
def retract(ctx, **kwargs):
|
|
1156
|
+
"""Drop all materialized models from target databases.
|
|
1157
|
+
|
|
1158
|
+
Removes tables and views created by DVT from the target databases.
|
|
1159
|
+
Sources are never dropped. Use this to clean up a project or reset state.
|
|
1160
|
+
|
|
1161
|
+
\b
|
|
1162
|
+
WARNING: This command permanently deletes data. Use --dry-run first!
|
|
1163
|
+
|
|
1164
|
+
\b
|
|
1165
|
+
Examples:
|
|
1166
|
+
dvt retract --dry-run # Preview what would be dropped
|
|
1167
|
+
dvt retract # Drop all materialized models
|
|
1168
|
+
dvt retract --select "dim_*" # Drop matching models only
|
|
1169
|
+
dvt retract --exclude "fact_*" # Keep matching models
|
|
1170
|
+
dvt retract --target prod # Drop from specific target
|
|
1171
|
+
"""
|
|
1172
|
+
from dbt.task.retract import RetractTask
|
|
1173
|
+
|
|
1174
|
+
task = RetractTask(
|
|
1175
|
+
ctx.obj["flags"],
|
|
1176
|
+
ctx.obj["runtime_config"],
|
|
1177
|
+
ctx.obj["manifest"],
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
results = task.run()
|
|
1181
|
+
success = task.interpret_results(results)
|
|
1182
|
+
return results, success
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
# DVT compute commands
|
|
1186
|
+
@cli.group()
|
|
1187
|
+
@click.pass_context
|
|
1188
|
+
@p.version
|
|
1189
|
+
def compute(ctx, **kwargs):
|
|
1190
|
+
"""Manage DVT compute engines for multi-source federation.
|
|
1191
|
+
|
|
1192
|
+
Compute engines are configured in .dvt/computes.yml.
|
|
1193
|
+
|
|
1194
|
+
\b
|
|
1195
|
+
Commands:
|
|
1196
|
+
list - List all configured compute engines
|
|
1197
|
+
test - Test a compute engine's connectivity
|
|
1198
|
+
edit - Open computes.yml in your editor
|
|
1199
|
+
validate - Validate computes.yml syntax
|
|
1200
|
+
|
|
1201
|
+
\b
|
|
1202
|
+
Examples:
|
|
1203
|
+
dvt compute list # List all engines
|
|
1204
|
+
dvt compute test spark-local # Test local Spark
|
|
1205
|
+
dvt compute edit # Edit configuration
|
|
1206
|
+
"""
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
@compute.command("list")
|
|
1210
|
+
@click.pass_context
|
|
1211
|
+
@p.project_dir
|
|
1212
|
+
def compute_list(ctx, **kwargs):
|
|
1213
|
+
"""List all configured compute engines.
|
|
1214
|
+
|
|
1215
|
+
Shows all compute engines defined in computes.yml with their
|
|
1216
|
+
platform type and description.
|
|
1217
|
+
|
|
1218
|
+
Examples:
|
|
1219
|
+
dvt compute list # List all compute engines
|
|
1220
|
+
"""
|
|
1221
|
+
from dbt.task.compute import ComputeTask
|
|
1222
|
+
|
|
1223
|
+
task = ComputeTask(project_dir=kwargs.get("project_dir"))
|
|
1224
|
+
success = task.list_computes()
|
|
1225
|
+
return None, success
|
|
1226
|
+
|
|
1227
|
+
|
|
1228
|
+
@compute.command("test")
|
|
1229
|
+
@click.pass_context
|
|
1230
|
+
@click.argument("compute_name", required=True)
|
|
1231
|
+
@p.project_dir
|
|
1232
|
+
def compute_test(ctx, compute_name, **kwargs):
|
|
1233
|
+
"""Test a compute engine's connectivity.
|
|
1234
|
+
|
|
1235
|
+
Tests the specified compute engine and shows its status.
|
|
1236
|
+
Use 'dvt compute list' to see all available compute engines.
|
|
1237
|
+
|
|
1238
|
+
Shows rich status symbols:
|
|
1239
|
+
✅ Connected/Available
|
|
1240
|
+
❌ Error/Not available
|
|
1241
|
+
⚠️ Warning (missing optional dependency)
|
|
1242
|
+
|
|
1243
|
+
Examples:
|
|
1244
|
+
dvt compute test spark-local # Test local Spark
|
|
1245
|
+
dvt compute test databricks-prod # Test Databricks connectivity
|
|
1246
|
+
dvt compute test spark-docker # Test Docker Spark cluster
|
|
1247
|
+
"""
|
|
1248
|
+
from dbt.task.compute import ComputeTask
|
|
1249
|
+
|
|
1250
|
+
task = ComputeTask(project_dir=kwargs.get("project_dir"))
|
|
1251
|
+
success = task.test_single_compute(compute_name)
|
|
1252
|
+
return None, success
|
|
1253
|
+
|
|
1254
|
+
|
|
1255
|
+
@compute.command("edit")
|
|
1256
|
+
@click.pass_context
|
|
1257
|
+
@p.project_dir
|
|
1258
|
+
def compute_edit(ctx, **kwargs):
|
|
1259
|
+
"""Open computes.yml in your editor.
|
|
1260
|
+
|
|
1261
|
+
Opens the compute configuration file in your preferred editor.
|
|
1262
|
+
Uses EDITOR environment variable, or falls back to common editors
|
|
1263
|
+
(code, nano, vim, vi, notepad).
|
|
1264
|
+
|
|
1265
|
+
The file contains comprehensive commented samples for:
|
|
1266
|
+
- Local Spark (default)
|
|
1267
|
+
- Databricks (SQL Warehouse and Interactive Cluster)
|
|
1268
|
+
- AWS EMR
|
|
1269
|
+
- GCP Dataproc
|
|
1270
|
+
- Standalone Spark clusters
|
|
1271
|
+
|
|
1272
|
+
After editing, run 'dvt compute validate' to check syntax.
|
|
1273
|
+
|
|
1274
|
+
Examples:
|
|
1275
|
+
dvt compute edit # Open in default editor
|
|
1276
|
+
EDITOR=nano dvt compute edit # Use specific editor
|
|
1277
|
+
"""
|
|
1278
|
+
from dbt.task.compute import ComputeTask
|
|
1279
|
+
|
|
1280
|
+
task = ComputeTask(project_dir=kwargs.get("project_dir"))
|
|
1281
|
+
success = task.edit_config()
|
|
1282
|
+
return None, success
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
@compute.command("validate")
|
|
1286
|
+
@click.pass_context
|
|
1287
|
+
@p.project_dir
|
|
1288
|
+
def compute_validate(ctx, **kwargs):
|
|
1289
|
+
"""Validate computes.yml syntax and configuration.
|
|
1290
|
+
|
|
1291
|
+
Checks the compute configuration file for:
|
|
1292
|
+
- Valid YAML syntax
|
|
1293
|
+
- Required fields (target_compute, type)
|
|
1294
|
+
- Valid compute engine references
|
|
1295
|
+
- Platform-specific configuration
|
|
1296
|
+
|
|
1297
|
+
Examples:
|
|
1298
|
+
dvt compute validate # Validate configuration
|
|
1299
|
+
"""
|
|
1300
|
+
from dbt.task.compute import ComputeTask
|
|
1301
|
+
|
|
1302
|
+
task = ComputeTask(project_dir=kwargs.get("project_dir"))
|
|
1303
|
+
is_valid = task.validate_config()
|
|
1304
|
+
return None, is_valid
|
|
1305
|
+
|
|
1306
|
+
|
|
1307
|
+
|
|
1308
|
+
|
|
1309
|
+
# DVT migrate command - migrate from .dbt/ to .dvt/
|
|
1310
|
+
@cli.command("migrate")
|
|
1311
|
+
@click.pass_context
|
|
1312
|
+
@p.profiles_dir
|
|
1313
|
+
@p.project_dir
|
|
1314
|
+
def migrate(ctx, **kwargs):
|
|
1315
|
+
"""Migrate configuration from .dbt/ to .dvt/ directory"""
|
|
1316
|
+
from pathlib import Path
|
|
1317
|
+
import shutil
|
|
1318
|
+
|
|
1319
|
+
home = Path.home()
|
|
1320
|
+
|
|
1321
|
+
old_dbt_dir = home / ".dbt"
|
|
1322
|
+
new_dvt_dir = home / ".dvt"
|
|
1323
|
+
|
|
1324
|
+
if not old_dbt_dir.exists():
|
|
1325
|
+
click.echo("No .dbt/ directory found - nothing to migrate")
|
|
1326
|
+
return True, True
|
|
1327
|
+
|
|
1328
|
+
if new_dvt_dir.exists():
|
|
1329
|
+
response = click.confirm(
|
|
1330
|
+
f".dvt/ directory already exists at {new_dvt_dir}. Overwrite files?"
|
|
1331
|
+
)
|
|
1332
|
+
if not response:
|
|
1333
|
+
click.echo("Migration cancelled")
|
|
1334
|
+
return False, False
|
|
1335
|
+
|
|
1336
|
+
# Create .dvt directory
|
|
1337
|
+
new_dvt_dir.mkdir(parents=True, exist_ok=True)
|
|
1338
|
+
|
|
1339
|
+
# Copy profiles.yml if it exists
|
|
1340
|
+
old_profiles = old_dbt_dir / "profiles.yml"
|
|
1341
|
+
new_profiles = new_dvt_dir / "profiles.yml"
|
|
1342
|
+
|
|
1343
|
+
migrated_files = []
|
|
1344
|
+
|
|
1345
|
+
if old_profiles.exists():
|
|
1346
|
+
shutil.copy2(old_profiles, new_profiles)
|
|
1347
|
+
migrated_files.append("profiles.yml")
|
|
1348
|
+
click.echo(f"✓ Copied {old_profiles} → {new_profiles}")
|
|
1349
|
+
|
|
1350
|
+
if migrated_files:
|
|
1351
|
+
click.echo("")
|
|
1352
|
+
click.echo(f"Migration complete! Migrated {len(migrated_files)} files:")
|
|
1353
|
+
for f in migrated_files:
|
|
1354
|
+
click.echo(f" - {f}")
|
|
1355
|
+
click.echo("")
|
|
1356
|
+
click.echo("Your .dbt/ directory has been preserved.")
|
|
1357
|
+
click.echo("DVT will now use .dvt/ for all configurations.")
|
|
1358
|
+
click.echo("")
|
|
1359
|
+
click.echo("Note: Compute engines are managed via 'dvt compute' commands.")
|
|
1360
|
+
else:
|
|
1361
|
+
click.echo("No files to migrate")
|
|
1362
|
+
|
|
1363
|
+
return True, True
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
# DVT target (connection) management commands
|
|
1367
|
+
@cli.group()
|
|
1368
|
+
@click.pass_context
|
|
1369
|
+
@p.version
|
|
1370
|
+
def target(ctx, **kwargs):
|
|
1371
|
+
"""Manage connection targets in profiles.yml.
|
|
1372
|
+
|
|
1373
|
+
\b
|
|
1374
|
+
Commands:
|
|
1375
|
+
list - List available targets
|
|
1376
|
+
test - Test connection to one or all targets
|
|
1377
|
+
add - Add a new target to a profile
|
|
1378
|
+
remove - Remove a target from a profile
|
|
1379
|
+
sync - Sync adapters and JDBC JARs
|
|
1380
|
+
|
|
1381
|
+
\b
|
|
1382
|
+
Examples:
|
|
1383
|
+
dvt target list # List all targets
|
|
1384
|
+
dvt target test # Test all connections
|
|
1385
|
+
dvt target test dev # Test specific target
|
|
1386
|
+
"""
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
@target.command("list")
|
|
1390
|
+
@click.option("--profile", help="Profile name to list targets from")
|
|
1391
|
+
@click.pass_context
|
|
1392
|
+
@p.profiles_dir
|
|
1393
|
+
@p.project_dir
|
|
1394
|
+
def target_list(ctx, profile, profiles_dir, project_dir, **kwargs):
|
|
1395
|
+
"""List available targets in profiles.yml.
|
|
1396
|
+
|
|
1397
|
+
If executed from within a DVT project directory, automatically detects the profile
|
|
1398
|
+
from dbt_project.yml. Use --profile to override or when outside a project directory.
|
|
1399
|
+
|
|
1400
|
+
Features colored output for improved readability:
|
|
1401
|
+
- Cyan: Profile and target names
|
|
1402
|
+
- Green: Default target indicators
|
|
1403
|
+
- Red: Error messages
|
|
1404
|
+
|
|
1405
|
+
Examples:
|
|
1406
|
+
dvt target list # Auto-detect from project
|
|
1407
|
+
dvt target list --profile my_proj # Specific profile
|
|
1408
|
+
"""
|
|
1409
|
+
from dbt.config.profile import read_profile
|
|
1410
|
+
from dbt.config.project_utils import get_project_profile_name
|
|
1411
|
+
|
|
1412
|
+
profiles = read_profile(profiles_dir)
|
|
1413
|
+
|
|
1414
|
+
if not profiles:
|
|
1415
|
+
click.echo(click.style("No profiles found in profiles.yml", fg="red"))
|
|
1416
|
+
ctx.exit(1)
|
|
1417
|
+
|
|
1418
|
+
# If --profile not provided, try to get from dbt_project.yml
|
|
1419
|
+
if not profile:
|
|
1420
|
+
profile = get_project_profile_name(project_dir)
|
|
1421
|
+
if not profile:
|
|
1422
|
+
# No profile specified and none found in project - show all profiles
|
|
1423
|
+
click.echo(click.style("Available profiles:", fg="cyan", bold=True))
|
|
1424
|
+
click.echo("")
|
|
1425
|
+
for profile_name, profile_data in profiles.items():
|
|
1426
|
+
if profile_data is None:
|
|
1427
|
+
profile_data = {}
|
|
1428
|
+
outputs = profile_data.get("outputs", {})
|
|
1429
|
+
target_count = len(outputs)
|
|
1430
|
+
default_target = profile_data.get(
|
|
1431
|
+
"default_target", profile_data.get("target", "unknown")
|
|
1432
|
+
)
|
|
1433
|
+
click.echo(f" {click.style(profile_name, fg='cyan')}")
|
|
1434
|
+
click.echo(
|
|
1435
|
+
f" Default target: {click.style(default_target, fg='green')}"
|
|
1436
|
+
)
|
|
1437
|
+
click.echo(f" Targets: {target_count}")
|
|
1438
|
+
click.echo("")
|
|
1439
|
+
return True, True
|
|
1440
|
+
|
|
1441
|
+
# Show targets for specific profile
|
|
1442
|
+
if profile not in profiles:
|
|
1443
|
+
click.echo(click.style(f"✗ Profile '{profile}' not found", fg="red"))
|
|
1444
|
+
ctx.exit(1)
|
|
1445
|
+
|
|
1446
|
+
profile_data = profiles[profile]
|
|
1447
|
+
if profile_data is None:
|
|
1448
|
+
click.echo(click.style(f"✗ Profile '{profile}' is empty or invalid", fg="red"))
|
|
1449
|
+
ctx.exit(1)
|
|
1450
|
+
outputs = profile_data.get("outputs", {})
|
|
1451
|
+
|
|
1452
|
+
if not outputs:
|
|
1453
|
+
click.echo(click.style(f"No targets found in profile '{profile}'", fg="yellow"))
|
|
1454
|
+
return True, True
|
|
1455
|
+
|
|
1456
|
+
default_target = profile_data.get(
|
|
1457
|
+
"default_target", profile_data.get("target", "unknown")
|
|
1458
|
+
)
|
|
1459
|
+
|
|
1460
|
+
# Always show profile name header for context
|
|
1461
|
+
click.echo(click.style(f"Profile: {profile}", fg="cyan", bold=True))
|
|
1462
|
+
click.echo(f"Default target: {click.style(default_target, fg='green')}")
|
|
1463
|
+
click.echo("")
|
|
1464
|
+
click.echo("Available targets:")
|
|
1465
|
+
for target_name, target_config in outputs.items():
|
|
1466
|
+
default_marker = (
|
|
1467
|
+
click.style(" (default)", fg="green")
|
|
1468
|
+
if target_name == default_target
|
|
1469
|
+
else ""
|
|
1470
|
+
)
|
|
1471
|
+
adapter_type = target_config.get("type", "unknown")
|
|
1472
|
+
click.echo(
|
|
1473
|
+
f" {click.style(target_name, fg='cyan')} ({adapter_type}){default_marker}"
|
|
1474
|
+
)
|
|
1475
|
+
|
|
1476
|
+
return True, True
|
|
1477
|
+
|
|
1478
|
+
|
|
1479
|
+
def _get_test_query(adapter_type: str) -> str:
|
|
1480
|
+
"""Get adapter-specific test query for connection validation.
|
|
1481
|
+
|
|
1482
|
+
Args:
|
|
1483
|
+
adapter_type: The adapter type (postgres, snowflake, etc.)
|
|
1484
|
+
|
|
1485
|
+
Returns:
|
|
1486
|
+
SQL query string for testing connectivity
|
|
1487
|
+
"""
|
|
1488
|
+
test_queries = {
|
|
1489
|
+
"postgres": "SELECT 1",
|
|
1490
|
+
"snowflake": "SELECT CURRENT_VERSION()",
|
|
1491
|
+
"bigquery": "SELECT 1",
|
|
1492
|
+
"redshift": "SELECT 1",
|
|
1493
|
+
"databricks": "SELECT 1",
|
|
1494
|
+
"mysql": "SELECT 1",
|
|
1495
|
+
"sqlserver": "SELECT 1",
|
|
1496
|
+
"oracle": "SELECT 1 FROM DUAL",
|
|
1497
|
+
"db2": "SELECT 1 FROM SYSIBM.SYSDUMMY1",
|
|
1498
|
+
"teradata": "SELECT 1",
|
|
1499
|
+
}
|
|
1500
|
+
return test_queries.get(adapter_type, "SELECT 1")
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
def _get_connection_error_hint(exception: Exception, adapter_type: str) -> str:
|
|
1504
|
+
"""Provide user-friendly hints for common connection errors.
|
|
1505
|
+
|
|
1506
|
+
Args:
|
|
1507
|
+
exception: The exception that was raised
|
|
1508
|
+
adapter_type: The adapter type being tested
|
|
1509
|
+
|
|
1510
|
+
Returns:
|
|
1511
|
+
A helpful error message with troubleshooting hints
|
|
1512
|
+
"""
|
|
1513
|
+
error_str = str(exception).lower()
|
|
1514
|
+
|
|
1515
|
+
# Common error patterns and hints
|
|
1516
|
+
if "timeout" in error_str or "timed out" in error_str:
|
|
1517
|
+
return "Connection timeout - Check network connectivity and firewall rules"
|
|
1518
|
+
elif "could not connect" in error_str or "connection refused" in error_str:
|
|
1519
|
+
return "Connection refused - Verify host and port are correct"
|
|
1520
|
+
elif (
|
|
1521
|
+
"authentication" in error_str or "password" in error_str or "login" in error_str
|
|
1522
|
+
):
|
|
1523
|
+
return "Authentication failed - Check username and password"
|
|
1524
|
+
elif "database" in error_str and "does not exist" in error_str:
|
|
1525
|
+
return "Database not found - Verify database name"
|
|
1526
|
+
elif "permission" in error_str or "access denied" in error_str:
|
|
1527
|
+
return "Permission denied - Check user privileges"
|
|
1528
|
+
elif "ssl" in error_str or "certificate" in error_str:
|
|
1529
|
+
return "SSL/TLS error - Check SSL configuration"
|
|
1530
|
+
elif "no such host" in error_str or "name resolution" in error_str:
|
|
1531
|
+
return "Host not found - Verify hostname is correct"
|
|
1532
|
+
|
|
1533
|
+
# Adapter-specific hints
|
|
1534
|
+
if adapter_type == "snowflake":
|
|
1535
|
+
if "account" in error_str:
|
|
1536
|
+
return "Invalid Snowflake account - Check account identifier format"
|
|
1537
|
+
elif "warehouse" in error_str:
|
|
1538
|
+
return "Warehouse error - Verify warehouse name and status"
|
|
1539
|
+
elif adapter_type == "databricks":
|
|
1540
|
+
if "token" in error_str:
|
|
1541
|
+
return "Invalid token - Check Databricks access token"
|
|
1542
|
+
elif "cluster" in error_str:
|
|
1543
|
+
return "Cluster error - Verify cluster is running and accessible"
|
|
1544
|
+
|
|
1545
|
+
return "Connection failed - See error details above"
|
|
1546
|
+
|
|
1547
|
+
|
|
1548
|
+
def _test_single_target(profile_data: dict, target_name: str) -> tuple[bool, str]:
|
|
1549
|
+
"""Test connection to a single target by executing a query.
|
|
1550
|
+
|
|
1551
|
+
Args:
|
|
1552
|
+
profile_data: The profile dictionary containing outputs
|
|
1553
|
+
target_name: Name of the target to test
|
|
1554
|
+
|
|
1555
|
+
Returns:
|
|
1556
|
+
Tuple of (success: bool, message: str)
|
|
1557
|
+
"""
|
|
1558
|
+
outputs = profile_data.get("outputs", {})
|
|
1559
|
+
|
|
1560
|
+
if target_name not in outputs:
|
|
1561
|
+
return False, f"Target '{target_name}' not found"
|
|
1562
|
+
|
|
1563
|
+
target_config = outputs[target_name]
|
|
1564
|
+
adapter_type = target_config.get("type", "unknown")
|
|
1565
|
+
|
|
1566
|
+
# Validate required fields first
|
|
1567
|
+
required_fields = {
|
|
1568
|
+
"postgres": ["host", "user", "database"],
|
|
1569
|
+
"snowflake": ["account", "user", "database", "warehouse"],
|
|
1570
|
+
"bigquery": ["project", "dataset"],
|
|
1571
|
+
"redshift": ["host", "user", "database"],
|
|
1572
|
+
"databricks": ["host", "http_path"],
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
missing_fields = []
|
|
1576
|
+
if adapter_type in required_fields:
|
|
1577
|
+
for field in required_fields[adapter_type]:
|
|
1578
|
+
if field not in target_config:
|
|
1579
|
+
missing_fields.append(field)
|
|
1580
|
+
|
|
1581
|
+
if missing_fields:
|
|
1582
|
+
return False, f"Missing required fields: {', '.join(missing_fields)}"
|
|
1583
|
+
|
|
1584
|
+
# Import adapter and test connection
|
|
1585
|
+
try:
|
|
1586
|
+
# Get test query for this adapter
|
|
1587
|
+
test_query = _get_test_query(adapter_type)
|
|
1588
|
+
|
|
1589
|
+
# Use the adapter's native connection testing approach
|
|
1590
|
+
# Each adapter has different connection methods, so we'll use
|
|
1591
|
+
# a simplified approach that works across adapters
|
|
1592
|
+
|
|
1593
|
+
if adapter_type == "postgres":
|
|
1594
|
+
try:
|
|
1595
|
+
import psycopg2
|
|
1596
|
+
|
|
1597
|
+
# Build connection string
|
|
1598
|
+
conn_params = {
|
|
1599
|
+
"host": target_config.get("host", "localhost"),
|
|
1600
|
+
"port": target_config.get("port", 5432),
|
|
1601
|
+
"database": target_config.get(
|
|
1602
|
+
"database", target_config.get("dbname")
|
|
1603
|
+
),
|
|
1604
|
+
"user": target_config.get("user"),
|
|
1605
|
+
"password": target_config.get("password"),
|
|
1606
|
+
}
|
|
1607
|
+
# Test connection
|
|
1608
|
+
conn = psycopg2.connect(**conn_params, connect_timeout=10)
|
|
1609
|
+
cursor = conn.cursor()
|
|
1610
|
+
cursor.execute(test_query)
|
|
1611
|
+
cursor.fetchone()
|
|
1612
|
+
cursor.close()
|
|
1613
|
+
conn.close()
|
|
1614
|
+
return True, "Connection successful"
|
|
1615
|
+
except ImportError:
|
|
1616
|
+
return (
|
|
1617
|
+
False,
|
|
1618
|
+
"psycopg2 not installed - Run 'pip install psycopg2-binary' or 'pip install dbt-postgres'",
|
|
1619
|
+
)
|
|
1620
|
+
except Exception as e:
|
|
1621
|
+
hint = _get_connection_error_hint(e, adapter_type)
|
|
1622
|
+
return False, f"Connection failed: {str(e)}\nHint: {hint}"
|
|
1623
|
+
|
|
1624
|
+
elif adapter_type == "snowflake":
|
|
1625
|
+
try:
|
|
1626
|
+
import snowflake.connector
|
|
1627
|
+
|
|
1628
|
+
# Build connection params
|
|
1629
|
+
conn_params = {
|
|
1630
|
+
"account": target_config.get("account"),
|
|
1631
|
+
"user": target_config.get("user"),
|
|
1632
|
+
"password": target_config.get("password"),
|
|
1633
|
+
"database": target_config.get("database"),
|
|
1634
|
+
"warehouse": target_config.get("warehouse"),
|
|
1635
|
+
"schema": target_config.get("schema", "PUBLIC"),
|
|
1636
|
+
}
|
|
1637
|
+
# Test connection
|
|
1638
|
+
conn = snowflake.connector.connect(**conn_params, login_timeout=10)
|
|
1639
|
+
cursor = conn.cursor()
|
|
1640
|
+
cursor.execute(test_query)
|
|
1641
|
+
cursor.fetchone()
|
|
1642
|
+
cursor.close()
|
|
1643
|
+
conn.close()
|
|
1644
|
+
return True, "Connection successful"
|
|
1645
|
+
except ImportError:
|
|
1646
|
+
return (
|
|
1647
|
+
False,
|
|
1648
|
+
"snowflake-connector-python not installed - Run 'pip install snowflake-connector-python' or 'pip install dbt-snowflake'",
|
|
1649
|
+
)
|
|
1650
|
+
except Exception as e:
|
|
1651
|
+
hint = _get_connection_error_hint(e, adapter_type)
|
|
1652
|
+
return False, f"Connection failed: {str(e)}\nHint: {hint}"
|
|
1653
|
+
|
|
1654
|
+
elif adapter_type == "bigquery":
|
|
1655
|
+
try:
|
|
1656
|
+
from google.cloud import bigquery
|
|
1657
|
+
|
|
1658
|
+
# Build client
|
|
1659
|
+
project = target_config.get("project")
|
|
1660
|
+
client = bigquery.Client(project=project)
|
|
1661
|
+
# Test connection with simple query
|
|
1662
|
+
query_job = client.query(test_query)
|
|
1663
|
+
query_job.result(timeout=10)
|
|
1664
|
+
return True, "Connection successful"
|
|
1665
|
+
except ImportError:
|
|
1666
|
+
return (
|
|
1667
|
+
False,
|
|
1668
|
+
"google-cloud-bigquery not installed - Run 'pip install google-cloud-bigquery' or 'pip install dbt-bigquery'",
|
|
1669
|
+
)
|
|
1670
|
+
except Exception as e:
|
|
1671
|
+
hint = _get_connection_error_hint(e, adapter_type)
|
|
1672
|
+
return False, f"Connection failed: {str(e)}\nHint: {hint}"
|
|
1673
|
+
|
|
1674
|
+
elif adapter_type == "redshift":
|
|
1675
|
+
try:
|
|
1676
|
+
import psycopg2
|
|
1677
|
+
|
|
1678
|
+
# Redshift uses postgres protocol
|
|
1679
|
+
conn_params = {
|
|
1680
|
+
"host": target_config.get("host"),
|
|
1681
|
+
"port": target_config.get("port", 5439),
|
|
1682
|
+
"database": target_config.get("database"),
|
|
1683
|
+
"user": target_config.get("user"),
|
|
1684
|
+
"password": target_config.get("password"),
|
|
1685
|
+
}
|
|
1686
|
+
# Test connection
|
|
1687
|
+
conn = psycopg2.connect(**conn_params, connect_timeout=10)
|
|
1688
|
+
cursor = conn.cursor()
|
|
1689
|
+
cursor.execute(test_query)
|
|
1690
|
+
cursor.fetchone()
|
|
1691
|
+
cursor.close()
|
|
1692
|
+
conn.close()
|
|
1693
|
+
return True, "Connection successful"
|
|
1694
|
+
except ImportError:
|
|
1695
|
+
return (
|
|
1696
|
+
False,
|
|
1697
|
+
"psycopg2 not installed - Run 'pip install psycopg2-binary' or 'pip install dbt-redshift'",
|
|
1698
|
+
)
|
|
1699
|
+
except Exception as e:
|
|
1700
|
+
hint = _get_connection_error_hint(e, adapter_type)
|
|
1701
|
+
return False, f"Connection failed: {str(e)}\nHint: {hint}"
|
|
1702
|
+
|
|
1703
|
+
elif adapter_type == "databricks":
|
|
1704
|
+
try:
|
|
1705
|
+
from databricks import sql
|
|
1706
|
+
|
|
1707
|
+
# Build connection params
|
|
1708
|
+
conn_params = {
|
|
1709
|
+
"server_hostname": target_config.get("host", "").replace(
|
|
1710
|
+
"https://", ""
|
|
1711
|
+
),
|
|
1712
|
+
"http_path": target_config.get("http_path"),
|
|
1713
|
+
"access_token": target_config.get("token"),
|
|
1714
|
+
}
|
|
1715
|
+
# Test connection
|
|
1716
|
+
conn = sql.connect(**conn_params)
|
|
1717
|
+
cursor = conn.cursor()
|
|
1718
|
+
cursor.execute(test_query)
|
|
1719
|
+
cursor.fetchone()
|
|
1720
|
+
cursor.close()
|
|
1721
|
+
conn.close()
|
|
1722
|
+
return True, "Connection successful"
|
|
1723
|
+
except ImportError:
|
|
1724
|
+
return (
|
|
1725
|
+
False,
|
|
1726
|
+
"databricks-sql-connector not installed - Run 'pip install databricks-sql-connector' or 'pip install dbt-databricks'",
|
|
1727
|
+
)
|
|
1728
|
+
except Exception as e:
|
|
1729
|
+
hint = _get_connection_error_hint(e, adapter_type)
|
|
1730
|
+
return False, f"Connection failed: {str(e)}\nHint: {hint}"
|
|
1731
|
+
|
|
1732
|
+
else:
|
|
1733
|
+
# For unsupported adapter types, just return configuration validation
|
|
1734
|
+
return (
|
|
1735
|
+
True,
|
|
1736
|
+
"Configuration valid (network testing not available for this adapter type)",
|
|
1737
|
+
)
|
|
1738
|
+
|
|
1739
|
+
except Exception as e:
|
|
1740
|
+
hint = _get_connection_error_hint(e, adapter_type)
|
|
1741
|
+
return False, f"Unexpected error: {str(e)}\nHint: {hint}"
|
|
1742
|
+
|
|
1743
|
+
|
|
1744
|
+
def _test_target_with_timeout(
|
|
1745
|
+
profile_data: dict, target_name: str, timeout: int = 30
|
|
1746
|
+
) -> tuple[bool, str]:
|
|
1747
|
+
"""Test target connection with timeout protection.
|
|
1748
|
+
|
|
1749
|
+
Args:
|
|
1750
|
+
profile_data: The profile dictionary
|
|
1751
|
+
target_name: Name of the target to test
|
|
1752
|
+
timeout: Timeout in seconds (default 30)
|
|
1753
|
+
|
|
1754
|
+
Returns:
|
|
1755
|
+
Tuple of (success: bool, message: str)
|
|
1756
|
+
"""
|
|
1757
|
+
with ThreadPoolExecutor(max_workers=1) as executor:
|
|
1758
|
+
future = executor.submit(_test_single_target, profile_data, target_name)
|
|
1759
|
+
try:
|
|
1760
|
+
success, message = future.result(timeout=timeout)
|
|
1761
|
+
return success, message
|
|
1762
|
+
except FuturesTimeoutError:
|
|
1763
|
+
return False, f"Connection test timed out after {timeout} seconds"
|
|
1764
|
+
except Exception as e:
|
|
1765
|
+
return False, f"Unexpected error during connection test: {str(e)}"
|
|
1766
|
+
|
|
1767
|
+
|
|
1768
|
+
@target.command("test")
|
|
1769
|
+
@click.argument("target_name", required=False, default=None)
|
|
1770
|
+
@click.option("--profile", help="Profile name (defaults to project profile)")
|
|
1771
|
+
@click.option(
|
|
1772
|
+
"--timeout",
|
|
1773
|
+
type=int,
|
|
1774
|
+
default=30,
|
|
1775
|
+
help="Connection timeout in seconds (default: 30)",
|
|
1776
|
+
)
|
|
1777
|
+
@click.pass_context
|
|
1778
|
+
@p.profiles_dir
|
|
1779
|
+
@p.project_dir
|
|
1780
|
+
def target_test(
|
|
1781
|
+
ctx, target_name, profile, timeout, profiles_dir, project_dir, **kwargs
|
|
1782
|
+
):
|
|
1783
|
+
"""Test connection to one or all targets.
|
|
1784
|
+
|
|
1785
|
+
When TARGET_NAME is provided: Tests connection to a specific target
|
|
1786
|
+
When TARGET_NAME is omitted: Tests all targets in the profile
|
|
1787
|
+
|
|
1788
|
+
This command now performs REAL connection testing by executing a simple query
|
|
1789
|
+
against the target database. It validates both configuration AND network connectivity.
|
|
1790
|
+
|
|
1791
|
+
Features colored output and proper exit codes:
|
|
1792
|
+
- Exit code 0: All connections succeeded
|
|
1793
|
+
- Exit code 1: One or more connections failed
|
|
1794
|
+
- Green checkmarks (✓): Success
|
|
1795
|
+
- Red X marks (✗): Errors
|
|
1796
|
+
|
|
1797
|
+
Examples:
|
|
1798
|
+
dvt target test # Test ALL targets (auto-detect profile)
|
|
1799
|
+
dvt target test dev # Test specific target (auto-detect profile)
|
|
1800
|
+
dvt target test prod --profile my_proj # Test specific target (explicit profile)
|
|
1801
|
+
dvt target test --profile my_proj # Test all targets in profile
|
|
1802
|
+
dvt target test dev --timeout 60 # Custom timeout
|
|
1803
|
+
|
|
1804
|
+
Performance:
|
|
1805
|
+
- Tests run with configurable timeout (default 30s)
|
|
1806
|
+
- Provides helpful error hints for common connection issues
|
|
1807
|
+
- Shows detailed connection information on success
|
|
1808
|
+
"""
|
|
1809
|
+
from dbt.config.profile import read_profile
|
|
1810
|
+
from dbt.config.project_utils import get_project_profile_name
|
|
1811
|
+
|
|
1812
|
+
profiles = read_profile(profiles_dir)
|
|
1813
|
+
|
|
1814
|
+
# Determine which profile to use
|
|
1815
|
+
if not profile:
|
|
1816
|
+
# Try to get from dbt_project.yml first
|
|
1817
|
+
profile = get_project_profile_name(project_dir)
|
|
1818
|
+
|
|
1819
|
+
if not profile:
|
|
1820
|
+
# If testing single target without profile, search all profiles
|
|
1821
|
+
if target_name:
|
|
1822
|
+
for prof_name, prof_data in profiles.items():
|
|
1823
|
+
outputs = prof_data.get("outputs", {}) if prof_data else {}
|
|
1824
|
+
if target_name in outputs:
|
|
1825
|
+
profile = prof_name
|
|
1826
|
+
break
|
|
1827
|
+
|
|
1828
|
+
if not profile:
|
|
1829
|
+
click.echo(
|
|
1830
|
+
click.style(
|
|
1831
|
+
"✗ Error: Could not determine profile. Use --profile flag.", fg="red"
|
|
1832
|
+
)
|
|
1833
|
+
)
|
|
1834
|
+
ctx.exit(1)
|
|
1835
|
+
|
|
1836
|
+
if profile not in profiles:
|
|
1837
|
+
click.echo(click.style(f"✗ Profile '{profile}' not found", fg="red"))
|
|
1838
|
+
ctx.exit(1)
|
|
1839
|
+
|
|
1840
|
+
profile_data = profiles[profile]
|
|
1841
|
+
if profile_data is None:
|
|
1842
|
+
click.echo(click.style(f"✗ Profile '{profile}' is empty or invalid", fg="red"))
|
|
1843
|
+
ctx.exit(1)
|
|
1844
|
+
outputs = profile_data.get("outputs", {})
|
|
1845
|
+
|
|
1846
|
+
if not outputs:
|
|
1847
|
+
click.echo(click.style(f"No targets found in profile '{profile}'", fg="yellow"))
|
|
1848
|
+
ctx.exit(0)
|
|
1849
|
+
|
|
1850
|
+
# CASE 1: Test specific target
|
|
1851
|
+
if target_name:
|
|
1852
|
+
if target_name not in outputs:
|
|
1853
|
+
click.echo(
|
|
1854
|
+
click.style(
|
|
1855
|
+
f"✗ Target '{target_name}' not found in profile '{profile}'",
|
|
1856
|
+
fg="red",
|
|
1857
|
+
)
|
|
1858
|
+
)
|
|
1859
|
+
ctx.exit(1)
|
|
1860
|
+
|
|
1861
|
+
target_config = outputs[target_name]
|
|
1862
|
+
adapter_type = target_config.get("type", "unknown")
|
|
1863
|
+
|
|
1864
|
+
click.echo(
|
|
1865
|
+
f"Testing connection: {click.style(target_name, fg='cyan')} ({adapter_type})"
|
|
1866
|
+
)
|
|
1867
|
+
|
|
1868
|
+
# Show connection details FIRST (like dbt debug)
|
|
1869
|
+
if "host" in target_config:
|
|
1870
|
+
click.echo(f" host: {target_config['host']}")
|
|
1871
|
+
if "port" in target_config:
|
|
1872
|
+
click.echo(f" port: {target_config['port']}")
|
|
1873
|
+
if "account" in target_config:
|
|
1874
|
+
click.echo(f" account: {target_config['account']}")
|
|
1875
|
+
if "database" in target_config or "dbname" in target_config:
|
|
1876
|
+
db = target_config.get("database") or target_config.get("dbname")
|
|
1877
|
+
click.echo(f" database: {db}")
|
|
1878
|
+
if "warehouse" in target_config:
|
|
1879
|
+
click.echo(f" warehouse: {target_config['warehouse']}")
|
|
1880
|
+
if "schema" in target_config:
|
|
1881
|
+
click.echo(f" schema: {target_config['schema']}")
|
|
1882
|
+
if "project" in target_config:
|
|
1883
|
+
click.echo(f" project: {target_config['project']}")
|
|
1884
|
+
|
|
1885
|
+
# Test connection with timeout
|
|
1886
|
+
success, message = _test_target_with_timeout(profile_data, target_name, timeout)
|
|
1887
|
+
|
|
1888
|
+
# Show test result (like dbt debug)
|
|
1889
|
+
if success:
|
|
1890
|
+
click.echo(
|
|
1891
|
+
f" Connection test: {click.style('[OK connection ok]', fg='green')}"
|
|
1892
|
+
)
|
|
1893
|
+
ctx.exit(0)
|
|
1894
|
+
else:
|
|
1895
|
+
click.echo(f" Connection test: {click.style('[ERROR]', fg='red')}")
|
|
1896
|
+
click.echo(f" {message}")
|
|
1897
|
+
ctx.exit(1)
|
|
1898
|
+
|
|
1899
|
+
# CASE 2: Test all targets in profile
|
|
1900
|
+
else:
|
|
1901
|
+
total_targets = len(outputs)
|
|
1902
|
+
click.echo(
|
|
1903
|
+
f"Testing all connections in profile {click.style(profile, fg='cyan')}...\n"
|
|
1904
|
+
)
|
|
1905
|
+
|
|
1906
|
+
# Test each target with progress indicators
|
|
1907
|
+
passed_count = 0
|
|
1908
|
+
failed_count = 0
|
|
1909
|
+
target_index = 1
|
|
1910
|
+
|
|
1911
|
+
for tgt_name, target_config in outputs.items():
|
|
1912
|
+
adapter_type = target_config.get("type", "unknown")
|
|
1913
|
+
|
|
1914
|
+
# Progress indicator
|
|
1915
|
+
progress = click.style(f"[{target_index}/{total_targets}]", fg="yellow")
|
|
1916
|
+
click.echo(
|
|
1917
|
+
f"{progress} Testing connection: {click.style(tgt_name, fg='cyan')} ({adapter_type})"
|
|
1918
|
+
)
|
|
1919
|
+
|
|
1920
|
+
# Show connection details FIRST (like dbt debug)
|
|
1921
|
+
if "host" in target_config:
|
|
1922
|
+
click.echo(f" host: {target_config['host']}")
|
|
1923
|
+
if "port" in target_config:
|
|
1924
|
+
click.echo(f" port: {target_config['port']}")
|
|
1925
|
+
if "account" in target_config:
|
|
1926
|
+
click.echo(f" account: {target_config['account']}")
|
|
1927
|
+
if "database" in target_config or "dbname" in target_config:
|
|
1928
|
+
db = target_config.get("database") or target_config.get("dbname")
|
|
1929
|
+
click.echo(f" database: {db}")
|
|
1930
|
+
if "warehouse" in target_config:
|
|
1931
|
+
click.echo(f" warehouse: {target_config['warehouse']}")
|
|
1932
|
+
if "schema" in target_config:
|
|
1933
|
+
click.echo(f" schema: {target_config['schema']}")
|
|
1934
|
+
if "project" in target_config:
|
|
1935
|
+
click.echo(f" project: {target_config['project']}")
|
|
1936
|
+
|
|
1937
|
+
# Test connection
|
|
1938
|
+
success, message = _test_target_with_timeout(
|
|
1939
|
+
profile_data, tgt_name, timeout
|
|
1940
|
+
)
|
|
1941
|
+
|
|
1942
|
+
# Show test result (like dbt debug)
|
|
1943
|
+
if success:
|
|
1944
|
+
click.echo(
|
|
1945
|
+
f" Connection test: {click.style('[OK connection ok]', fg='green')}"
|
|
1946
|
+
)
|
|
1947
|
+
passed_count += 1
|
|
1948
|
+
else:
|
|
1949
|
+
click.echo(f" Connection test: {click.style('[ERROR]', fg='red')}")
|
|
1950
|
+
click.echo(f" {message}")
|
|
1951
|
+
failed_count += 1
|
|
1952
|
+
|
|
1953
|
+
click.echo("")
|
|
1954
|
+
target_index += 1
|
|
1955
|
+
|
|
1956
|
+
# Summary line
|
|
1957
|
+
click.echo("─" * 60)
|
|
1958
|
+
if failed_count == 0:
|
|
1959
|
+
summary = click.style(
|
|
1960
|
+
f"✓ All {passed_count} connection tests passed", fg="green", bold=True
|
|
1961
|
+
)
|
|
1962
|
+
click.echo(summary)
|
|
1963
|
+
ctx.exit(0)
|
|
1964
|
+
else:
|
|
1965
|
+
passed_str = click.style(f"{passed_count} passed", fg="green")
|
|
1966
|
+
failed_str = click.style(f"{failed_count} failed", fg="red")
|
|
1967
|
+
summary = f"✗ {passed_str}, {failed_str}"
|
|
1968
|
+
click.echo(summary)
|
|
1969
|
+
ctx.exit(1)
|
|
1970
|
+
|
|
1971
|
+
|
|
1972
|
+
@target.command("add")
|
|
1973
|
+
@click.argument("target_name")
|
|
1974
|
+
@click.option("--profile", required=True, help="Profile name to add target to")
|
|
1975
|
+
@click.option(
|
|
1976
|
+
"--type",
|
|
1977
|
+
"adapter_type",
|
|
1978
|
+
required=True,
|
|
1979
|
+
help="Adapter type (postgres, snowflake, etc)",
|
|
1980
|
+
)
|
|
1981
|
+
@click.option("--host", help="Database host")
|
|
1982
|
+
@click.option("--port", type=int, help="Database port")
|
|
1983
|
+
@click.option("--user", help="Database user")
|
|
1984
|
+
@click.option("--password", help="Database password")
|
|
1985
|
+
@click.option("--database", help="Database name")
|
|
1986
|
+
@click.option("--schema", help="Default schema")
|
|
1987
|
+
@click.option("--threads", type=int, default=4, help="Number of threads")
|
|
1988
|
+
@click.option("--set-default", is_flag=True, help="Set as default target for profile")
|
|
1989
|
+
@click.pass_context
|
|
1990
|
+
@p.profiles_dir
|
|
1991
|
+
def target_add(
|
|
1992
|
+
ctx,
|
|
1993
|
+
target_name,
|
|
1994
|
+
profile,
|
|
1995
|
+
adapter_type,
|
|
1996
|
+
host,
|
|
1997
|
+
port,
|
|
1998
|
+
user,
|
|
1999
|
+
password,
|
|
2000
|
+
database,
|
|
2001
|
+
schema,
|
|
2002
|
+
threads,
|
|
2003
|
+
set_default,
|
|
2004
|
+
profiles_dir,
|
|
2005
|
+
**kwargs,
|
|
2006
|
+
):
|
|
2007
|
+
"""Add a new target to a profile in profiles.yml"""
|
|
2008
|
+
import yaml
|
|
2009
|
+
from pathlib import Path
|
|
2010
|
+
|
|
2011
|
+
profiles_file = Path(profiles_dir) / "profiles.yml"
|
|
2012
|
+
|
|
2013
|
+
if not profiles_file.exists():
|
|
2014
|
+
click.echo(f"✗ profiles.yml not found at {profiles_file}")
|
|
2015
|
+
return False, False
|
|
2016
|
+
|
|
2017
|
+
with open(profiles_file, "r") as f:
|
|
2018
|
+
profiles = yaml.safe_load(f) or {}
|
|
2019
|
+
|
|
2020
|
+
if profile not in profiles:
|
|
2021
|
+
click.echo(f"✗ Profile '{profile}' not found in profiles.yml")
|
|
2022
|
+
return False, False
|
|
2023
|
+
|
|
2024
|
+
profile_data = profiles[profile]
|
|
2025
|
+
if profile_data is None:
|
|
2026
|
+
click.echo(f"✗ Profile '{profile}' is empty or invalid")
|
|
2027
|
+
return False, False
|
|
2028
|
+
|
|
2029
|
+
# Get or create outputs dict (standard dbt format)
|
|
2030
|
+
if "outputs" not in profile_data:
|
|
2031
|
+
profile_data["outputs"] = {}
|
|
2032
|
+
|
|
2033
|
+
outputs = profile_data["outputs"]
|
|
2034
|
+
|
|
2035
|
+
# Check if target already exists
|
|
2036
|
+
if target_name in outputs:
|
|
2037
|
+
if not click.confirm(f"Target '{target_name}' already exists. Overwrite?"):
|
|
2038
|
+
return False, False
|
|
2039
|
+
|
|
2040
|
+
# Build target config
|
|
2041
|
+
target_config = {"type": adapter_type}
|
|
2042
|
+
|
|
2043
|
+
if host:
|
|
2044
|
+
target_config["host"] = host
|
|
2045
|
+
if port:
|
|
2046
|
+
target_config["port"] = port
|
|
2047
|
+
if user:
|
|
2048
|
+
target_config["user"] = user
|
|
2049
|
+
if password:
|
|
2050
|
+
target_config["password"] = password
|
|
2051
|
+
if database:
|
|
2052
|
+
target_config["database"] = database
|
|
2053
|
+
if schema:
|
|
2054
|
+
target_config["schema"] = schema
|
|
2055
|
+
if threads:
|
|
2056
|
+
target_config["threads"] = threads
|
|
2057
|
+
|
|
2058
|
+
# Add target to outputs
|
|
2059
|
+
outputs[target_name] = target_config
|
|
2060
|
+
|
|
2061
|
+
# Set as default if requested
|
|
2062
|
+
if set_default:
|
|
2063
|
+
profile_data["target"] = target_name
|
|
2064
|
+
|
|
2065
|
+
# Write back to profiles.yml
|
|
2066
|
+
with open(profiles_file, "w") as f:
|
|
2067
|
+
yaml.dump(profiles, f, default_flow_style=False, sort_keys=False)
|
|
2068
|
+
|
|
2069
|
+
click.echo(f"✓ Added target '{target_name}' to profile '{profile}'")
|
|
2070
|
+
if set_default:
|
|
2071
|
+
click.echo(f" Set as default target")
|
|
2072
|
+
|
|
2073
|
+
return True, True
|
|
2074
|
+
|
|
2075
|
+
|
|
2076
|
+
@target.command("sync")
|
|
2077
|
+
@click.option("--profile", help="Profile name (defaults to project profile)")
|
|
2078
|
+
@click.option("--clean", is_flag=True, help="Remove adapters not needed by profiles.yml")
|
|
2079
|
+
@click.option("--dry-run", is_flag=True, help="Show what would be done without making changes")
|
|
2080
|
+
@click.pass_context
|
|
2081
|
+
@p.profiles_dir
|
|
2082
|
+
@p.project_dir
|
|
2083
|
+
def target_sync(ctx, profile, clean, dry_run, profiles_dir, project_dir, **kwargs):
|
|
2084
|
+
"""Sync adapters and JDBC JARs based on profiles.yml connections.
|
|
2085
|
+
|
|
2086
|
+
Scans your profiles.yml to find all connection types, then:
|
|
2087
|
+
- Installs required dbt adapters via pip
|
|
2088
|
+
- Updates JDBC JARs for Spark federation
|
|
2089
|
+
- Optionally removes unused adapters (with --clean)
|
|
2090
|
+
|
|
2091
|
+
Examples:
|
|
2092
|
+
|
|
2093
|
+
dvt target sync # Sync for current project
|
|
2094
|
+
dvt target sync --profile my_project # Sync specific profile
|
|
2095
|
+
dvt target sync --dry-run # Show what would happen
|
|
2096
|
+
dvt target sync --clean # Also remove unused adapters
|
|
2097
|
+
"""
|
|
2098
|
+
from dbt.task.target_sync import TargetSyncTask
|
|
2099
|
+
|
|
2100
|
+
task = TargetSyncTask(
|
|
2101
|
+
project_dir=project_dir,
|
|
2102
|
+
profiles_dir=profiles_dir,
|
|
2103
|
+
profile_name=profile,
|
|
2104
|
+
)
|
|
2105
|
+
success = task.sync(verbose=True, clean=clean, dry_run=dry_run)
|
|
2106
|
+
return None, success
|
|
2107
|
+
|
|
2108
|
+
|
|
2109
|
+
@target.command("remove")
|
|
2110
|
+
@click.argument("target_name")
|
|
2111
|
+
@click.option("--profile", required=True, help="Profile name to remove target from")
|
|
2112
|
+
@click.pass_context
|
|
2113
|
+
@p.profiles_dir
|
|
2114
|
+
def target_remove(ctx, target_name, profile, profiles_dir, **kwargs):
|
|
2115
|
+
"""Remove a target from a profile in profiles.yml"""
|
|
2116
|
+
import yaml
|
|
2117
|
+
from pathlib import Path
|
|
2118
|
+
|
|
2119
|
+
profiles_file = Path(profiles_dir) / "profiles.yml"
|
|
2120
|
+
|
|
2121
|
+
if not profiles_file.exists():
|
|
2122
|
+
click.echo(f"✗ profiles.yml not found at {profiles_file}")
|
|
2123
|
+
return False, False
|
|
2124
|
+
|
|
2125
|
+
with open(profiles_file, "r") as f:
|
|
2126
|
+
profiles = yaml.safe_load(f) or {}
|
|
2127
|
+
|
|
2128
|
+
if profile not in profiles:
|
|
2129
|
+
click.echo(f"✗ Profile '{profile}' not found in profiles.yml")
|
|
2130
|
+
return False, False
|
|
2131
|
+
|
|
2132
|
+
profile_data = profiles[profile]
|
|
2133
|
+
if profile_data is None:
|
|
2134
|
+
click.echo(f"✗ Profile '{profile}' is empty or invalid")
|
|
2135
|
+
return False, False
|
|
2136
|
+
outputs = profile_data.get("outputs", {})
|
|
2137
|
+
|
|
2138
|
+
if target_name not in outputs:
|
|
2139
|
+
click.echo(f"✗ Target '{target_name}' not found in profile '{profile}'")
|
|
2140
|
+
return False, False
|
|
2141
|
+
|
|
2142
|
+
# Remove the target
|
|
2143
|
+
del outputs[target_name]
|
|
2144
|
+
|
|
2145
|
+
# Check if this was the default target
|
|
2146
|
+
default_target = profile_data.get("target")
|
|
2147
|
+
if default_target == target_name:
|
|
2148
|
+
# Set new default to first available target
|
|
2149
|
+
if outputs:
|
|
2150
|
+
new_default = list(outputs.keys())[0]
|
|
2151
|
+
profile_data["target"] = new_default
|
|
2152
|
+
click.echo(
|
|
2153
|
+
f" Note: '{target_name}' was the default target, changed to '{new_default}'"
|
|
2154
|
+
)
|
|
2155
|
+
else:
|
|
2156
|
+
click.echo(f" Warning: No targets remaining in profile '{profile}'")
|
|
2157
|
+
|
|
2158
|
+
# Write back to profiles.yml
|
|
2159
|
+
with open(profiles_file, "w") as f:
|
|
2160
|
+
yaml.dump(profiles, f, default_flow_style=False, sort_keys=False)
|
|
2161
|
+
|
|
2162
|
+
click.echo(f"✓ Removed target '{target_name}' from profile '{profile}'")
|
|
2163
|
+
|
|
2164
|
+
return True, True
|
|
2165
|
+
|
|
2166
|
+
|
|
2167
|
+
# DVT java commands for Java management
|
|
2168
|
+
@cli.group()
|
|
2169
|
+
@click.pass_context
|
|
2170
|
+
@p.version
|
|
2171
|
+
def java(ctx, **kwargs):
|
|
2172
|
+
"""Manage Java installations for PySpark.
|
|
2173
|
+
|
|
2174
|
+
Java is required for Spark compute engines.
|
|
2175
|
+
|
|
2176
|
+
\b
|
|
2177
|
+
Compatibility:
|
|
2178
|
+
PySpark 4.0.x -> Java 17 or 21
|
|
2179
|
+
PySpark 3.5.x -> Java 8, 11, or 17
|
|
2180
|
+
PySpark 3.3-3.4 -> Java 8 or 11
|
|
2181
|
+
|
|
2182
|
+
\b
|
|
2183
|
+
Commands:
|
|
2184
|
+
check - Check Java compatibility with PySpark
|
|
2185
|
+
search - Find all Java installations
|
|
2186
|
+
set - Select and configure JAVA_HOME
|
|
2187
|
+
install - Show installation guide
|
|
2188
|
+
|
|
2189
|
+
\b
|
|
2190
|
+
Examples:
|
|
2191
|
+
dvt java check # Check compatibility
|
|
2192
|
+
dvt java search # Find installations
|
|
2193
|
+
dvt java set # Configure JAVA_HOME
|
|
2194
|
+
"""
|
|
2195
|
+
|
|
2196
|
+
|
|
2197
|
+
@java.command("check")
|
|
2198
|
+
@click.pass_context
|
|
2199
|
+
def java_check(ctx, **kwargs):
|
|
2200
|
+
"""Check Java installation and PySpark compatibility.
|
|
2201
|
+
|
|
2202
|
+
Shows current Java version, installed PySpark version, and whether
|
|
2203
|
+
they are compatible. Provides guidance if there's a mismatch.
|
|
2204
|
+
|
|
2205
|
+
Exit codes:
|
|
2206
|
+
0 - Java and PySpark are compatible
|
|
2207
|
+
1 - Java/PySpark mismatch or not found
|
|
2208
|
+
"""
|
|
2209
|
+
from dbt.task.java import JavaTask
|
|
2210
|
+
|
|
2211
|
+
task = JavaTask()
|
|
2212
|
+
is_compatible = task.check()
|
|
2213
|
+
ctx.exit(0 if is_compatible else 1)
|
|
2214
|
+
|
|
2215
|
+
|
|
2216
|
+
@java.command("search")
|
|
2217
|
+
@click.pass_context
|
|
2218
|
+
def java_search(ctx, **kwargs):
|
|
2219
|
+
"""Find all Java installations on the system.
|
|
2220
|
+
|
|
2221
|
+
Searches common installation locations for Java on your OS:
|
|
2222
|
+
|
|
2223
|
+
\b
|
|
2224
|
+
macOS: /Library/Java/JavaVirtualMachines, Homebrew, SDKMAN
|
|
2225
|
+
Linux: /usr/lib/jvm, /opt/java, update-alternatives, SDKMAN
|
|
2226
|
+
Windows: Program Files, Registry, Scoop, Chocolatey
|
|
2227
|
+
|
|
2228
|
+
Shows Java version, vendor, and compatibility with installed PySpark.
|
|
2229
|
+
"""
|
|
2230
|
+
from dbt.task.java import JavaTask
|
|
2231
|
+
|
|
2232
|
+
task = JavaTask()
|
|
2233
|
+
installations = task.search()
|
|
2234
|
+
ctx.exit(0 if installations else 1)
|
|
2235
|
+
|
|
2236
|
+
|
|
2237
|
+
@java.command("set")
|
|
2238
|
+
@click.pass_context
|
|
2239
|
+
def java_set(ctx, **kwargs):
|
|
2240
|
+
"""Interactively select and set JAVA_HOME.
|
|
2241
|
+
|
|
2242
|
+
Shows all found Java installations with compatibility indicators,
|
|
2243
|
+
lets you choose one, and updates your shell configuration file
|
|
2244
|
+
(.zshrc, .bashrc, etc.) to persist JAVA_HOME.
|
|
2245
|
+
|
|
2246
|
+
After setting, restart your terminal or run 'source ~/.zshrc'
|
|
2247
|
+
(or equivalent) for changes to take effect.
|
|
2248
|
+
"""
|
|
2249
|
+
from dbt.task.java import JavaTask
|
|
2250
|
+
|
|
2251
|
+
task = JavaTask()
|
|
2252
|
+
success = task.set_java_home()
|
|
2253
|
+
ctx.exit(0 if success else 1)
|
|
2254
|
+
|
|
2255
|
+
|
|
2256
|
+
@java.command("install")
|
|
2257
|
+
@click.pass_context
|
|
2258
|
+
def java_install(ctx, **kwargs):
|
|
2259
|
+
"""Show Java installation guide for your platform.
|
|
2260
|
+
|
|
2261
|
+
Provides platform-specific installation instructions based on
|
|
2262
|
+
your installed PySpark version. Includes options for:
|
|
2263
|
+
|
|
2264
|
+
\b
|
|
2265
|
+
macOS: Homebrew, SDKMAN, manual download
|
|
2266
|
+
Linux: apt-get, dnf, pacman, SDKMAN
|
|
2267
|
+
Windows: Winget, Chocolatey, Scoop, manual download
|
|
2268
|
+
"""
|
|
2269
|
+
from dbt.task.java import JavaTask
|
|
2270
|
+
|
|
2271
|
+
task = JavaTask()
|
|
2272
|
+
task.install_guide()
|
|
2273
|
+
ctx.exit(0)
|
|
2274
|
+
|
|
2275
|
+
|
|
2276
|
+
# DVT spark commands for Spark/PySpark management
|
|
2277
|
+
@cli.group()
|
|
2278
|
+
@click.pass_context
|
|
2279
|
+
@p.version
|
|
2280
|
+
def spark(ctx, **kwargs):
|
|
2281
|
+
"""Manage PySpark installations and cluster compatibility.
|
|
2282
|
+
|
|
2283
|
+
PySpark is used by DVT for federated query execution.
|
|
2284
|
+
|
|
2285
|
+
\b
|
|
2286
|
+
Commands:
|
|
2287
|
+
check - Check PySpark/Java status
|
|
2288
|
+
set-version - Install specific PySpark version
|
|
2289
|
+
match-cluster - Match PySpark to cluster version
|
|
2290
|
+
versions - Show compatibility matrix
|
|
2291
|
+
|
|
2292
|
+
\b
|
|
2293
|
+
Examples:
|
|
2294
|
+
dvt spark check # Check status
|
|
2295
|
+
dvt spark set-version # Install PySpark
|
|
2296
|
+
dvt spark match-cluster spark # Match to cluster
|
|
2297
|
+
dvt spark versions # Show matrix
|
|
2298
|
+
"""
|
|
2299
|
+
|
|
2300
|
+
|
|
2301
|
+
@spark.command("check")
|
|
2302
|
+
@click.pass_context
|
|
2303
|
+
def spark_check(ctx, **kwargs):
|
|
2304
|
+
"""Check PySpark installation and Java compatibility.
|
|
2305
|
+
|
|
2306
|
+
Shows:
|
|
2307
|
+
- Installed PySpark version and requirements
|
|
2308
|
+
- Current Java version
|
|
2309
|
+
- Compatibility status
|
|
2310
|
+
|
|
2311
|
+
Exit codes:
|
|
2312
|
+
0 - PySpark installed and Java compatible
|
|
2313
|
+
1 - PySpark not installed or Java incompatible
|
|
2314
|
+
"""
|
|
2315
|
+
from dbt.task.spark import SparkTask
|
|
2316
|
+
|
|
2317
|
+
task = SparkTask()
|
|
2318
|
+
is_ok = task.check()
|
|
2319
|
+
ctx.exit(0 if is_ok else 1)
|
|
2320
|
+
|
|
2321
|
+
|
|
2322
|
+
@spark.command("set-version")
|
|
2323
|
+
@click.pass_context
|
|
2324
|
+
def spark_set_version(ctx, **kwargs):
|
|
2325
|
+
"""Interactively select and install a PySpark version.
|
|
2326
|
+
|
|
2327
|
+
Presents available PySpark versions with their Java requirements.
|
|
2328
|
+
Shows compatibility indicators based on your current Java.
|
|
2329
|
+
Installs the selected version via pip.
|
|
2330
|
+
|
|
2331
|
+
Available versions:
|
|
2332
|
+
\b
|
|
2333
|
+
PySpark 4.0.x - Latest, requires Java 17+
|
|
2334
|
+
PySpark 3.5.x - Stable, Java 8/11/17
|
|
2335
|
+
PySpark 3.4.x - Java 8/11/17
|
|
2336
|
+
PySpark 3.3.x - Java 8/11
|
|
2337
|
+
PySpark 3.2.x - Java 8/11
|
|
2338
|
+
|
|
2339
|
+
After installing, check Java compatibility with 'dvt java check'.
|
|
2340
|
+
"""
|
|
2341
|
+
from dbt.task.spark import SparkTask
|
|
2342
|
+
|
|
2343
|
+
task = SparkTask()
|
|
2344
|
+
success = task.set_version()
|
|
2345
|
+
ctx.exit(0 if success else 1)
|
|
2346
|
+
|
|
2347
|
+
|
|
2348
|
+
@spark.command("match-cluster")
|
|
2349
|
+
@click.argument("compute_name")
|
|
2350
|
+
@click.pass_context
|
|
2351
|
+
def spark_match_cluster(ctx, compute_name, **kwargs):
|
|
2352
|
+
"""Detect cluster Spark version and check PySpark compatibility.
|
|
2353
|
+
|
|
2354
|
+
Connects to the specified compute engine from computes.yml,
|
|
2355
|
+
detects its Spark version, and compares with locally installed
|
|
2356
|
+
PySpark. Provides recommendations if versions don't match.
|
|
2357
|
+
|
|
2358
|
+
IMPORTANT: PySpark version must match the cluster's Spark version
|
|
2359
|
+
(same major.minor). A mismatch can cause runtime errors.
|
|
2360
|
+
|
|
2361
|
+
Arguments:
|
|
2362
|
+
COMPUTE_NAME: Name of compute engine in computes.yml
|
|
2363
|
+
|
|
2364
|
+
Examples:
|
|
2365
|
+
|
|
2366
|
+
\b
|
|
2367
|
+
dvt spark match-cluster spark-docker
|
|
2368
|
+
dvt spark match-cluster spark-local
|
|
2369
|
+
dvt spark match-cluster databricks-prod
|
|
2370
|
+
"""
|
|
2371
|
+
from dbt.task.spark import SparkTask
|
|
2372
|
+
|
|
2373
|
+
task = SparkTask()
|
|
2374
|
+
is_match = task.match_cluster(compute_name)
|
|
2375
|
+
ctx.exit(0 if is_match else 1)
|
|
2376
|
+
|
|
2377
|
+
|
|
2378
|
+
@spark.command("versions")
|
|
2379
|
+
@click.pass_context
|
|
2380
|
+
def spark_versions(ctx, **kwargs):
|
|
2381
|
+
"""Display PySpark/Java compatibility matrix.
|
|
2382
|
+
|
|
2383
|
+
Shows all available PySpark versions with their Java requirements,
|
|
2384
|
+
marks the currently installed version, and shows your current
|
|
2385
|
+
Java installation.
|
|
2386
|
+
"""
|
|
2387
|
+
from dbt.task.spark import SparkTask
|
|
2388
|
+
|
|
2389
|
+
task = SparkTask()
|
|
2390
|
+
task.show_versions()
|
|
2391
|
+
ctx.exit(0)
|
|
2392
|
+
|
|
2393
|
+
|
|
2394
|
+
# Register DVT command groups with main CLI
|
|
2395
|
+
cli.add_command(compute)
|
|
2396
|
+
cli.add_command(target)
|
|
2397
|
+
cli.add_command(java)
|
|
2398
|
+
cli.add_command(spark)
|
|
2399
|
+
|
|
2400
|
+
|
|
2401
|
+
# Support running as a module (python -m dbt.cli.main)
|
|
2402
|
+
if __name__ == "__main__":
|
|
2403
|
+
cli()
|