dvt-core 0.52.2__cp310-cp310-macosx_10_9_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dbt/__init__.py +7 -0
- dbt/_pydantic_shim.py +26 -0
- dbt/artifacts/__init__.py +0 -0
- dbt/artifacts/exceptions/__init__.py +1 -0
- dbt/artifacts/exceptions/schemas.py +31 -0
- dbt/artifacts/resources/__init__.py +116 -0
- dbt/artifacts/resources/base.py +67 -0
- dbt/artifacts/resources/types.py +93 -0
- dbt/artifacts/resources/v1/analysis.py +10 -0
- dbt/artifacts/resources/v1/catalog.py +23 -0
- dbt/artifacts/resources/v1/components.py +274 -0
- dbt/artifacts/resources/v1/config.py +277 -0
- dbt/artifacts/resources/v1/documentation.py +11 -0
- dbt/artifacts/resources/v1/exposure.py +51 -0
- dbt/artifacts/resources/v1/function.py +52 -0
- dbt/artifacts/resources/v1/generic_test.py +31 -0
- dbt/artifacts/resources/v1/group.py +21 -0
- dbt/artifacts/resources/v1/hook.py +11 -0
- dbt/artifacts/resources/v1/macro.py +29 -0
- dbt/artifacts/resources/v1/metric.py +172 -0
- dbt/artifacts/resources/v1/model.py +145 -0
- dbt/artifacts/resources/v1/owner.py +10 -0
- dbt/artifacts/resources/v1/saved_query.py +111 -0
- dbt/artifacts/resources/v1/seed.py +41 -0
- dbt/artifacts/resources/v1/semantic_layer_components.py +72 -0
- dbt/artifacts/resources/v1/semantic_model.py +314 -0
- dbt/artifacts/resources/v1/singular_test.py +14 -0
- dbt/artifacts/resources/v1/snapshot.py +91 -0
- dbt/artifacts/resources/v1/source_definition.py +84 -0
- dbt/artifacts/resources/v1/sql_operation.py +10 -0
- dbt/artifacts/resources/v1/unit_test_definition.py +77 -0
- dbt/artifacts/schemas/__init__.py +0 -0
- dbt/artifacts/schemas/base.py +191 -0
- dbt/artifacts/schemas/batch_results.py +24 -0
- dbt/artifacts/schemas/catalog/__init__.py +11 -0
- dbt/artifacts/schemas/catalog/v1/__init__.py +0 -0
- dbt/artifacts/schemas/catalog/v1/catalog.py +59 -0
- dbt/artifacts/schemas/freshness/__init__.py +1 -0
- dbt/artifacts/schemas/freshness/v3/__init__.py +0 -0
- dbt/artifacts/schemas/freshness/v3/freshness.py +158 -0
- dbt/artifacts/schemas/manifest/__init__.py +2 -0
- dbt/artifacts/schemas/manifest/v12/__init__.py +0 -0
- dbt/artifacts/schemas/manifest/v12/manifest.py +211 -0
- dbt/artifacts/schemas/results.py +147 -0
- dbt/artifacts/schemas/run/__init__.py +2 -0
- dbt/artifacts/schemas/run/v5/__init__.py +0 -0
- dbt/artifacts/schemas/run/v5/run.py +184 -0
- dbt/artifacts/schemas/upgrades/__init__.py +4 -0
- dbt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
- dbt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
- dbt/artifacts/utils/validation.py +153 -0
- dbt/cli/__init__.py +1 -0
- dbt/cli/context.py +17 -0
- dbt/cli/exceptions.py +57 -0
- dbt/cli/flags.py +560 -0
- dbt/cli/main.py +2039 -0
- dbt/cli/option_types.py +121 -0
- dbt/cli/options.py +80 -0
- dbt/cli/params.py +804 -0
- dbt/cli/requires.py +490 -0
- dbt/cli/resolvers.py +50 -0
- dbt/cli/types.py +40 -0
- dbt/clients/__init__.py +0 -0
- dbt/clients/checked_load.py +83 -0
- dbt/clients/git.py +164 -0
- dbt/clients/jinja.py +206 -0
- dbt/clients/jinja_static.py +245 -0
- dbt/clients/registry.py +192 -0
- dbt/clients/yaml_helper.py +68 -0
- dbt/compilation.py +876 -0
- dbt/compute/__init__.py +14 -0
- dbt/compute/engines/__init__.py +12 -0
- dbt/compute/engines/spark_engine.py +624 -0
- dbt/compute/federated_executor.py +837 -0
- dbt/compute/filter_pushdown.cpython-310-darwin.so +0 -0
- dbt/compute/filter_pushdown.py +273 -0
- dbt/compute/jar_provisioning.cpython-310-darwin.so +0 -0
- dbt/compute/jar_provisioning.py +255 -0
- dbt/compute/java_compat.cpython-310-darwin.so +0 -0
- dbt/compute/java_compat.py +689 -0
- dbt/compute/jdbc_utils.cpython-310-darwin.so +0 -0
- dbt/compute/jdbc_utils.py +678 -0
- dbt/compute/smart_selector.cpython-310-darwin.so +0 -0
- dbt/compute/smart_selector.py +311 -0
- dbt/compute/strategies/__init__.py +54 -0
- dbt/compute/strategies/base.py +165 -0
- dbt/compute/strategies/dataproc.py +207 -0
- dbt/compute/strategies/emr.py +203 -0
- dbt/compute/strategies/local.py +364 -0
- dbt/compute/strategies/standalone.py +262 -0
- dbt/config/__init__.py +4 -0
- dbt/config/catalogs.py +94 -0
- dbt/config/compute.cpython-310-darwin.so +0 -0
- dbt/config/compute.py +547 -0
- dbt/config/dvt_profile.cpython-310-darwin.so +0 -0
- dbt/config/dvt_profile.py +342 -0
- dbt/config/profile.py +422 -0
- dbt/config/project.py +873 -0
- dbt/config/project_utils.py +28 -0
- dbt/config/renderer.py +231 -0
- dbt/config/runtime.py +553 -0
- dbt/config/selectors.py +208 -0
- dbt/config/utils.py +77 -0
- dbt/constants.py +28 -0
- dbt/context/__init__.py +0 -0
- dbt/context/base.py +745 -0
- dbt/context/configured.py +135 -0
- dbt/context/context_config.py +382 -0
- dbt/context/docs.py +82 -0
- dbt/context/exceptions_jinja.py +178 -0
- dbt/context/macro_resolver.py +195 -0
- dbt/context/macros.py +171 -0
- dbt/context/manifest.py +72 -0
- dbt/context/providers.py +2249 -0
- dbt/context/query_header.py +13 -0
- dbt/context/secret.py +58 -0
- dbt/context/target.py +74 -0
- dbt/contracts/__init__.py +0 -0
- dbt/contracts/files.py +413 -0
- dbt/contracts/graph/__init__.py +0 -0
- dbt/contracts/graph/manifest.py +1904 -0
- dbt/contracts/graph/metrics.py +97 -0
- dbt/contracts/graph/model_config.py +70 -0
- dbt/contracts/graph/node_args.py +42 -0
- dbt/contracts/graph/nodes.py +1806 -0
- dbt/contracts/graph/semantic_manifest.py +232 -0
- dbt/contracts/graph/unparsed.py +811 -0
- dbt/contracts/project.py +417 -0
- dbt/contracts/results.py +53 -0
- dbt/contracts/selection.py +23 -0
- dbt/contracts/sql.py +85 -0
- dbt/contracts/state.py +68 -0
- dbt/contracts/util.py +46 -0
- dbt/deprecations.py +346 -0
- dbt/deps/__init__.py +0 -0
- dbt/deps/base.py +152 -0
- dbt/deps/git.py +195 -0
- dbt/deps/local.py +79 -0
- dbt/deps/registry.py +130 -0
- dbt/deps/resolver.py +149 -0
- dbt/deps/tarball.py +120 -0
- dbt/docs/source/_ext/dbt_click.py +119 -0
- dbt/docs/source/conf.py +32 -0
- dbt/env_vars.py +64 -0
- dbt/event_time/event_time.py +40 -0
- dbt/event_time/sample_window.py +60 -0
- dbt/events/__init__.py +15 -0
- dbt/events/base_types.py +36 -0
- dbt/events/core_types_pb2.py +2 -0
- dbt/events/logging.py +108 -0
- dbt/events/types.py +2516 -0
- dbt/exceptions.py +1486 -0
- dbt/flags.py +89 -0
- dbt/graph/__init__.py +11 -0
- dbt/graph/cli.py +247 -0
- dbt/graph/graph.py +172 -0
- dbt/graph/queue.py +214 -0
- dbt/graph/selector.py +374 -0
- dbt/graph/selector_methods.py +975 -0
- dbt/graph/selector_spec.py +222 -0
- dbt/graph/thread_pool.py +18 -0
- dbt/hooks.py +21 -0
- dbt/include/README.md +49 -0
- dbt/include/__init__.py +3 -0
- dbt/include/starter_project/.gitignore +4 -0
- dbt/include/starter_project/README.md +15 -0
- dbt/include/starter_project/__init__.py +3 -0
- dbt/include/starter_project/analyses/.gitkeep +0 -0
- dbt/include/starter_project/dbt_project.yml +36 -0
- dbt/include/starter_project/macros/.gitkeep +0 -0
- dbt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
- dbt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
- dbt/include/starter_project/models/example/schema.yml +21 -0
- dbt/include/starter_project/seeds/.gitkeep +0 -0
- dbt/include/starter_project/snapshots/.gitkeep +0 -0
- dbt/include/starter_project/tests/.gitkeep +0 -0
- dbt/internal_deprecations.py +26 -0
- dbt/jsonschemas/__init__.py +3 -0
- dbt/jsonschemas/jsonschemas.py +309 -0
- dbt/jsonschemas/project/0.0.110.json +4717 -0
- dbt/jsonschemas/project/0.0.85.json +2015 -0
- dbt/jsonschemas/resources/0.0.110.json +2636 -0
- dbt/jsonschemas/resources/0.0.85.json +2536 -0
- dbt/jsonschemas/resources/latest.json +6773 -0
- dbt/links.py +4 -0
- dbt/materializations/__init__.py +0 -0
- dbt/materializations/incremental/__init__.py +0 -0
- dbt/materializations/incremental/microbatch.py +236 -0
- dbt/mp_context.py +8 -0
- dbt/node_types.py +37 -0
- dbt/parser/__init__.py +23 -0
- dbt/parser/analysis.py +21 -0
- dbt/parser/base.py +548 -0
- dbt/parser/common.py +266 -0
- dbt/parser/docs.py +52 -0
- dbt/parser/fixtures.py +51 -0
- dbt/parser/functions.py +30 -0
- dbt/parser/generic_test.py +100 -0
- dbt/parser/generic_test_builders.py +333 -0
- dbt/parser/hooks.py +118 -0
- dbt/parser/macros.py +137 -0
- dbt/parser/manifest.py +2204 -0
- dbt/parser/models.py +573 -0
- dbt/parser/partial.py +1178 -0
- dbt/parser/read_files.py +445 -0
- dbt/parser/schema_generic_tests.py +422 -0
- dbt/parser/schema_renderer.py +111 -0
- dbt/parser/schema_yaml_readers.py +935 -0
- dbt/parser/schemas.py +1466 -0
- dbt/parser/search.py +149 -0
- dbt/parser/seeds.py +28 -0
- dbt/parser/singular_test.py +20 -0
- dbt/parser/snapshots.py +44 -0
- dbt/parser/sources.py +558 -0
- dbt/parser/sql.py +62 -0
- dbt/parser/unit_tests.py +621 -0
- dbt/plugins/__init__.py +20 -0
- dbt/plugins/contracts.py +9 -0
- dbt/plugins/exceptions.py +2 -0
- dbt/plugins/manager.py +163 -0
- dbt/plugins/manifest.py +21 -0
- dbt/profiler.py +20 -0
- dbt/py.typed +1 -0
- dbt/query_analyzer.cpython-310-darwin.so +0 -0
- dbt/query_analyzer.py +410 -0
- dbt/runners/__init__.py +2 -0
- dbt/runners/exposure_runner.py +7 -0
- dbt/runners/no_op_runner.py +45 -0
- dbt/runners/saved_query_runner.py +7 -0
- dbt/selected_resources.py +8 -0
- dbt/task/__init__.py +0 -0
- dbt/task/base.py +503 -0
- dbt/task/build.py +197 -0
- dbt/task/clean.py +56 -0
- dbt/task/clone.py +161 -0
- dbt/task/compile.py +150 -0
- dbt/task/compute.py +454 -0
- dbt/task/debug.py +505 -0
- dbt/task/deps.py +280 -0
- dbt/task/docs/__init__.py +3 -0
- dbt/task/docs/generate.py +660 -0
- dbt/task/docs/index.html +250 -0
- dbt/task/docs/serve.py +29 -0
- dbt/task/freshness.py +322 -0
- dbt/task/function.py +121 -0
- dbt/task/group_lookup.py +46 -0
- dbt/task/init.py +553 -0
- dbt/task/java.py +316 -0
- dbt/task/list.py +236 -0
- dbt/task/printer.py +175 -0
- dbt/task/retry.py +175 -0
- dbt/task/run.py +1306 -0
- dbt/task/run_operation.py +141 -0
- dbt/task/runnable.py +758 -0
- dbt/task/seed.py +103 -0
- dbt/task/show.py +149 -0
- dbt/task/snapshot.py +56 -0
- dbt/task/spark.py +414 -0
- dbt/task/sql.py +110 -0
- dbt/task/target_sync.py +759 -0
- dbt/task/test.py +464 -0
- dbt/tests/fixtures/__init__.py +1 -0
- dbt/tests/fixtures/project.py +620 -0
- dbt/tests/util.py +651 -0
- dbt/tracking.py +529 -0
- dbt/utils/__init__.py +3 -0
- dbt/utils/artifact_upload.py +151 -0
- dbt/utils/utils.py +408 -0
- dbt/version.py +268 -0
- dvt_cli/__init__.py +72 -0
- dvt_core-0.52.2.dist-info/METADATA +286 -0
- dvt_core-0.52.2.dist-info/RECORD +275 -0
- dvt_core-0.52.2.dist-info/WHEEL +5 -0
- dvt_core-0.52.2.dist-info/entry_points.txt +2 -0
- dvt_core-0.52.2.dist-info/top_level.txt +2 -0
dbt/task/init.py
ADDED
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
import dbt.config
|
|
12
|
+
import dbt_common.clients.system
|
|
13
|
+
from dbt.adapters.factory import get_include_paths, load_plugin
|
|
14
|
+
from dbt.config.profile import read_profile
|
|
15
|
+
from dbt.contracts.util import Identifier as ProjectName
|
|
16
|
+
from dbt.events.types import (
|
|
17
|
+
ConfigFolderDirectory,
|
|
18
|
+
InvalidProfileTemplateYAML,
|
|
19
|
+
NoSampleProfileFound,
|
|
20
|
+
ProfileWrittenWithProjectTemplateYAML,
|
|
21
|
+
ProfileWrittenWithSample,
|
|
22
|
+
ProfileWrittenWithTargetTemplateYAML,
|
|
23
|
+
ProjectCreated,
|
|
24
|
+
ProjectNameAlreadyExists,
|
|
25
|
+
SettingUpProfile,
|
|
26
|
+
StarterProjectPath,
|
|
27
|
+
)
|
|
28
|
+
from dbt.flags import get_flags
|
|
29
|
+
from dbt.task.base import BaseTask, move_to_nearest_project_dir
|
|
30
|
+
from dbt.version import _get_adapter_plugin_names
|
|
31
|
+
from dbt_common.events.functions import fire_event
|
|
32
|
+
from dbt_common.exceptions import DbtRuntimeError
|
|
33
|
+
|
|
34
|
+
DOCS_URL = "https://docs.getdbt.com/docs/configure-your-profile"
|
|
35
|
+
SLACK_URL = "https://community.getdbt.com/"
|
|
36
|
+
|
|
37
|
+
# This file is not needed for the starter project but exists for finding the resource path
|
|
38
|
+
IGNORE_FILES = ["__init__.py", "__pycache__"]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# https://click.palletsprojects.com/en/8.0.x/api/#types
|
|
42
|
+
# click v7.0 has UNPROCESSED, STRING, INT, FLOAT, BOOL, and UUID available.
|
|
43
|
+
click_type_mapping = {
|
|
44
|
+
"string": click.STRING,
|
|
45
|
+
"int": click.INT,
|
|
46
|
+
"float": click.FLOAT,
|
|
47
|
+
"bool": click.BOOL,
|
|
48
|
+
None: None,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class InitTask(BaseTask):
|
|
53
|
+
def copy_starter_repo(self, project_name: str) -> None:
|
|
54
|
+
# Lazy import to avoid ModuleNotFoundError
|
|
55
|
+
from dbt.include.starter_project import (
|
|
56
|
+
PACKAGE_PATH as starter_project_directory,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
fire_event(StarterProjectPath(dir=starter_project_directory))
|
|
60
|
+
shutil.copytree(
|
|
61
|
+
starter_project_directory, project_name, ignore=shutil.ignore_patterns(*IGNORE_FILES)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def create_profiles_dir(self, profiles_dir: str) -> bool:
|
|
65
|
+
"""Create the user's profiles directory if it doesn't already exist."""
|
|
66
|
+
profiles_path = Path(profiles_dir)
|
|
67
|
+
if not profiles_path.exists():
|
|
68
|
+
fire_event(ConfigFolderDirectory(dir=str(profiles_dir)))
|
|
69
|
+
dbt_common.clients.system.make_directory(profiles_dir)
|
|
70
|
+
return True
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
def create_profile_from_sample(self, adapter: str, profile_name: str):
|
|
74
|
+
"""Create a profile entry using the adapter's sample_profiles.yml
|
|
75
|
+
|
|
76
|
+
Renames the profile in sample_profiles.yml to match that of the project."""
|
|
77
|
+
# Line below raises an exception if the specified adapter is not found
|
|
78
|
+
load_plugin(adapter)
|
|
79
|
+
adapter_path = get_include_paths(adapter)[0]
|
|
80
|
+
sample_profiles_path = adapter_path / "sample_profiles.yml"
|
|
81
|
+
|
|
82
|
+
if not sample_profiles_path.exists():
|
|
83
|
+
fire_event(NoSampleProfileFound(adapter=adapter))
|
|
84
|
+
else:
|
|
85
|
+
with open(sample_profiles_path, "r") as f:
|
|
86
|
+
sample_profile = f.read()
|
|
87
|
+
sample_profile_name = list(yaml.safe_load(sample_profile).keys())[0]
|
|
88
|
+
# Use a regex to replace the name of the sample_profile with
|
|
89
|
+
# that of the project without losing any comments from the sample
|
|
90
|
+
sample_profile = re.sub(f"^{sample_profile_name}:", f"{profile_name}:", sample_profile)
|
|
91
|
+
profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
|
|
92
|
+
if profiles_filepath.exists():
|
|
93
|
+
with open(profiles_filepath, "a") as f:
|
|
94
|
+
f.write("\n" + sample_profile)
|
|
95
|
+
else:
|
|
96
|
+
with open(profiles_filepath, "w") as f:
|
|
97
|
+
f.write(sample_profile)
|
|
98
|
+
fire_event(
|
|
99
|
+
ProfileWrittenWithSample(name=profile_name, path=str(profiles_filepath))
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def generate_target_from_input(self, profile_template: dict, target: dict = {}) -> dict:
|
|
103
|
+
"""Generate a target configuration from profile_template and user input."""
|
|
104
|
+
profile_template_local = copy.deepcopy(profile_template)
|
|
105
|
+
for key, value in profile_template_local.items():
|
|
106
|
+
if key.startswith("_choose"):
|
|
107
|
+
choice_type = key[8:].replace("_", " ")
|
|
108
|
+
option_list = list(value.keys())
|
|
109
|
+
prompt_msg = (
|
|
110
|
+
"\n".join([f"[{n + 1}] {v}" for n, v in enumerate(option_list)])
|
|
111
|
+
+ f"\nDesired {choice_type} option (enter a number)"
|
|
112
|
+
)
|
|
113
|
+
numeric_choice = click.prompt(prompt_msg, type=click.INT)
|
|
114
|
+
choice = option_list[numeric_choice - 1]
|
|
115
|
+
# Complete the chosen option's values in a recursive call
|
|
116
|
+
target = self.generate_target_from_input(
|
|
117
|
+
profile_template_local[key][choice], target
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
if key.startswith("_fixed"):
|
|
121
|
+
# _fixed prefixed keys are not presented to the user
|
|
122
|
+
target[key[7:]] = value
|
|
123
|
+
else:
|
|
124
|
+
hide_input = value.get("hide_input", False)
|
|
125
|
+
default = value.get("default", None)
|
|
126
|
+
hint = value.get("hint", None)
|
|
127
|
+
type = click_type_mapping[value.get("type", None)]
|
|
128
|
+
text = key + (f" ({hint})" if hint else "")
|
|
129
|
+
target[key] = click.prompt(
|
|
130
|
+
text, default=default, hide_input=hide_input, type=type
|
|
131
|
+
)
|
|
132
|
+
return target
|
|
133
|
+
|
|
134
|
+
def get_profile_name_from_current_project(self) -> str:
|
|
135
|
+
"""Reads dbt_project.yml in the current directory to retrieve the
|
|
136
|
+
profile name.
|
|
137
|
+
"""
|
|
138
|
+
with open("dbt_project.yml") as f:
|
|
139
|
+
dbt_project = yaml.safe_load(f)
|
|
140
|
+
return dbt_project["profile"]
|
|
141
|
+
|
|
142
|
+
def write_profile(self, profile: dict, profile_name: str):
|
|
143
|
+
"""Given a profile, write it to the current project's profiles.yml.
|
|
144
|
+
This will overwrite any profile with a matching name."""
|
|
145
|
+
# Create the profile directory if it doesn't exist
|
|
146
|
+
profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
|
|
147
|
+
|
|
148
|
+
profiles = {profile_name: profile}
|
|
149
|
+
|
|
150
|
+
if profiles_filepath.exists():
|
|
151
|
+
with open(profiles_filepath, "r") as f:
|
|
152
|
+
profiles = yaml.safe_load(f) or {}
|
|
153
|
+
profiles[profile_name] = profile
|
|
154
|
+
|
|
155
|
+
# Write the profiles dictionary to a brand-new or pre-existing file
|
|
156
|
+
with open(profiles_filepath, "w") as f:
|
|
157
|
+
yaml.dump(profiles, f)
|
|
158
|
+
|
|
159
|
+
def create_profile_from_profile_template(self, profile_template: dict, profile_name: str):
|
|
160
|
+
"""Create and write a profile using the supplied profile_template."""
|
|
161
|
+
initial_target = profile_template.get("fixed", {})
|
|
162
|
+
prompts = profile_template.get("prompts", {})
|
|
163
|
+
target = self.generate_target_from_input(prompts, initial_target)
|
|
164
|
+
target_name = target.pop("target", "dev")
|
|
165
|
+
profile = {"outputs": {target_name: target}, "target": target_name}
|
|
166
|
+
self.write_profile(profile, profile_name)
|
|
167
|
+
|
|
168
|
+
def create_profile_from_target(self, adapter: str, profile_name: str):
|
|
169
|
+
"""Create a profile without defaults using target's profile_template.yml if available, or
|
|
170
|
+
sample_profiles.yml as a fallback."""
|
|
171
|
+
# Line below raises an exception if the specified adapter is not found
|
|
172
|
+
load_plugin(adapter)
|
|
173
|
+
adapter_path = get_include_paths(adapter)[0]
|
|
174
|
+
profile_template_path = adapter_path / "profile_template.yml"
|
|
175
|
+
|
|
176
|
+
if profile_template_path.exists():
|
|
177
|
+
with open(profile_template_path) as f:
|
|
178
|
+
profile_template = yaml.safe_load(f)
|
|
179
|
+
self.create_profile_from_profile_template(profile_template, profile_name)
|
|
180
|
+
profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
|
|
181
|
+
fire_event(
|
|
182
|
+
ProfileWrittenWithTargetTemplateYAML(
|
|
183
|
+
name=profile_name, path=str(profiles_filepath)
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
# For adapters without a profile_template.yml defined, fallback on
|
|
188
|
+
# sample_profiles.yml
|
|
189
|
+
self.create_profile_from_sample(adapter, profile_name)
|
|
190
|
+
|
|
191
|
+
def check_if_profile_exists(self, profile_name: str) -> bool:
|
|
192
|
+
"""
|
|
193
|
+
Validate that the specified profile exists. Can't use the regular profile validation
|
|
194
|
+
routine because it assumes the project file exists
|
|
195
|
+
"""
|
|
196
|
+
profiles_dir = get_flags().PROFILES_DIR
|
|
197
|
+
raw_profiles = read_profile(profiles_dir)
|
|
198
|
+
return profile_name in raw_profiles
|
|
199
|
+
|
|
200
|
+
def check_if_can_write_profile(self, profile_name: Optional[str] = None) -> bool:
|
|
201
|
+
"""Using either a provided profile name or that specified in dbt_project.yml,
|
|
202
|
+
check if the profile already exists in profiles.yml, and if so ask the
|
|
203
|
+
user whether to proceed and overwrite it."""
|
|
204
|
+
profiles_file = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
|
|
205
|
+
if not profiles_file.exists():
|
|
206
|
+
return True
|
|
207
|
+
profile_name = profile_name or self.get_profile_name_from_current_project()
|
|
208
|
+
with open(profiles_file, "r") as f:
|
|
209
|
+
profiles = yaml.safe_load(f) or {}
|
|
210
|
+
if profile_name in profiles.keys():
|
|
211
|
+
# Profile already exists, just skip profile setup
|
|
212
|
+
click.echo(f"Profile '{profile_name}' already exists in {profiles_file}, skipping profile setup.")
|
|
213
|
+
return False
|
|
214
|
+
else:
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
def create_profile_using_project_profile_template(self, profile_name):
|
|
218
|
+
"""Create a profile using the project's profile_template.yml"""
|
|
219
|
+
with open("profile_template.yml") as f:
|
|
220
|
+
profile_template = yaml.safe_load(f)
|
|
221
|
+
self.create_profile_from_profile_template(profile_template, profile_name)
|
|
222
|
+
profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
|
|
223
|
+
fire_event(
|
|
224
|
+
ProfileWrittenWithProjectTemplateYAML(name=profile_name, path=str(profiles_filepath))
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def ask_for_adapter_choice(self) -> str:
|
|
228
|
+
"""Ask the user which adapter (database) they'd like to use."""
|
|
229
|
+
available_adapters = list(_get_adapter_plugin_names())
|
|
230
|
+
|
|
231
|
+
if not available_adapters:
|
|
232
|
+
raise dbt.exceptions.NoAdaptersAvailableError()
|
|
233
|
+
|
|
234
|
+
prompt_msg = (
|
|
235
|
+
"Which database would you like to use?\n"
|
|
236
|
+
+ "\n".join([f"[{n + 1}] {v}" for n, v in enumerate(available_adapters)])
|
|
237
|
+
+ "\n\n(Don't see the one you want? https://docs.getdbt.com/docs/available-adapters)"
|
|
238
|
+
+ "\n\nEnter a number"
|
|
239
|
+
)
|
|
240
|
+
numeric_choice = click.prompt(prompt_msg, type=click.INT)
|
|
241
|
+
return available_adapters[numeric_choice - 1]
|
|
242
|
+
|
|
243
|
+
def setup_profile(self, profile_name: str) -> None:
|
|
244
|
+
"""Set up a new profile for a project"""
|
|
245
|
+
fire_event(SettingUpProfile())
|
|
246
|
+
if not self.check_if_can_write_profile(profile_name=profile_name):
|
|
247
|
+
return
|
|
248
|
+
# If a profile_template.yml exists in the project root, that effectively
|
|
249
|
+
# overrides the profile_template.yml for the given target.
|
|
250
|
+
profile_template_path = Path("profile_template.yml")
|
|
251
|
+
if profile_template_path.exists():
|
|
252
|
+
try:
|
|
253
|
+
# This relies on a valid profile_template.yml from the user,
|
|
254
|
+
# so use a try: except to fall back to the default on failure
|
|
255
|
+
self.create_profile_using_project_profile_template(profile_name)
|
|
256
|
+
return
|
|
257
|
+
except Exception:
|
|
258
|
+
fire_event(InvalidProfileTemplateYAML())
|
|
259
|
+
adapter = self.ask_for_adapter_choice()
|
|
260
|
+
self.create_profile_from_target(adapter, profile_name=profile_name)
|
|
261
|
+
|
|
262
|
+
def get_adapter_metadata(self) -> dict:
|
|
263
|
+
"""Get categorized adapter information with descriptions - COMPREHENSIVE."""
|
|
264
|
+
return {
|
|
265
|
+
"Cloud Data Warehouses": {
|
|
266
|
+
"snowflake": {"name": "Snowflake", "desc": "Cloud data warehouse", "jdbc": True},
|
|
267
|
+
"bigquery": {"name": "Google BigQuery", "desc": "Serverless warehouse", "jdbc": True},
|
|
268
|
+
"databricks": {"name": "Databricks", "desc": "Lakehouse platform", "jdbc": True},
|
|
269
|
+
"redshift": {"name": "Amazon Redshift", "desc": "AWS warehouse", "jdbc": True},
|
|
270
|
+
"firebolt": {"name": "Firebolt", "desc": "Cloud DW for analytics", "jdbc": True},
|
|
271
|
+
},
|
|
272
|
+
"Microsoft Ecosystem": {
|
|
273
|
+
"fabric": {"name": "Microsoft Fabric", "desc": "Unified analytics", "jdbc": True},
|
|
274
|
+
"synapse": {"name": "Azure Synapse", "desc": "Analytics service", "jdbc": True},
|
|
275
|
+
"sqlserver": {"name": "SQL Server", "desc": "Enterprise RDBMS", "jdbc": True},
|
|
276
|
+
},
|
|
277
|
+
"Enterprise Data Warehouses": {
|
|
278
|
+
"teradata": {"name": "Teradata", "desc": "Enterprise warehouse", "jdbc": True},
|
|
279
|
+
"oracle": {"name": "Oracle Database", "desc": "Enterprise RDBMS", "jdbc": True},
|
|
280
|
+
"db2": {"name": "IBM DB2", "desc": "IBM database system", "jdbc": True},
|
|
281
|
+
"exasol": {"name": "Exasol", "desc": "In-memory analytics", "jdbc": True},
|
|
282
|
+
"vertica": {"name": "Vertica", "desc": "Columnar analytics", "jdbc": True},
|
|
283
|
+
},
|
|
284
|
+
"SQL Engines & Query Platforms": {
|
|
285
|
+
"spark": {"name": "Apache Spark", "desc": "Unified analytics", "jdbc": True},
|
|
286
|
+
"trino": {"name": "Trino", "desc": "Distributed SQL engine", "jdbc": True},
|
|
287
|
+
"presto": {"name": "Presto", "desc": "Meta's query engine", "jdbc": True},
|
|
288
|
+
"athena": {"name": "Amazon Athena", "desc": "Query S3 data", "jdbc": True},
|
|
289
|
+
"dremio": {"name": "Dremio", "desc": "Data lakehouse platform", "jdbc": True},
|
|
290
|
+
"hive": {"name": "Apache Hive", "desc": "Hadoop data warehouse", "jdbc": True},
|
|
291
|
+
"impala": {"name": "Cloudera Impala", "desc": "MPP SQL engine", "jdbc": True},
|
|
292
|
+
"glue": {"name": "AWS Glue", "desc": "Serverless ETL", "jdbc": True},
|
|
293
|
+
},
|
|
294
|
+
"Open Source Databases": {
|
|
295
|
+
"postgres": {"name": "PostgreSQL", "desc": "Popular open-source DB", "jdbc": True},
|
|
296
|
+
"mysql": {"name": "MySQL", "desc": "World's most popular DB", "jdbc": True},
|
|
297
|
+
"mariadb": {"name": "MariaDB", "desc": "MySQL fork", "jdbc": True},
|
|
298
|
+
"sqlite": {"name": "SQLite", "desc": "Embedded database", "jdbc": False},
|
|
299
|
+
"duckdb": {"name": "DuckDB", "desc": "In-process OLAP", "jdbc": False},
|
|
300
|
+
"cratedb": {"name": "CrateDB", "desc": "Distributed SQL", "jdbc": True},
|
|
301
|
+
},
|
|
302
|
+
"OLAP & Analytics Databases": {
|
|
303
|
+
"clickhouse": {"name": "ClickHouse", "desc": "Fast OLAP database", "jdbc": True},
|
|
304
|
+
"starrocks": {"name": "StarRocks", "desc": "MPP analytics", "jdbc": True},
|
|
305
|
+
"doris": {"name": "Apache Doris", "desc": "Real-time analytics", "jdbc": True},
|
|
306
|
+
"greenplum": {"name": "Greenplum", "desc": "MPP database", "jdbc": True},
|
|
307
|
+
"monetdb": {"name": "MonetDB", "desc": "Columnar database", "jdbc": True},
|
|
308
|
+
},
|
|
309
|
+
"Time-Series & Streaming": {
|
|
310
|
+
"timescaledb": {"name": "TimescaleDB", "desc": "PostgreSQL for time-series", "jdbc": True},
|
|
311
|
+
"questdb": {"name": "QuestDB", "desc": "Fast time-series", "jdbc": True},
|
|
312
|
+
"materialize": {"name": "Materialize", "desc": "Streaming SQL", "jdbc": True},
|
|
313
|
+
"rockset": {"name": "Rockset", "desc": "Real-time analytics", "jdbc": True},
|
|
314
|
+
},
|
|
315
|
+
"Data Lakes & Modern Formats": {
|
|
316
|
+
"iceberg": {"name": "Apache Iceberg", "desc": "Table format", "jdbc": True},
|
|
317
|
+
},
|
|
318
|
+
"Specialized & Emerging": {
|
|
319
|
+
"singlestore": {"name": "SingleStore", "desc": "Real-time analytics", "jdbc": True},
|
|
320
|
+
"neo4j": {"name": "Neo4j", "desc": "Graph database", "jdbc": True},
|
|
321
|
+
"mindsdb": {"name": "MindsDB", "desc": "ML database", "jdbc": True},
|
|
322
|
+
},
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
def ask_for_adapter_choice_enhanced(self, prompt_prefix: str = "") -> str:
|
|
326
|
+
"""Enhanced adapter selection with categories."""
|
|
327
|
+
metadata = self.get_adapter_metadata()
|
|
328
|
+
menu_lines = []
|
|
329
|
+
adapter_list = []
|
|
330
|
+
counter = 1
|
|
331
|
+
|
|
332
|
+
for category, adapters in metadata.items():
|
|
333
|
+
menu_lines.append(f"\n{category}:")
|
|
334
|
+
for adapter_key, info in adapters.items():
|
|
335
|
+
jdbc = " [JDBC]" if info["jdbc"] else ""
|
|
336
|
+
menu_lines.append(f" [{counter}] {info['name']}{jdbc} - {info['desc']}")
|
|
337
|
+
adapter_list.append(adapter_key)
|
|
338
|
+
counter += 1
|
|
339
|
+
|
|
340
|
+
prompt_msg = (
|
|
341
|
+
f"{prompt_prefix}" + "\n".join(menu_lines) +
|
|
342
|
+
"\n\nAll adapters support Spark JDBC federation\nEnter a number"
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
numeric_choice = click.prompt(prompt_msg, type=click.INT)
|
|
346
|
+
return adapter_list[numeric_choice - 1]
|
|
347
|
+
|
|
348
|
+
def ask_for_multi_connection_setup(self) -> bool:
|
|
349
|
+
"""Ask if user wants multi-connection setup."""
|
|
350
|
+
msg = (
|
|
351
|
+
"\nDVT supports multi-source data federation.\n"
|
|
352
|
+
"Set up multiple database connections?\n"
|
|
353
|
+
" [1] Yes - Multiple connections (recommended for federation)\n"
|
|
354
|
+
" [2] No - Single connection (can add more later)\n"
|
|
355
|
+
"\nEnter a number"
|
|
356
|
+
)
|
|
357
|
+
choice = click.prompt(msg, type=click.INT, default=1)
|
|
358
|
+
return choice == 1
|
|
359
|
+
|
|
360
|
+
def setup_multi_connection_profile(self, profile_name: str) -> int:
|
|
361
|
+
"""
|
|
362
|
+
Set up profile with multiple connections interactively.
|
|
363
|
+
Returns number of connections configured.
|
|
364
|
+
"""
|
|
365
|
+
fire_event(SettingUpProfile())
|
|
366
|
+
|
|
367
|
+
if not self.check_if_can_write_profile(profile_name=profile_name):
|
|
368
|
+
return 0
|
|
369
|
+
|
|
370
|
+
# Ask how many
|
|
371
|
+
num = click.prompt(
|
|
372
|
+
"\nHow many database connections? (1-10)",
|
|
373
|
+
type=click.INT,
|
|
374
|
+
default=2
|
|
375
|
+
)
|
|
376
|
+
num = max(1, min(10, num))
|
|
377
|
+
|
|
378
|
+
outputs = {}
|
|
379
|
+
|
|
380
|
+
for i in range(num):
|
|
381
|
+
click.echo(f"\n--- Connection {i+1}/{num} ---")
|
|
382
|
+
|
|
383
|
+
adapter = self.ask_for_adapter_choice_enhanced(
|
|
384
|
+
prompt_prefix=f"\nSelect database type for connection {i+1}:\n"
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
default_name = f"{adapter}_{i+1}" if i > 0 else adapter
|
|
388
|
+
conn_name = click.prompt(
|
|
389
|
+
f"Connection name",
|
|
390
|
+
default=default_name
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# Use adapter's profile template for prompts
|
|
394
|
+
load_plugin(adapter)
|
|
395
|
+
adapter_path = get_include_paths(adapter)[0]
|
|
396
|
+
template_path = adapter_path / "profile_template.yml"
|
|
397
|
+
|
|
398
|
+
if template_path.exists():
|
|
399
|
+
with open(template_path) as f:
|
|
400
|
+
template = yaml.safe_load(f)
|
|
401
|
+
prompts = template.get("prompts", {})
|
|
402
|
+
fixed = template.get("fixed", {})
|
|
403
|
+
target_config = self.generate_target_from_input(prompts, fixed)
|
|
404
|
+
target_config.pop("target", None)
|
|
405
|
+
outputs[conn_name] = target_config
|
|
406
|
+
else:
|
|
407
|
+
outputs[conn_name] = {"type": adapter}
|
|
408
|
+
|
|
409
|
+
# Set default target
|
|
410
|
+
output_names = list(outputs.keys())
|
|
411
|
+
default_target = output_names[0]
|
|
412
|
+
|
|
413
|
+
if len(output_names) > 1:
|
|
414
|
+
click.echo("\nAvailable connections:")
|
|
415
|
+
for idx, name in enumerate(output_names):
|
|
416
|
+
click.echo(f" [{idx+1}] {name}")
|
|
417
|
+
|
|
418
|
+
default_choice = click.prompt(
|
|
419
|
+
"\nDefault target? (enter number)",
|
|
420
|
+
type=click.INT,
|
|
421
|
+
default=1
|
|
422
|
+
)
|
|
423
|
+
default_target = output_names[max(0, min(default_choice - 1, len(output_names) - 1))]
|
|
424
|
+
|
|
425
|
+
# Write profile
|
|
426
|
+
profile = {"outputs": outputs, "target": default_target}
|
|
427
|
+
self.write_profile(profile, profile_name)
|
|
428
|
+
|
|
429
|
+
profiles_path = Path(get_flags().PROFILES_DIR) / "profiles.yml"
|
|
430
|
+
fire_event(ProfileWrittenWithTargetTemplateYAML(name=profile_name, path=str(profiles_path)))
|
|
431
|
+
|
|
432
|
+
return len(outputs)
|
|
433
|
+
|
|
434
|
+
def show_next_steps(self, project_name: str, num_connections: int) -> None:
|
|
435
|
+
"""Show helpful next steps."""
|
|
436
|
+
click.echo("\n" + "=" * 60)
|
|
437
|
+
click.echo("š DVT project initialized successfully!")
|
|
438
|
+
click.echo("=" * 60)
|
|
439
|
+
click.echo(f"\nProject: {project_name}")
|
|
440
|
+
click.echo(f"Connections: {num_connections} configured")
|
|
441
|
+
click.echo("\nš Next Steps:")
|
|
442
|
+
click.echo(f" 1. cd {project_name}")
|
|
443
|
+
click.echo(" 2. Edit models/example/my_first_dbt_model.sql")
|
|
444
|
+
click.echo(" 3. dvt run")
|
|
445
|
+
click.echo(" 4. dvt test")
|
|
446
|
+
click.echo("\nš Useful Commands:")
|
|
447
|
+
click.echo(" dvt target list # List connections")
|
|
448
|
+
click.echo(" dvt target test-all # Test all connections")
|
|
449
|
+
click.echo(" dvt compute list # Show Spark config")
|
|
450
|
+
click.echo(" dvt --help # See all commands")
|
|
451
|
+
click.echo("\n" + "=" * 60)
|
|
452
|
+
|
|
453
|
+
def get_valid_project_name(self) -> str:
|
|
454
|
+
"""Returns a valid project name, either from CLI arg or user prompt."""
|
|
455
|
+
|
|
456
|
+
# Lazy import to avoid ModuleNotFoundError
|
|
457
|
+
from dbt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
|
|
458
|
+
|
|
459
|
+
name = self.args.project_name
|
|
460
|
+
internal_package_names = {GLOBAL_PROJECT_NAME}
|
|
461
|
+
available_adapters = list(_get_adapter_plugin_names())
|
|
462
|
+
for adapter_name in available_adapters:
|
|
463
|
+
internal_package_names.update(f"dbt_{adapter_name}")
|
|
464
|
+
while not ProjectName.is_valid(name) or name in internal_package_names:
|
|
465
|
+
if name:
|
|
466
|
+
click.echo(name + " is not a valid project name.")
|
|
467
|
+
name = click.prompt("Enter a name for your project (letters, digits, underscore)")
|
|
468
|
+
|
|
469
|
+
return name
|
|
470
|
+
|
|
471
|
+
def create_new_project(self, project_name: str, profile_name: str):
|
|
472
|
+
self.copy_starter_repo(project_name)
|
|
473
|
+
os.chdir(project_name)
|
|
474
|
+
with open("dbt_project.yml", "r") as f:
|
|
475
|
+
content = f"{f.read()}".format(project_name=project_name, profile_name=profile_name)
|
|
476
|
+
with open("dbt_project.yml", "w") as f:
|
|
477
|
+
f.write(content)
|
|
478
|
+
|
|
479
|
+
# Create project-level .dvt/jdbc_jars/ directory
|
|
480
|
+
from dbt.config.compute import ComputeRegistry
|
|
481
|
+
ComputeRegistry.ensure_jdbc_jars_dir(".")
|
|
482
|
+
|
|
483
|
+
fire_event(
|
|
484
|
+
ProjectCreated(
|
|
485
|
+
project_name=project_name,
|
|
486
|
+
docs_url=DOCS_URL,
|
|
487
|
+
slack_url=SLACK_URL,
|
|
488
|
+
)
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
def run(self):
|
|
492
|
+
"""Entry point for the init task."""
|
|
493
|
+
profiles_dir = get_flags().PROFILES_DIR
|
|
494
|
+
self.create_profiles_dir(profiles_dir)
|
|
495
|
+
|
|
496
|
+
try:
|
|
497
|
+
move_to_nearest_project_dir(self.args.project_dir)
|
|
498
|
+
in_project = True
|
|
499
|
+
except dbt_common.exceptions.DbtRuntimeError:
|
|
500
|
+
in_project = False
|
|
501
|
+
|
|
502
|
+
if in_project:
|
|
503
|
+
# If --profile was specified, it means use an existing profile, which is not
|
|
504
|
+
# applicable to this case
|
|
505
|
+
if self.args.profile:
|
|
506
|
+
raise DbtRuntimeError(
|
|
507
|
+
msg="Can not init existing project with specified profile, edit dbt_project.yml instead"
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Ensure project-level .dvt/jdbc_jars/ directory exists
|
|
511
|
+
from dbt.config.compute import ComputeRegistry
|
|
512
|
+
ComputeRegistry.ensure_jdbc_jars_dir(".")
|
|
513
|
+
|
|
514
|
+
# When dbt init is run inside an existing project,
|
|
515
|
+
# just setup the user's profile.
|
|
516
|
+
if not self.args.skip_profile_setup:
|
|
517
|
+
# Get profile name from dbt_project.yml
|
|
518
|
+
profile_name = self.get_profile_name_from_current_project()
|
|
519
|
+
self.setup_profile(profile_name)
|
|
520
|
+
else:
|
|
521
|
+
# When dbt init is run outside of an existing project,
|
|
522
|
+
# create a new project and set up the user's profile.
|
|
523
|
+
project_name = self.get_valid_project_name()
|
|
524
|
+
project_path = Path(project_name)
|
|
525
|
+
if project_path.exists():
|
|
526
|
+
fire_event(ProjectNameAlreadyExists(name=project_name))
|
|
527
|
+
return
|
|
528
|
+
|
|
529
|
+
# If the user specified an existing profile to use, use it instead of generating a new one
|
|
530
|
+
user_profile_name = self.args.profile
|
|
531
|
+
if user_profile_name:
|
|
532
|
+
if not self.check_if_profile_exists(user_profile_name):
|
|
533
|
+
raise DbtRuntimeError(
|
|
534
|
+
msg="Could not find profile named '{}'".format(user_profile_name)
|
|
535
|
+
)
|
|
536
|
+
self.create_new_project(project_name, user_profile_name)
|
|
537
|
+
self.show_next_steps(project_name, 1)
|
|
538
|
+
else:
|
|
539
|
+
profile_name = project_name
|
|
540
|
+
# Create the profile after creating the project to avoid leaving a random profile
|
|
541
|
+
# if the former fails.
|
|
542
|
+
self.create_new_project(project_name, profile_name)
|
|
543
|
+
|
|
544
|
+
# DVT v0.5.1: Enhanced multi-connection init wizard
|
|
545
|
+
if not self.args.skip_profile_setup:
|
|
546
|
+
# Ask about multi-connection setup
|
|
547
|
+
if self.ask_for_multi_connection_setup():
|
|
548
|
+
num_conn = self.setup_multi_connection_profile(profile_name)
|
|
549
|
+
else:
|
|
550
|
+
self.setup_profile(profile_name)
|
|
551
|
+
num_conn = 1
|
|
552
|
+
|
|
553
|
+
self.show_next_steps(project_name, num_conn)
|