dvt-core 0.59.0a51__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +2660 -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 +60 -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 +642 -0
- dbt/compute/federated_executor.py +1080 -0
- dbt/compute/filter_pushdown.py +273 -0
- dbt/compute/jar_provisioning.py +273 -0
- dbt/compute/java_compat.py +689 -0
- dbt/compute/jdbc_utils.py +1252 -0
- dbt/compute/metadata/__init__.py +63 -0
- dbt/compute/metadata/adapters_registry.py +370 -0
- dbt/compute/metadata/catalog_store.py +1036 -0
- dbt/compute/metadata/registry.py +674 -0
- dbt/compute/metadata/store.py +1020 -0
- dbt/compute/smart_selector.py +377 -0
- dbt/compute/spark_logger.py +272 -0
- dbt/compute/strategies/__init__.py +55 -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 +472 -0
- dbt/compute/strategies/standalone.py +262 -0
- dbt/config/__init__.py +4 -0
- dbt/config/catalogs.py +94 -0
- dbt/config/compute.py +513 -0
- dbt/config/dvt_profile.py +408 -0
- dbt/config/profile.py +422 -0
- dbt/config/project.py +888 -0
- dbt/config/project_utils.py +48 -0
- dbt/config/renderer.py +231 -0
- dbt/config/runtime.py +564 -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 +419 -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_comprehensive_registry.py +1254 -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/dvt_starter_project/README.md +15 -0
- dbt/include/dvt_starter_project/__init__.py +3 -0
- dbt/include/dvt_starter_project/analyses/PLACEHOLDER +0 -0
- dbt/include/dvt_starter_project/dvt_project.yml +39 -0
- dbt/include/dvt_starter_project/logs/PLACEHOLDER +0 -0
- dbt/include/dvt_starter_project/macros/PLACEHOLDER +0 -0
- dbt/include/dvt_starter_project/models/example/my_first_dbt_model.sql +27 -0
- dbt/include/dvt_starter_project/models/example/my_second_dbt_model.sql +6 -0
- dbt/include/dvt_starter_project/models/example/schema.yml +21 -0
- dbt/include/dvt_starter_project/seeds/PLACEHOLDER +0 -0
- dbt/include/dvt_starter_project/snapshots/PLACEHOLDER +0 -0
- dbt/include/dvt_starter_project/tests/PLACEHOLDER +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 +122 -0
- dbt/parser/macros.py +137 -0
- dbt/parser/manifest.py +2208 -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.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 +506 -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 +458 -0
- dbt/task/debug.py +513 -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.py +204 -0
- dbt/task/docs/api/lineage.py +234 -0
- dbt/task/docs/api/profile.py +204 -0
- dbt/task/docs/api/spark.py +186 -0
- dbt/task/docs/generate.py +1002 -0
- dbt/task/docs/index.html +250 -0
- dbt/task/docs/serve.py +174 -0
- dbt/task/dvt_output.py +509 -0
- dbt/task/dvt_run.py +282 -0
- dbt/task/dvt_seed.py +806 -0
- dbt/task/freshness.py +322 -0
- dbt/task/function.py +121 -0
- dbt/task/group_lookup.py +46 -0
- dbt/task/init.py +1022 -0
- dbt/task/java.py +316 -0
- dbt/task/list.py +236 -0
- dbt/task/metadata.py +804 -0
- dbt/task/migrate.py +714 -0
- dbt/task/printer.py +175 -0
- dbt/task/profile.py +1489 -0
- dbt/task/profile_serve.py +662 -0
- dbt/task/retract.py +441 -0
- dbt/task/retry.py +175 -0
- dbt/task/run.py +1647 -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 +814 -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 +271 -0
- dvt_cli/__init__.py +158 -0
- dvt_core-0.59.0a51.dist-info/METADATA +288 -0
- dvt_core-0.59.0a51.dist-info/RECORD +299 -0
- dvt_core-0.59.0a51.dist-info/WHEEL +5 -0
- dvt_core-0.59.0a51.dist-info/entry_points.txt +2 -0
- dvt_core-0.59.0a51.dist-info/top_level.txt +2 -0
dbt/task/init.py
ADDED
|
@@ -0,0 +1,1022 @@
|
|
|
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.compute.metadata import ProjectMetadataStore, CatalogStore
|
|
15
|
+
from dbt.config.profile import read_profile
|
|
16
|
+
from dbt.contracts.util import Identifier as ProjectName
|
|
17
|
+
from dbt.events.types import (
|
|
18
|
+
ConfigFolderDirectory,
|
|
19
|
+
InvalidProfileTemplateYAML,
|
|
20
|
+
NoSampleProfileFound,
|
|
21
|
+
ProfileWrittenWithProjectTemplateYAML,
|
|
22
|
+
ProfileWrittenWithSample,
|
|
23
|
+
ProfileWrittenWithTargetTemplateYAML,
|
|
24
|
+
ProjectCreated,
|
|
25
|
+
ProjectNameAlreadyExists,
|
|
26
|
+
SettingUpProfile,
|
|
27
|
+
StarterProjectPath,
|
|
28
|
+
)
|
|
29
|
+
from dbt.flags import get_flags
|
|
30
|
+
from dbt.task.base import BaseTask, move_to_nearest_project_dir
|
|
31
|
+
from dbt.version import _get_adapter_plugin_names
|
|
32
|
+
from dbt_common.events.functions import fire_event
|
|
33
|
+
from dbt_common.exceptions import DbtRuntimeError
|
|
34
|
+
|
|
35
|
+
DOCS_URL = "https://docs.getdbt.com/docs/configure-your-profile"
|
|
36
|
+
SLACK_URL = "https://community.getdbt.com/"
|
|
37
|
+
|
|
38
|
+
# These files are not needed for the starter project but exist for finding the resource path
|
|
39
|
+
# PLACEHOLDER files are used instead of .gitkeep because setuptools doesn't include hidden files
|
|
40
|
+
# gitignore.txt is renamed to .gitignore after copying (setuptools doesn't include hidden files)
|
|
41
|
+
IGNORE_FILES = ["__init__.py", "__pycache__", "PLACEHOLDER", "gitignore.txt"]
|
|
42
|
+
|
|
43
|
+
# Content for .gitignore file (created programmatically because setuptools skips hidden files)
|
|
44
|
+
GITIGNORE_CONTENT = """target/
|
|
45
|
+
dbt_packages/
|
|
46
|
+
logs/
|
|
47
|
+
flatfiles/
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# https://click.palletsprojects.com/en/8.0.x/api/#types
|
|
52
|
+
# click v7.0 has UNPROCESSED, STRING, INT, FLOAT, BOOL, and UUID available.
|
|
53
|
+
click_type_mapping = {
|
|
54
|
+
"string": click.STRING,
|
|
55
|
+
"int": click.INT,
|
|
56
|
+
"float": click.FLOAT,
|
|
57
|
+
"bool": click.BOOL,
|
|
58
|
+
None: None,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class InitTask(BaseTask):
|
|
63
|
+
def copy_starter_repo(self, project_name: str) -> None:
|
|
64
|
+
# Lazy import to avoid ModuleNotFoundError
|
|
65
|
+
from dbt.include.dvt_starter_project import (
|
|
66
|
+
PACKAGE_PATH as starter_project_directory,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
fire_event(StarterProjectPath(dir=starter_project_directory))
|
|
70
|
+
shutil.copytree(
|
|
71
|
+
starter_project_directory, project_name, ignore=shutil.ignore_patterns(*IGNORE_FILES)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Create .gitignore file (setuptools doesn't include hidden files)
|
|
75
|
+
gitignore_path = Path(project_name) / ".gitignore"
|
|
76
|
+
with open(gitignore_path, "w") as f:
|
|
77
|
+
f.write(GITIGNORE_CONTENT)
|
|
78
|
+
|
|
79
|
+
def create_profiles_dir(self, profiles_dir: str) -> bool:
|
|
80
|
+
"""Create the user's profiles directory if it doesn't already exist."""
|
|
81
|
+
profiles_path = Path(profiles_dir)
|
|
82
|
+
if not profiles_path.exists():
|
|
83
|
+
fire_event(ConfigFolderDirectory(dir=str(profiles_dir)))
|
|
84
|
+
dbt_common.clients.system.make_directory(profiles_dir)
|
|
85
|
+
return True
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
def create_profile_from_sample(self, adapter: str, profile_name: str):
|
|
89
|
+
"""Create a profile entry using the adapter's sample_profiles.yml
|
|
90
|
+
|
|
91
|
+
Renames the profile in sample_profiles.yml to match that of the project."""
|
|
92
|
+
# Line below raises an exception if the specified adapter is not found
|
|
93
|
+
load_plugin(adapter)
|
|
94
|
+
adapter_path = get_include_paths(adapter)[0]
|
|
95
|
+
sample_profiles_path = adapter_path / "sample_profiles.yml"
|
|
96
|
+
|
|
97
|
+
if not sample_profiles_path.exists():
|
|
98
|
+
fire_event(NoSampleProfileFound(adapter=adapter))
|
|
99
|
+
else:
|
|
100
|
+
with open(sample_profiles_path, "r") as f:
|
|
101
|
+
sample_profile = f.read()
|
|
102
|
+
sample_profile_name = list(yaml.safe_load(sample_profile).keys())[0]
|
|
103
|
+
# Use a regex to replace the name of the sample_profile with
|
|
104
|
+
# that of the project without losing any comments from the sample
|
|
105
|
+
sample_profile = re.sub(f"^{sample_profile_name}:", f"{profile_name}:", sample_profile)
|
|
106
|
+
profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
|
|
107
|
+
if profiles_filepath.exists():
|
|
108
|
+
with open(profiles_filepath, "a") as f:
|
|
109
|
+
f.write("\n" + sample_profile)
|
|
110
|
+
else:
|
|
111
|
+
with open(profiles_filepath, "w") as f:
|
|
112
|
+
f.write(sample_profile)
|
|
113
|
+
fire_event(
|
|
114
|
+
ProfileWrittenWithSample(name=profile_name, path=str(profiles_filepath))
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def generate_target_from_input(self, profile_template: dict, target: dict = {}) -> dict:
|
|
118
|
+
"""Generate a target configuration from profile_template and user input."""
|
|
119
|
+
profile_template_local = copy.deepcopy(profile_template)
|
|
120
|
+
for key, value in profile_template_local.items():
|
|
121
|
+
if key.startswith("_choose"):
|
|
122
|
+
choice_type = key[8:].replace("_", " ")
|
|
123
|
+
option_list = list(value.keys())
|
|
124
|
+
prompt_msg = (
|
|
125
|
+
"\n".join([f"[{n + 1}] {v}" for n, v in enumerate(option_list)])
|
|
126
|
+
+ f"\nDesired {choice_type} option (enter a number)"
|
|
127
|
+
)
|
|
128
|
+
numeric_choice = click.prompt(prompt_msg, type=click.INT)
|
|
129
|
+
choice = option_list[numeric_choice - 1]
|
|
130
|
+
# Complete the chosen option's values in a recursive call
|
|
131
|
+
target = self.generate_target_from_input(
|
|
132
|
+
profile_template_local[key][choice], target
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
if key.startswith("_fixed"):
|
|
136
|
+
# _fixed prefixed keys are not presented to the user
|
|
137
|
+
target[key[7:]] = value
|
|
138
|
+
else:
|
|
139
|
+
hide_input = value.get("hide_input", False)
|
|
140
|
+
default = value.get("default", None)
|
|
141
|
+
hint = value.get("hint", None)
|
|
142
|
+
type = click_type_mapping[value.get("type", None)]
|
|
143
|
+
text = key + (f" ({hint})" if hint else "")
|
|
144
|
+
target[key] = click.prompt(
|
|
145
|
+
text, default=default, hide_input=hide_input, type=type
|
|
146
|
+
)
|
|
147
|
+
return target
|
|
148
|
+
|
|
149
|
+
def get_profile_name_from_current_project(self) -> str:
|
|
150
|
+
"""Reads dvt_project.yml in the current directory to retrieve the
|
|
151
|
+
profile name.
|
|
152
|
+
"""
|
|
153
|
+
with open("dvt_project.yml") as f:
|
|
154
|
+
dvt_project = yaml.safe_load(f)
|
|
155
|
+
return dvt_project["profile"]
|
|
156
|
+
|
|
157
|
+
def write_profile(self, profile: dict, profile_name: str):
|
|
158
|
+
"""Given a profile, write it to the current project's profiles.yml.
|
|
159
|
+
This will overwrite any profile with a matching name."""
|
|
160
|
+
# Create the profile directory if it doesn't exist
|
|
161
|
+
profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
|
|
162
|
+
|
|
163
|
+
profiles = {profile_name: profile}
|
|
164
|
+
|
|
165
|
+
if profiles_filepath.exists():
|
|
166
|
+
with open(profiles_filepath, "r") as f:
|
|
167
|
+
profiles = yaml.safe_load(f) or {}
|
|
168
|
+
profiles[profile_name] = profile
|
|
169
|
+
|
|
170
|
+
# Write the profiles dictionary to a brand-new or pre-existing file
|
|
171
|
+
with open(profiles_filepath, "w") as f:
|
|
172
|
+
yaml.dump(profiles, f)
|
|
173
|
+
|
|
174
|
+
def create_profile_from_profile_template(self, profile_template: dict, profile_name: str):
|
|
175
|
+
"""Create and write a profile using the supplied profile_template."""
|
|
176
|
+
initial_target = profile_template.get("fixed", {})
|
|
177
|
+
prompts = profile_template.get("prompts", {})
|
|
178
|
+
target = self.generate_target_from_input(prompts, initial_target)
|
|
179
|
+
target_name = target.pop("target", "dev")
|
|
180
|
+
profile = {"outputs": {target_name: target}, "target": target_name}
|
|
181
|
+
self.write_profile(profile, profile_name)
|
|
182
|
+
|
|
183
|
+
def create_profile_from_target(self, adapter: str, profile_name: str):
|
|
184
|
+
"""Create a profile without defaults using target's profile_template.yml if available, or
|
|
185
|
+
sample_profiles.yml as a fallback."""
|
|
186
|
+
# Line below raises an exception if the specified adapter is not found
|
|
187
|
+
load_plugin(adapter)
|
|
188
|
+
adapter_path = get_include_paths(adapter)[0]
|
|
189
|
+
profile_template_path = adapter_path / "profile_template.yml"
|
|
190
|
+
|
|
191
|
+
if profile_template_path.exists():
|
|
192
|
+
with open(profile_template_path) as f:
|
|
193
|
+
profile_template = yaml.safe_load(f)
|
|
194
|
+
self.create_profile_from_profile_template(profile_template, profile_name)
|
|
195
|
+
profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
|
|
196
|
+
fire_event(
|
|
197
|
+
ProfileWrittenWithTargetTemplateYAML(
|
|
198
|
+
name=profile_name, path=str(profiles_filepath)
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
else:
|
|
202
|
+
# For adapters without a profile_template.yml defined, fallback on
|
|
203
|
+
# sample_profiles.yml
|
|
204
|
+
self.create_profile_from_sample(adapter, profile_name)
|
|
205
|
+
|
|
206
|
+
def check_if_profile_exists(self, profile_name: str) -> bool:
|
|
207
|
+
"""
|
|
208
|
+
Validate that the specified profile exists. Can't use the regular profile validation
|
|
209
|
+
routine because it assumes the project file exists
|
|
210
|
+
"""
|
|
211
|
+
profiles_dir = get_flags().PROFILES_DIR
|
|
212
|
+
raw_profiles = read_profile(profiles_dir)
|
|
213
|
+
return profile_name in raw_profiles
|
|
214
|
+
|
|
215
|
+
def check_if_can_write_profile(self, profile_name: Optional[str] = None) -> bool:
|
|
216
|
+
"""Using either a provided profile name or that specified in dbt_project.yml,
|
|
217
|
+
check if the profile already exists in profiles.yml, and if so ask the
|
|
218
|
+
user whether to proceed and overwrite it."""
|
|
219
|
+
profiles_file = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
|
|
220
|
+
if not profiles_file.exists():
|
|
221
|
+
return True
|
|
222
|
+
profile_name = profile_name or self.get_profile_name_from_current_project()
|
|
223
|
+
with open(profiles_file, "r") as f:
|
|
224
|
+
profiles = yaml.safe_load(f) or {}
|
|
225
|
+
if profile_name in profiles.keys():
|
|
226
|
+
# Profile already exists, just skip profile setup
|
|
227
|
+
click.echo(f"Profile '{profile_name}' already exists in {profiles_file}, skipping profile setup.")
|
|
228
|
+
return False
|
|
229
|
+
else:
|
|
230
|
+
return True
|
|
231
|
+
|
|
232
|
+
def create_profile_using_project_profile_template(self, profile_name):
|
|
233
|
+
"""Create a profile using the project's profile_template.yml"""
|
|
234
|
+
with open("profile_template.yml") as f:
|
|
235
|
+
profile_template = yaml.safe_load(f)
|
|
236
|
+
self.create_profile_from_profile_template(profile_template, profile_name)
|
|
237
|
+
profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
|
|
238
|
+
fire_event(
|
|
239
|
+
ProfileWrittenWithProjectTemplateYAML(name=profile_name, path=str(profiles_filepath))
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def ask_for_adapter_choice(self) -> str:
|
|
243
|
+
"""Ask the user which adapter (database) they'd like to use."""
|
|
244
|
+
available_adapters = list(_get_adapter_plugin_names())
|
|
245
|
+
|
|
246
|
+
if not available_adapters:
|
|
247
|
+
raise dbt.exceptions.NoAdaptersAvailableError()
|
|
248
|
+
|
|
249
|
+
prompt_msg = (
|
|
250
|
+
"Which database would you like to use?\n"
|
|
251
|
+
+ "\n".join([f"[{n + 1}] {v}" for n, v in enumerate(available_adapters)])
|
|
252
|
+
+ "\n\n(Don't see the one you want? https://docs.getdbt.com/docs/available-adapters)"
|
|
253
|
+
+ "\n\nEnter a number"
|
|
254
|
+
)
|
|
255
|
+
numeric_choice = click.prompt(prompt_msg, type=click.INT)
|
|
256
|
+
return available_adapters[numeric_choice - 1]
|
|
257
|
+
|
|
258
|
+
def setup_profile(self, profile_name: str) -> None:
|
|
259
|
+
"""Set up a new profile for a project"""
|
|
260
|
+
fire_event(SettingUpProfile())
|
|
261
|
+
if not self.check_if_can_write_profile(profile_name=profile_name):
|
|
262
|
+
return
|
|
263
|
+
# If a profile_template.yml exists in the project root, that effectively
|
|
264
|
+
# overrides the profile_template.yml for the given target.
|
|
265
|
+
profile_template_path = Path("profile_template.yml")
|
|
266
|
+
if profile_template_path.exists():
|
|
267
|
+
try:
|
|
268
|
+
# This relies on a valid profile_template.yml from the user,
|
|
269
|
+
# so use a try: except to fall back to the default on failure
|
|
270
|
+
self.create_profile_using_project_profile_template(profile_name)
|
|
271
|
+
return
|
|
272
|
+
except Exception:
|
|
273
|
+
fire_event(InvalidProfileTemplateYAML())
|
|
274
|
+
adapter = self.ask_for_adapter_choice()
|
|
275
|
+
self.create_profile_from_target(adapter, profile_name=profile_name)
|
|
276
|
+
|
|
277
|
+
def get_valid_project_name(self) -> str:
|
|
278
|
+
"""Returns a valid project name, either from CLI arg or user prompt."""
|
|
279
|
+
|
|
280
|
+
# Lazy import to avoid ModuleNotFoundError
|
|
281
|
+
from dbt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
|
|
282
|
+
|
|
283
|
+
name = self.args.project_name
|
|
284
|
+
internal_package_names = {GLOBAL_PROJECT_NAME}
|
|
285
|
+
available_adapters = list(_get_adapter_plugin_names())
|
|
286
|
+
for adapter_name in available_adapters:
|
|
287
|
+
internal_package_names.update(f"dbt_{adapter_name}")
|
|
288
|
+
while not ProjectName.is_valid(name) or name in internal_package_names:
|
|
289
|
+
if name:
|
|
290
|
+
click.echo(name + " is not a valid project name.")
|
|
291
|
+
name = click.prompt("Enter a name for your project (letters, digits, underscore)")
|
|
292
|
+
|
|
293
|
+
return name
|
|
294
|
+
|
|
295
|
+
def create_new_project(self, project_name: str, profile_name: str):
|
|
296
|
+
self.copy_starter_repo(project_name)
|
|
297
|
+
os.chdir(project_name)
|
|
298
|
+
with open("dvt_project.yml", "r") as f:
|
|
299
|
+
content = f"{f.read()}".format(project_name=project_name, profile_name=profile_name)
|
|
300
|
+
with open("dvt_project.yml", "w") as f:
|
|
301
|
+
f.write(content)
|
|
302
|
+
|
|
303
|
+
# v0.59.0: Create project-level .dvt/ structure
|
|
304
|
+
# 1. Create .dvt/ directory
|
|
305
|
+
project_dvt_dir = Path(".") / ".dvt"
|
|
306
|
+
project_dvt_dir.mkdir(parents=True, exist_ok=True)
|
|
307
|
+
|
|
308
|
+
# 2. Create .dvt/jdbc_jars/ directory
|
|
309
|
+
from dbt.config.compute import ComputeRegistry
|
|
310
|
+
ComputeRegistry.ensure_jdbc_jars_dir(".")
|
|
311
|
+
|
|
312
|
+
# 3. Create .dvt/computes.yml with defaults
|
|
313
|
+
registry = ComputeRegistry(project_dir=".")
|
|
314
|
+
registry.ensure_config_exists()
|
|
315
|
+
click.echo(" ✓ Compute config initialized (.dvt/computes.yml)")
|
|
316
|
+
|
|
317
|
+
# 4. Initialize project metadata store (.dvt/metadata_store.duckdb)
|
|
318
|
+
self._initialize_metadata_store(Path("."))
|
|
319
|
+
|
|
320
|
+
# 5. Create default DuckDB database for starter profile
|
|
321
|
+
self._create_default_duckdb(Path("."))
|
|
322
|
+
|
|
323
|
+
# 6. v0.59.0: Create flatfiles/ directory
|
|
324
|
+
self._create_flatfiles_dir()
|
|
325
|
+
|
|
326
|
+
fire_event(
|
|
327
|
+
ProjectCreated(
|
|
328
|
+
project_name=project_name,
|
|
329
|
+
docs_url=DOCS_URL,
|
|
330
|
+
slack_url=SLACK_URL,
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def _create_project_in_place(self, project_name: str, profile_name: str) -> None:
|
|
335
|
+
"""
|
|
336
|
+
Create DVT project in the current directory (no folder creation).
|
|
337
|
+
|
|
338
|
+
v0.59.0a5: When user runs `dvt init` without a project name in an empty
|
|
339
|
+
folder, we initialize the project in place using the folder name.
|
|
340
|
+
|
|
341
|
+
This copies starter project contents to current directory and sets up
|
|
342
|
+
all DVT infrastructure (.dvt/, flatfiles/, etc.).
|
|
343
|
+
"""
|
|
344
|
+
from dbt.include.dvt_starter_project import (
|
|
345
|
+
PACKAGE_PATH as starter_project_directory,
|
|
346
|
+
)
|
|
347
|
+
from dbt.config.compute import ComputeRegistry
|
|
348
|
+
|
|
349
|
+
# Track if dvt_project.yml already existed (don't modify if so)
|
|
350
|
+
dvt_project_existed = Path("dvt_project.yml").exists()
|
|
351
|
+
|
|
352
|
+
# Copy starter project contents to current directory (not a subfolder)
|
|
353
|
+
for item in Path(starter_project_directory).iterdir():
|
|
354
|
+
if item.name in IGNORE_FILES or item.name.startswith('__'):
|
|
355
|
+
continue
|
|
356
|
+
dest = Path(".") / item.name
|
|
357
|
+
if item.is_dir():
|
|
358
|
+
if not dest.exists():
|
|
359
|
+
shutil.copytree(item, dest, ignore=shutil.ignore_patterns(*IGNORE_FILES))
|
|
360
|
+
else:
|
|
361
|
+
if not dest.exists():
|
|
362
|
+
shutil.copy2(item, dest)
|
|
363
|
+
|
|
364
|
+
# Create .gitignore file (setuptools doesn't include hidden files)
|
|
365
|
+
gitignore_path = Path(".") / ".gitignore"
|
|
366
|
+
if not gitignore_path.exists():
|
|
367
|
+
with open(gitignore_path, "w") as f:
|
|
368
|
+
f.write(GITIGNORE_CONTENT)
|
|
369
|
+
|
|
370
|
+
# Update dvt_project.yml with project name ONLY if it was just created
|
|
371
|
+
# (existing files may have Jinja templates that conflict with .format())
|
|
372
|
+
if not dvt_project_existed:
|
|
373
|
+
with open("dvt_project.yml", "r") as f:
|
|
374
|
+
content = f.read().replace("{project_name}", project_name).replace("{profile_name}", profile_name)
|
|
375
|
+
with open("dvt_project.yml", "w") as f:
|
|
376
|
+
f.write(content)
|
|
377
|
+
|
|
378
|
+
# Create .dvt/ directory
|
|
379
|
+
project_dvt_dir = Path(".") / ".dvt"
|
|
380
|
+
project_dvt_dir.mkdir(parents=True, exist_ok=True)
|
|
381
|
+
|
|
382
|
+
# Create .dvt/jdbc_jars/ directory
|
|
383
|
+
ComputeRegistry.ensure_jdbc_jars_dir(".")
|
|
384
|
+
|
|
385
|
+
# Create .dvt/computes.yml with defaults
|
|
386
|
+
registry = ComputeRegistry(project_dir=".")
|
|
387
|
+
registry.ensure_config_exists()
|
|
388
|
+
click.echo(" ✓ Compute config initialized (.dvt/computes.yml)")
|
|
389
|
+
|
|
390
|
+
# Initialize project metadata store
|
|
391
|
+
self._initialize_metadata_store(Path("."))
|
|
392
|
+
|
|
393
|
+
# Create default DuckDB database for starter profile
|
|
394
|
+
self._create_default_duckdb(Path("."))
|
|
395
|
+
|
|
396
|
+
# Create flatfiles/ directory
|
|
397
|
+
self._create_flatfiles_dir()
|
|
398
|
+
|
|
399
|
+
fire_event(
|
|
400
|
+
ProjectCreated(
|
|
401
|
+
project_name=project_name,
|
|
402
|
+
docs_url=DOCS_URL,
|
|
403
|
+
slack_url=SLACK_URL,
|
|
404
|
+
)
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
def _initialize_metadata_store(self, project_root: Path) -> None:
|
|
408
|
+
"""
|
|
409
|
+
Initialize DVT metadata stores in .dvt/ directory.
|
|
410
|
+
|
|
411
|
+
v0.59.0: Creates TWO project-level DuckDB databases:
|
|
412
|
+
|
|
413
|
+
1. metastore.duckdb - Runtime/operational data:
|
|
414
|
+
- column_metadata: Schema info from federated runs
|
|
415
|
+
- row_counts: Cached row counts from dvt snap
|
|
416
|
+
- profile_results: Data profiling from dvt profile
|
|
417
|
+
|
|
418
|
+
2. catalog.duckdb - Project catalog (federation-aware):
|
|
419
|
+
- targets: Available connections from profiles.yml
|
|
420
|
+
- source_definitions: Sources with connections from manifest
|
|
421
|
+
- model_definitions: Models with targets from manifest
|
|
422
|
+
- catalog_nodes: Enriched catalog for docs visualization
|
|
423
|
+
- lineage_edges: DAG lineage for visualization
|
|
424
|
+
|
|
425
|
+
This separation ensures catalog operations don't interfere with
|
|
426
|
+
runtime operations like profiling, run, build, etc.
|
|
427
|
+
|
|
428
|
+
NOTE: Static registry data (type mappings, syntax rules) comes from
|
|
429
|
+
the shipped adapters_registry.duckdb in ~/.dvt/.data/mdm.duckdb.
|
|
430
|
+
|
|
431
|
+
Both databases are overridden with empty schemas on each dvt init.
|
|
432
|
+
"""
|
|
433
|
+
try:
|
|
434
|
+
# 1. Initialize metastore.duckdb (runtime data)
|
|
435
|
+
metastore = ProjectMetadataStore(project_root)
|
|
436
|
+
metastore.initialize()
|
|
437
|
+
metastore.close()
|
|
438
|
+
click.echo(" ✓ Metastore initialized (.dvt/metastore.duckdb)")
|
|
439
|
+
|
|
440
|
+
# 2. Initialize catalog.duckdb (project catalog)
|
|
441
|
+
catalog = CatalogStore(project_root)
|
|
442
|
+
catalog.initialize()
|
|
443
|
+
catalog.close()
|
|
444
|
+
click.echo(" ✓ Catalog store initialized (.dvt/catalog.duckdb)")
|
|
445
|
+
|
|
446
|
+
except ImportError:
|
|
447
|
+
# DuckDB not installed - skip metadata stores (optional feature)
|
|
448
|
+
click.echo(" ⚠ DuckDB not installed - skipping metadata stores")
|
|
449
|
+
except Exception as e:
|
|
450
|
+
# Don't fail init on metadata store errors
|
|
451
|
+
click.echo(f" ⚠ Could not initialize metadata stores: {e}")
|
|
452
|
+
|
|
453
|
+
def _create_flatfiles_dir(self) -> None:
|
|
454
|
+
"""
|
|
455
|
+
Create empty flatfiles/ directory at project root (gitignored).
|
|
456
|
+
|
|
457
|
+
v0.59.0: For local file staging and data ingestion.
|
|
458
|
+
"""
|
|
459
|
+
flatfiles_dir = Path(".") / "flatfiles"
|
|
460
|
+
flatfiles_dir.mkdir(parents=True, exist_ok=True)
|
|
461
|
+
click.echo(" ✓ flatfiles/ directory created")
|
|
462
|
+
|
|
463
|
+
def _create_default_duckdb(self, project_root: Path) -> None:
|
|
464
|
+
"""
|
|
465
|
+
Create empty default.duckdb file for starter profile.
|
|
466
|
+
|
|
467
|
+
v0.59.0a14: Creates .dvt/default.duckdb so the DuckDB starter
|
|
468
|
+
profile works immediately without needing dbt-duckdb adapter.
|
|
469
|
+
The file is an empty DuckDB database.
|
|
470
|
+
"""
|
|
471
|
+
try:
|
|
472
|
+
import duckdb
|
|
473
|
+
duckdb_path = project_root / ".dvt" / "default.duckdb"
|
|
474
|
+
if not duckdb_path.exists():
|
|
475
|
+
# Create empty DuckDB database
|
|
476
|
+
conn = duckdb.connect(str(duckdb_path))
|
|
477
|
+
conn.close()
|
|
478
|
+
click.echo(" ✓ Default DuckDB database created (.dvt/default.duckdb)")
|
|
479
|
+
except ImportError:
|
|
480
|
+
# DuckDB not available - skip (user will need to install dbt-duckdb)
|
|
481
|
+
pass
|
|
482
|
+
except Exception as e:
|
|
483
|
+
click.echo(f" ⚠ Could not create default DuckDB: {e}")
|
|
484
|
+
|
|
485
|
+
def _add_project_profile(self, project_name: str, project_path: Optional[Path] = None) -> None:
|
|
486
|
+
"""
|
|
487
|
+
Add a project profile with a DuckDB starter adapter to ~/.dvt/profiles.yml.
|
|
488
|
+
|
|
489
|
+
v0.59.0a11: Uses ABSOLUTE path to project's .dvt/default.duckdb
|
|
490
|
+
v0.59.0a24: Ensures profiles directory is created if it doesn't exist.
|
|
491
|
+
Surgically adds 'dev' output if missing from existing profile.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
project_name: Name of the project to add profile for
|
|
495
|
+
project_path: Optional path to project directory (defaults to cwd)
|
|
496
|
+
"""
|
|
497
|
+
profiles_dir = Path(get_flags().PROFILES_DIR)
|
|
498
|
+
profiles_path = profiles_dir / "profiles.yml"
|
|
499
|
+
|
|
500
|
+
# v0.59.0a24: Ensure profiles directory exists
|
|
501
|
+
profiles_dir.mkdir(parents=True, exist_ok=True)
|
|
502
|
+
|
|
503
|
+
# Get absolute project path for DuckDB file
|
|
504
|
+
# Use provided path or fall back to current directory
|
|
505
|
+
project_dir = (project_path or Path.cwd()).resolve()
|
|
506
|
+
duckdb_path = str(project_dir / ".dvt" / "default.duckdb")
|
|
507
|
+
|
|
508
|
+
# DuckDB starter configuration with ABSOLUTE path
|
|
509
|
+
# v0.59.0a39: Path resolution added in profile loading to handle moved projects
|
|
510
|
+
duckdb_output = {
|
|
511
|
+
"type": "duckdb",
|
|
512
|
+
"path": duckdb_path,
|
|
513
|
+
"threads": 4,
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if profiles_path.exists():
|
|
517
|
+
# Read existing profiles
|
|
518
|
+
with open(profiles_path, "r") as f:
|
|
519
|
+
existing_profiles = yaml.safe_load(f) or {}
|
|
520
|
+
|
|
521
|
+
action = None
|
|
522
|
+
|
|
523
|
+
if project_name in existing_profiles:
|
|
524
|
+
profile_data = existing_profiles[project_name]
|
|
525
|
+
|
|
526
|
+
if profile_data and isinstance(profile_data, dict):
|
|
527
|
+
outputs = profile_data.get("outputs", {})
|
|
528
|
+
|
|
529
|
+
if outputs and isinstance(outputs, dict):
|
|
530
|
+
# Check if 'dev' output exists
|
|
531
|
+
if "dev" in outputs:
|
|
532
|
+
# Profile has 'dev' output - check if it's DuckDB type
|
|
533
|
+
click.echo(f" Profile '{project_name}' already configured in profiles.yml")
|
|
534
|
+
return
|
|
535
|
+
else:
|
|
536
|
+
# Profile has outputs but missing 'dev' - surgically add it
|
|
537
|
+
outputs["dev"] = duckdb_output
|
|
538
|
+
profile_data["outputs"] = outputs
|
|
539
|
+
existing_profiles[project_name] = profile_data
|
|
540
|
+
action = "add_dev"
|
|
541
|
+
else:
|
|
542
|
+
# Profile has no outputs - add dev
|
|
543
|
+
profile_data["outputs"] = {"dev": duckdb_output}
|
|
544
|
+
if "target" not in profile_data:
|
|
545
|
+
profile_data["target"] = "dev"
|
|
546
|
+
existing_profiles[project_name] = profile_data
|
|
547
|
+
action = "fix_outputs"
|
|
548
|
+
else:
|
|
549
|
+
# Profile is None or invalid - create fresh
|
|
550
|
+
existing_profiles[project_name] = {
|
|
551
|
+
"target": "dev",
|
|
552
|
+
"outputs": {"dev": duckdb_output}
|
|
553
|
+
}
|
|
554
|
+
action = "fix_invalid"
|
|
555
|
+
else:
|
|
556
|
+
# Profile doesn't exist - add it
|
|
557
|
+
existing_profiles[project_name] = {
|
|
558
|
+
"target": "dev",
|
|
559
|
+
"outputs": {"dev": duckdb_output}
|
|
560
|
+
}
|
|
561
|
+
action = "add"
|
|
562
|
+
|
|
563
|
+
# Write profiles back with header
|
|
564
|
+
with open(profiles_path, "w") as f:
|
|
565
|
+
f.write("# DVT Profiles Configuration\n")
|
|
566
|
+
f.write("# =============================================================================\n")
|
|
567
|
+
f.write("# Configure your database connections below. For documentation, see:\n")
|
|
568
|
+
f.write("# https://docs.getdbt.com/docs/configure-your-profile\n")
|
|
569
|
+
f.write("#\n")
|
|
570
|
+
f.write("# After modifying connections, run: dvt target sync\n")
|
|
571
|
+
f.write("# =============================================================================\n\n")
|
|
572
|
+
yaml.dump(existing_profiles, f, default_flow_style=False, sort_keys=False)
|
|
573
|
+
|
|
574
|
+
# Print appropriate message
|
|
575
|
+
if action == "add":
|
|
576
|
+
click.echo(f" ✓ Profile '{project_name}' added to profiles.yml")
|
|
577
|
+
elif action == "add_dev":
|
|
578
|
+
click.echo(f" ✓ Added 'dev' (DuckDB) output to profile '{project_name}'")
|
|
579
|
+
elif action == "fix_outputs":
|
|
580
|
+
click.echo(f" ✓ Profile '{project_name}' updated with DuckDB starter output")
|
|
581
|
+
elif action == "fix_invalid":
|
|
582
|
+
click.echo(f" ✓ Profile '{project_name}' fixed with DuckDB starter configuration")
|
|
583
|
+
else:
|
|
584
|
+
# Create new profiles.yml with header and profile
|
|
585
|
+
new_profile = {
|
|
586
|
+
project_name: {
|
|
587
|
+
"target": "dev",
|
|
588
|
+
"outputs": {"dev": duckdb_output}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
with open(profiles_path, "w") as f:
|
|
592
|
+
f.write("# DVT Profiles Configuration\n")
|
|
593
|
+
f.write("# =============================================================================\n")
|
|
594
|
+
f.write("# Configure your database connections below. For documentation, see:\n")
|
|
595
|
+
f.write("# https://docs.getdbt.com/docs/configure-your-profile\n")
|
|
596
|
+
f.write("#\n")
|
|
597
|
+
f.write("# After modifying connections, run: dvt target sync\n")
|
|
598
|
+
f.write("# =============================================================================\n\n")
|
|
599
|
+
yaml.dump(new_profile, f, default_flow_style=False, sort_keys=False)
|
|
600
|
+
|
|
601
|
+
click.echo(f" ✓ profiles.yml created with '{project_name}' profile")
|
|
602
|
+
|
|
603
|
+
def _convert_dbt_to_dvt(self) -> None:
|
|
604
|
+
"""
|
|
605
|
+
Convert dbt project to DVT project.
|
|
606
|
+
|
|
607
|
+
v0.59.0a2: When `dvt init` is issued inside a dbt project (has dbt_project.yml
|
|
608
|
+
but no dvt_project.yml), this method:
|
|
609
|
+
|
|
610
|
+
1. Backs up dbt_project.yml → dbt_project.yml.bak
|
|
611
|
+
2. Gets project name and profile from dbt_project.yml
|
|
612
|
+
3. Creates fresh dvt_project.yml from standard dbt init template + DVT additions
|
|
613
|
+
4. Creates flatfiles/ directory
|
|
614
|
+
5. Migrates project-specific profile from ~/.dbt/profiles.yml to ~/.dvt/profiles.yml
|
|
615
|
+
|
|
616
|
+
Note: Fresh template is used for consistency. User can copy project-specific
|
|
617
|
+
configs (like models: overrides) from the backup if needed.
|
|
618
|
+
"""
|
|
619
|
+
import shutil
|
|
620
|
+
|
|
621
|
+
dbt_project_file = Path("dbt_project.yml")
|
|
622
|
+
backup_file = Path("dbt_project.yml.bak")
|
|
623
|
+
|
|
624
|
+
# 1. Backup dbt_project.yml
|
|
625
|
+
if dbt_project_file.exists():
|
|
626
|
+
if backup_file.exists():
|
|
627
|
+
# Add timestamp if backup already exists
|
|
628
|
+
import time
|
|
629
|
+
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
|
630
|
+
backup_file = Path(f"dbt_project.yml.{timestamp}.bak")
|
|
631
|
+
shutil.copy2(dbt_project_file, backup_file)
|
|
632
|
+
click.echo(f" ✓ Backed up dbt_project.yml → {backup_file.name}")
|
|
633
|
+
|
|
634
|
+
# 2. Get project name and profile from dbt_project.yml
|
|
635
|
+
with open(dbt_project_file, "r") as f:
|
|
636
|
+
dbt_content = f.read()
|
|
637
|
+
|
|
638
|
+
project_name = Path.cwd().name
|
|
639
|
+
profile_name = project_name # Default to project name
|
|
640
|
+
try:
|
|
641
|
+
config = yaml.safe_load(dbt_content)
|
|
642
|
+
if config:
|
|
643
|
+
if "name" in config:
|
|
644
|
+
project_name = config["name"]
|
|
645
|
+
if "profile" in config:
|
|
646
|
+
profile_name = config["profile"]
|
|
647
|
+
except Exception:
|
|
648
|
+
pass
|
|
649
|
+
|
|
650
|
+
# 3. Create fresh dvt_project.yml from standard dbt init template + DVT additions
|
|
651
|
+
# This matches what dbt init creates, plus DVT-specific flatfile-paths
|
|
652
|
+
dvt_project_content = f"""name: '{project_name}'
|
|
653
|
+
version: '1.0.0'
|
|
654
|
+
|
|
655
|
+
# This setting configures which "profile" dbt uses for this project.
|
|
656
|
+
profile: '{profile_name}'
|
|
657
|
+
|
|
658
|
+
# These configurations specify where dbt should look for different types of files.
|
|
659
|
+
# The `model-paths` config, for example, states that models in this project can be
|
|
660
|
+
# found in the "models/" directory. You probably won't need to change these!
|
|
661
|
+
model-paths: ["models"]
|
|
662
|
+
analysis-paths: ["analyses"]
|
|
663
|
+
test-paths: ["tests"]
|
|
664
|
+
seed-paths: ["seeds"]
|
|
665
|
+
macro-paths: ["macros"]
|
|
666
|
+
snapshot-paths: ["snapshots"]
|
|
667
|
+
|
|
668
|
+
# DVT-specific: Path for flat files (CSV, Parquet, etc.) for local data ingestion
|
|
669
|
+
flatfile-paths: ["flatfiles"]
|
|
670
|
+
|
|
671
|
+
clean-targets:
|
|
672
|
+
- "target"
|
|
673
|
+
- "dbt_packages"
|
|
674
|
+
|
|
675
|
+
# Configuring models
|
|
676
|
+
# Full documentation: https://docs.getdbt.com/docs/configuring-models
|
|
677
|
+
|
|
678
|
+
# In this example config, we tell dbt to build all models in the example/
|
|
679
|
+
# directory as views. These settings can be overridden in the individual model
|
|
680
|
+
# files using the `{{ config(...) }}` macro.
|
|
681
|
+
models:
|
|
682
|
+
{project_name}:
|
|
683
|
+
# Config indicated by + and applies to all files under models/example/
|
|
684
|
+
+materialized: view
|
|
685
|
+
"""
|
|
686
|
+
with open("dvt_project.yml", "w") as f:
|
|
687
|
+
f.write(dvt_project_content)
|
|
688
|
+
click.echo(f" ✓ Created dvt_project.yml")
|
|
689
|
+
|
|
690
|
+
# 5. Create flatfiles/ directory
|
|
691
|
+
flatfiles_dir = Path("flatfiles")
|
|
692
|
+
flatfiles_dir.mkdir(exist_ok=True)
|
|
693
|
+
# Add .gitignore to flatfiles/
|
|
694
|
+
gitignore_path = flatfiles_dir / ".gitignore"
|
|
695
|
+
if not gitignore_path.exists():
|
|
696
|
+
with open(gitignore_path, "w") as f:
|
|
697
|
+
f.write("# Ignore all flat files (CSV, Parquet, etc.)\n*\n!.gitignore\n")
|
|
698
|
+
click.echo(" ✓ Created flatfiles/ directory")
|
|
699
|
+
|
|
700
|
+
# 6. Migrate profiles from ~/.dbt/ to ~/.dvt/
|
|
701
|
+
from dbt.task.migrate import MigrateTask
|
|
702
|
+
task = MigrateTask(profiles_only=True)
|
|
703
|
+
task._migrate_profiles()
|
|
704
|
+
|
|
705
|
+
# Get project name from the content for display
|
|
706
|
+
project_name = Path.cwd().name
|
|
707
|
+
try:
|
|
708
|
+
config = yaml.safe_load(dbt_content)
|
|
709
|
+
if config and "name" in config:
|
|
710
|
+
project_name = config["name"]
|
|
711
|
+
except Exception:
|
|
712
|
+
pass
|
|
713
|
+
|
|
714
|
+
click.echo("")
|
|
715
|
+
click.echo("=" * 60)
|
|
716
|
+
click.echo("DVT project initialized from dbt project!")
|
|
717
|
+
click.echo("=" * 60)
|
|
718
|
+
click.echo(f" Project: {project_name}")
|
|
719
|
+
click.echo(f" Config: dvt_project.yml")
|
|
720
|
+
click.echo(f" Backup: {backup_file.name}")
|
|
721
|
+
click.echo("")
|
|
722
|
+
click.echo("Next steps:")
|
|
723
|
+
click.echo(" 1. Review dvt_project.yml")
|
|
724
|
+
click.echo(" 2. Run 'dvt target list' to verify connections")
|
|
725
|
+
click.echo(" 3. Run 'dvt run' to build your models")
|
|
726
|
+
click.echo("=" * 60)
|
|
727
|
+
|
|
728
|
+
def _initialize_user_metadata_db(self) -> None:
|
|
729
|
+
"""
|
|
730
|
+
Initialize the user-level metadata database at ~/.dvt/.data/mdm.duckdb.
|
|
731
|
+
|
|
732
|
+
v0.59.0: Copies data from the packaged adapters_registry.duckdb to the
|
|
733
|
+
user-level database. This ensures users have access to type mappings,
|
|
734
|
+
syntax rules, and adapter queries even if the package is not accessible.
|
|
735
|
+
|
|
736
|
+
ALWAYS recreates the database on init for easy testing and updates.
|
|
737
|
+
"""
|
|
738
|
+
try:
|
|
739
|
+
import duckdb
|
|
740
|
+
from dbt.compute.metadata import AdaptersRegistry
|
|
741
|
+
|
|
742
|
+
# Get paths
|
|
743
|
+
dvt_home = Path.home() / ".dvt"
|
|
744
|
+
data_dir = dvt_home / ".data"
|
|
745
|
+
user_db_path = data_dir / "mdm.duckdb"
|
|
746
|
+
|
|
747
|
+
# Create directories
|
|
748
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
|
749
|
+
|
|
750
|
+
# ALWAYS recreate for testing (remove if exists)
|
|
751
|
+
if user_db_path.exists():
|
|
752
|
+
user_db_path.unlink()
|
|
753
|
+
|
|
754
|
+
# Get packaged registry path
|
|
755
|
+
registry = AdaptersRegistry()
|
|
756
|
+
source_db_path = registry.get_registry_path()
|
|
757
|
+
|
|
758
|
+
# Connect to source (read-only) and destination
|
|
759
|
+
source_conn = duckdb.connect(str(source_db_path), read_only=True)
|
|
760
|
+
dest_conn = duckdb.connect(str(user_db_path))
|
|
761
|
+
|
|
762
|
+
# Get data from source and copy to destination
|
|
763
|
+
# (DuckDB doesn't support cross-database queries, so we fetch and insert)
|
|
764
|
+
|
|
765
|
+
# Copy datatype_mappings table
|
|
766
|
+
mappings = source_conn.execute("SELECT * FROM datatype_mappings").fetchall()
|
|
767
|
+
dest_conn.execute("DROP TABLE IF EXISTS datatype_mappings")
|
|
768
|
+
dest_conn.execute("""
|
|
769
|
+
CREATE TABLE datatype_mappings (
|
|
770
|
+
adapter_name VARCHAR,
|
|
771
|
+
adapter_type VARCHAR,
|
|
772
|
+
spark_type VARCHAR,
|
|
773
|
+
spark_version VARCHAR,
|
|
774
|
+
is_complex BOOLEAN,
|
|
775
|
+
cast_expression VARCHAR,
|
|
776
|
+
notes VARCHAR
|
|
777
|
+
)
|
|
778
|
+
""")
|
|
779
|
+
|
|
780
|
+
# Insert data
|
|
781
|
+
if mappings:
|
|
782
|
+
dest_conn.executemany(
|
|
783
|
+
"INSERT INTO datatype_mappings VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
784
|
+
mappings
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
# Copy adapter_queries table
|
|
788
|
+
queries = source_conn.execute("SELECT * FROM adapter_queries").fetchall()
|
|
789
|
+
dest_conn.execute("DROP TABLE IF EXISTS adapter_queries")
|
|
790
|
+
dest_conn.execute("""
|
|
791
|
+
CREATE TABLE adapter_queries (
|
|
792
|
+
adapter_name VARCHAR,
|
|
793
|
+
query_type VARCHAR,
|
|
794
|
+
query_template VARCHAR,
|
|
795
|
+
notes VARCHAR
|
|
796
|
+
)
|
|
797
|
+
""")
|
|
798
|
+
if queries:
|
|
799
|
+
dest_conn.executemany(
|
|
800
|
+
"INSERT INTO adapter_queries VALUES (?, ?, ?, ?)",
|
|
801
|
+
queries
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
# Copy syntax_registry table
|
|
805
|
+
syntax = source_conn.execute("SELECT * FROM syntax_registry").fetchall()
|
|
806
|
+
dest_conn.execute("DROP TABLE IF EXISTS syntax_registry")
|
|
807
|
+
dest_conn.execute("""
|
|
808
|
+
CREATE TABLE syntax_registry (
|
|
809
|
+
adapter_name VARCHAR,
|
|
810
|
+
quote_start VARCHAR,
|
|
811
|
+
quote_end VARCHAR,
|
|
812
|
+
case_sensitivity VARCHAR,
|
|
813
|
+
reserved_keywords VARCHAR
|
|
814
|
+
)
|
|
815
|
+
""")
|
|
816
|
+
if syntax:
|
|
817
|
+
dest_conn.executemany(
|
|
818
|
+
"INSERT INTO syntax_registry VALUES (?, ?, ?, ?, ?)",
|
|
819
|
+
syntax
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
# Copy value_transformations table (v0.59.0a32: seed pattern transformations)
|
|
823
|
+
transforms = []
|
|
824
|
+
try:
|
|
825
|
+
transforms = source_conn.execute("SELECT * FROM value_transformations").fetchall()
|
|
826
|
+
dest_conn.execute("DROP TABLE IF EXISTS value_transformations")
|
|
827
|
+
dest_conn.execute("""
|
|
828
|
+
CREATE TABLE value_transformations (
|
|
829
|
+
pattern VARCHAR NOT NULL,
|
|
830
|
+
target_type VARCHAR NOT NULL,
|
|
831
|
+
transform_expr VARCHAR NOT NULL,
|
|
832
|
+
priority INTEGER DEFAULT 50,
|
|
833
|
+
description VARCHAR,
|
|
834
|
+
PRIMARY KEY (pattern)
|
|
835
|
+
)
|
|
836
|
+
""")
|
|
837
|
+
if transforms:
|
|
838
|
+
dest_conn.executemany(
|
|
839
|
+
"INSERT INTO value_transformations VALUES (?, ?, ?, ?, ?)",
|
|
840
|
+
transforms
|
|
841
|
+
)
|
|
842
|
+
except Exception:
|
|
843
|
+
# Table might not exist in older registries
|
|
844
|
+
pass
|
|
845
|
+
|
|
846
|
+
source_conn.close()
|
|
847
|
+
dest_conn.close()
|
|
848
|
+
|
|
849
|
+
mappings_count = len(mappings) if mappings else 0
|
|
850
|
+
queries_count = len(queries) if queries else 0
|
|
851
|
+
syntax_count = len(syntax) if syntax else 0
|
|
852
|
+
transforms_count = len(transforms) if transforms else 0
|
|
853
|
+
|
|
854
|
+
click.echo(f" ✓ User metadata database initialized (~/.dvt/.data/mdm.duckdb)")
|
|
855
|
+
click.echo(f" - {mappings_count} type mappings (all adapters x all types x all Spark versions)")
|
|
856
|
+
click.echo(f" - {queries_count} adapter queries")
|
|
857
|
+
click.echo(f" - {syntax_count} syntax rules")
|
|
858
|
+
if transforms_count > 0:
|
|
859
|
+
click.echo(f" - {transforms_count} value transformation patterns (for dvt seed)")
|
|
860
|
+
|
|
861
|
+
except ImportError:
|
|
862
|
+
# DuckDB not installed - skip
|
|
863
|
+
click.echo(" ⚠ DuckDB not installed - skipping user metadata database")
|
|
864
|
+
except Exception as e:
|
|
865
|
+
# Don't fail init on metadata errors
|
|
866
|
+
click.echo(f" ⚠ Could not initialize user metadata database: {e}")
|
|
867
|
+
|
|
868
|
+
def _show_init_success(self, project_name: str) -> None:
|
|
869
|
+
"""
|
|
870
|
+
Display success message and next steps after project initialization.
|
|
871
|
+
|
|
872
|
+
v0.59.0: Non-interactive flow - a DuckDB starter adapter is created,
|
|
873
|
+
user can modify it to use their actual database connections.
|
|
874
|
+
"""
|
|
875
|
+
click.echo("")
|
|
876
|
+
click.echo("=" * 60)
|
|
877
|
+
click.echo("DVT project initialized successfully!")
|
|
878
|
+
click.echo("=" * 60)
|
|
879
|
+
click.echo(f"\nProject: {project_name}")
|
|
880
|
+
click.echo(f"Profile: ~/.dvt/profiles.yml")
|
|
881
|
+
click.echo("")
|
|
882
|
+
click.echo("A DuckDB starter adapter has been configured.")
|
|
883
|
+
click.echo("Modify ~/.dvt/profiles.yml to add your database connections.")
|
|
884
|
+
click.echo("")
|
|
885
|
+
click.echo("Next Steps:")
|
|
886
|
+
click.echo(f" 1. cd {project_name}")
|
|
887
|
+
click.echo(" 2. dvt run # Test with DuckDB starter")
|
|
888
|
+
click.echo(" 3. Edit ~/.dvt/profiles.yml to add real connections")
|
|
889
|
+
click.echo(" 4. dvt target list # Verify connections")
|
|
890
|
+
click.echo("")
|
|
891
|
+
click.echo("Docs: https://docs.getdbt.com/docs/configure-your-profile")
|
|
892
|
+
click.echo("=" * 60)
|
|
893
|
+
|
|
894
|
+
def run(self):
|
|
895
|
+
"""Entry point for the init task."""
|
|
896
|
+
profiles_dir = get_flags().PROFILES_DIR
|
|
897
|
+
# Ensure profiles_dir is a string (may be PosixPath from default_profiles_dir())
|
|
898
|
+
if hasattr(profiles_dir, '__fspath__'):
|
|
899
|
+
profiles_dir = str(profiles_dir)
|
|
900
|
+
self.create_profiles_dir(profiles_dir)
|
|
901
|
+
|
|
902
|
+
# v0.58.8: Initialize user-level metadata database with packaged registry data
|
|
903
|
+
self._initialize_user_metadata_db()
|
|
904
|
+
|
|
905
|
+
try:
|
|
906
|
+
move_to_nearest_project_dir(self.args.project_dir)
|
|
907
|
+
in_project = True
|
|
908
|
+
except dbt_common.exceptions.DbtRuntimeError:
|
|
909
|
+
in_project = False
|
|
910
|
+
|
|
911
|
+
# v0.59.0: Check for dbt project that needs migration
|
|
912
|
+
dbt_project_file = Path("dbt_project.yml")
|
|
913
|
+
dvt_project_file = Path("dvt_project.yml")
|
|
914
|
+
in_dbt_project = dbt_project_file.exists() and not dvt_project_file.exists()
|
|
915
|
+
|
|
916
|
+
if in_dbt_project:
|
|
917
|
+
# Detected dbt project - convert to DVT project
|
|
918
|
+
click.echo("Detected existing dbt project (dbt_project.yml).")
|
|
919
|
+
click.echo("Converting to DVT project...")
|
|
920
|
+
click.echo("")
|
|
921
|
+
self._convert_dbt_to_dvt()
|
|
922
|
+
in_project = True # Now we have a DVT project
|
|
923
|
+
|
|
924
|
+
if in_project:
|
|
925
|
+
# If --profile was specified, it means use an existing profile, which is not
|
|
926
|
+
# applicable to this case
|
|
927
|
+
if self.args.profile:
|
|
928
|
+
raise DbtRuntimeError(
|
|
929
|
+
msg="Can not init existing project with specified profile, edit dvt_project.yml instead"
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
# v0.55.0: Ensure project-level .dvt/ structure exists
|
|
933
|
+
from dbt.config.compute import ComputeRegistry
|
|
934
|
+
|
|
935
|
+
# Create .dvt/ directory and jdbc_jars/
|
|
936
|
+
ComputeRegistry.ensure_jdbc_jars_dir(".")
|
|
937
|
+
|
|
938
|
+
# Ensure computes.yml exists at project level
|
|
939
|
+
registry = ComputeRegistry(project_dir=".")
|
|
940
|
+
registry.ensure_config_exists()
|
|
941
|
+
|
|
942
|
+
# Initialize metadata store if not already present
|
|
943
|
+
self._initialize_metadata_store(Path("."))
|
|
944
|
+
|
|
945
|
+
# Create default DuckDB database for starter profile
|
|
946
|
+
self._create_default_duckdb(Path("."))
|
|
947
|
+
|
|
948
|
+
# v0.59.0a11: Ensure profile has 'dev' DuckDB output
|
|
949
|
+
# Get project name from dvt_project.yml
|
|
950
|
+
try:
|
|
951
|
+
with open("dvt_project.yml", "r") as f:
|
|
952
|
+
project_config = yaml.safe_load(f)
|
|
953
|
+
project_name = project_config.get("name", Path.cwd().name)
|
|
954
|
+
except (FileNotFoundError, yaml.YAMLError):
|
|
955
|
+
# If we can't read project name, use folder name
|
|
956
|
+
project_name = Path.cwd().name
|
|
957
|
+
|
|
958
|
+
# v0.59.0a24: Always add profile (don't let exceptions be swallowed)
|
|
959
|
+
try:
|
|
960
|
+
self._add_project_profile(project_name)
|
|
961
|
+
except Exception as e:
|
|
962
|
+
click.echo(f" ⚠ Could not add profile: {e}")
|
|
963
|
+
else:
|
|
964
|
+
# When dvt init is run outside of an existing project,
|
|
965
|
+
# create a new project.
|
|
966
|
+
#
|
|
967
|
+
# v0.59.0a5: Two modes:
|
|
968
|
+
# 1. dvt init <project_name> → Create new folder with that name
|
|
969
|
+
# 2. dvt init (no args, in empty folder) → Init in place using folder name
|
|
970
|
+
|
|
971
|
+
user_profile_name = self.args.profile
|
|
972
|
+
|
|
973
|
+
if self.args.project_name:
|
|
974
|
+
# Mode 1: User specified project name → create new folder
|
|
975
|
+
project_name = self.args.project_name
|
|
976
|
+
if not ProjectName.is_valid(project_name):
|
|
977
|
+
click.echo(f"'{project_name}' is not a valid project name.")
|
|
978
|
+
click.echo("Project names must contain only letters, digits, and underscores.")
|
|
979
|
+
return
|
|
980
|
+
|
|
981
|
+
project_path = Path(project_name)
|
|
982
|
+
if project_path.exists():
|
|
983
|
+
fire_event(ProjectNameAlreadyExists(name=project_name))
|
|
984
|
+
return
|
|
985
|
+
|
|
986
|
+
if user_profile_name:
|
|
987
|
+
if not self.check_if_profile_exists(user_profile_name):
|
|
988
|
+
raise DbtRuntimeError(
|
|
989
|
+
msg="Could not find profile named '{}'".format(user_profile_name)
|
|
990
|
+
)
|
|
991
|
+
self.create_new_project(project_name, user_profile_name)
|
|
992
|
+
else:
|
|
993
|
+
self.create_new_project(project_name, project_name)
|
|
994
|
+
|
|
995
|
+
# create_new_project changes CWD into the project directory
|
|
996
|
+
# and already sets up .dvt/ structure, so just add profile
|
|
997
|
+
self._add_project_profile(project_name) # Uses CWD which is now inside project
|
|
998
|
+
self._show_init_success(project_name)
|
|
999
|
+
else:
|
|
1000
|
+
# Mode 2: No project name → init in current folder using folder name
|
|
1001
|
+
project_name = Path.cwd().name
|
|
1002
|
+
|
|
1003
|
+
# Validate folder name as project name
|
|
1004
|
+
if not ProjectName.is_valid(project_name):
|
|
1005
|
+
click.echo(f"Current folder '{project_name}' is not a valid project name.")
|
|
1006
|
+
click.echo("Project names must contain only letters, digits, and underscores.")
|
|
1007
|
+
click.echo("Use: dvt init <valid_project_name>")
|
|
1008
|
+
return
|
|
1009
|
+
|
|
1010
|
+
click.echo(f"Initializing DVT project '{project_name}' in current folder...")
|
|
1011
|
+
click.echo("")
|
|
1012
|
+
|
|
1013
|
+
profile_name = user_profile_name if user_profile_name else project_name
|
|
1014
|
+
if user_profile_name and not self.check_if_profile_exists(user_profile_name):
|
|
1015
|
+
raise DbtRuntimeError(
|
|
1016
|
+
msg="Could not find profile named '{}'".format(user_profile_name)
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
# Init in place (no folder creation)
|
|
1020
|
+
self._create_project_in_place(project_name, profile_name)
|
|
1021
|
+
self._add_project_profile(project_name)
|
|
1022
|
+
self._show_init_success(project_name)
|