dvt-core 1.11.0b4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dvt-core might be problematic. Click here for more details.

Files changed (261) hide show
  1. dvt/__init__.py +7 -0
  2. dvt/_pydantic_shim.py +26 -0
  3. dvt/adapters/__init__.py +16 -0
  4. dvt/adapters/multi_adapter_manager.py +268 -0
  5. dvt/artifacts/__init__.py +0 -0
  6. dvt/artifacts/exceptions/__init__.py +1 -0
  7. dvt/artifacts/exceptions/schemas.py +31 -0
  8. dvt/artifacts/resources/__init__.py +116 -0
  9. dvt/artifacts/resources/base.py +68 -0
  10. dvt/artifacts/resources/types.py +93 -0
  11. dvt/artifacts/resources/v1/analysis.py +10 -0
  12. dvt/artifacts/resources/v1/catalog.py +23 -0
  13. dvt/artifacts/resources/v1/components.py +275 -0
  14. dvt/artifacts/resources/v1/config.py +282 -0
  15. dvt/artifacts/resources/v1/documentation.py +11 -0
  16. dvt/artifacts/resources/v1/exposure.py +52 -0
  17. dvt/artifacts/resources/v1/function.py +53 -0
  18. dvt/artifacts/resources/v1/generic_test.py +32 -0
  19. dvt/artifacts/resources/v1/group.py +22 -0
  20. dvt/artifacts/resources/v1/hook.py +11 -0
  21. dvt/artifacts/resources/v1/macro.py +30 -0
  22. dvt/artifacts/resources/v1/metric.py +173 -0
  23. dvt/artifacts/resources/v1/model.py +146 -0
  24. dvt/artifacts/resources/v1/owner.py +10 -0
  25. dvt/artifacts/resources/v1/saved_query.py +112 -0
  26. dvt/artifacts/resources/v1/seed.py +42 -0
  27. dvt/artifacts/resources/v1/semantic_layer_components.py +72 -0
  28. dvt/artifacts/resources/v1/semantic_model.py +315 -0
  29. dvt/artifacts/resources/v1/singular_test.py +14 -0
  30. dvt/artifacts/resources/v1/snapshot.py +92 -0
  31. dvt/artifacts/resources/v1/source_definition.py +85 -0
  32. dvt/artifacts/resources/v1/sql_operation.py +10 -0
  33. dvt/artifacts/resources/v1/unit_test_definition.py +78 -0
  34. dvt/artifacts/schemas/__init__.py +0 -0
  35. dvt/artifacts/schemas/base.py +191 -0
  36. dvt/artifacts/schemas/batch_results.py +24 -0
  37. dvt/artifacts/schemas/catalog/__init__.py +12 -0
  38. dvt/artifacts/schemas/catalog/v1/__init__.py +0 -0
  39. dvt/artifacts/schemas/catalog/v1/catalog.py +60 -0
  40. dvt/artifacts/schemas/freshness/__init__.py +1 -0
  41. dvt/artifacts/schemas/freshness/v3/__init__.py +0 -0
  42. dvt/artifacts/schemas/freshness/v3/freshness.py +159 -0
  43. dvt/artifacts/schemas/manifest/__init__.py +2 -0
  44. dvt/artifacts/schemas/manifest/v12/__init__.py +0 -0
  45. dvt/artifacts/schemas/manifest/v12/manifest.py +212 -0
  46. dvt/artifacts/schemas/results.py +148 -0
  47. dvt/artifacts/schemas/run/__init__.py +2 -0
  48. dvt/artifacts/schemas/run/v5/__init__.py +0 -0
  49. dvt/artifacts/schemas/run/v5/run.py +184 -0
  50. dvt/artifacts/schemas/upgrades/__init__.py +4 -0
  51. dvt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
  52. dvt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
  53. dvt/artifacts/utils/validation.py +153 -0
  54. dvt/cli/__init__.py +1 -0
  55. dvt/cli/context.py +16 -0
  56. dvt/cli/exceptions.py +56 -0
  57. dvt/cli/flags.py +558 -0
  58. dvt/cli/main.py +971 -0
  59. dvt/cli/option_types.py +121 -0
  60. dvt/cli/options.py +79 -0
  61. dvt/cli/params.py +803 -0
  62. dvt/cli/requires.py +478 -0
  63. dvt/cli/resolvers.py +32 -0
  64. dvt/cli/types.py +40 -0
  65. dvt/clients/__init__.py +0 -0
  66. dvt/clients/checked_load.py +82 -0
  67. dvt/clients/git.py +164 -0
  68. dvt/clients/jinja.py +206 -0
  69. dvt/clients/jinja_static.py +245 -0
  70. dvt/clients/registry.py +192 -0
  71. dvt/clients/yaml_helper.py +68 -0
  72. dvt/compilation.py +833 -0
  73. dvt/compute/__init__.py +26 -0
  74. dvt/compute/base.py +288 -0
  75. dvt/compute/engines/__init__.py +13 -0
  76. dvt/compute/engines/duckdb_engine.py +368 -0
  77. dvt/compute/engines/spark_engine.py +273 -0
  78. dvt/compute/query_analyzer.py +212 -0
  79. dvt/compute/router.py +483 -0
  80. dvt/config/__init__.py +4 -0
  81. dvt/config/catalogs.py +95 -0
  82. dvt/config/compute_config.py +406 -0
  83. dvt/config/profile.py +411 -0
  84. dvt/config/profiles_v2.py +464 -0
  85. dvt/config/project.py +893 -0
  86. dvt/config/renderer.py +232 -0
  87. dvt/config/runtime.py +491 -0
  88. dvt/config/selectors.py +209 -0
  89. dvt/config/utils.py +78 -0
  90. dvt/connectors/.gitignore +6 -0
  91. dvt/connectors/README.md +306 -0
  92. dvt/connectors/catalog.yml +217 -0
  93. dvt/connectors/download_connectors.py +300 -0
  94. dvt/constants.py +29 -0
  95. dvt/context/__init__.py +0 -0
  96. dvt/context/base.py +746 -0
  97. dvt/context/configured.py +136 -0
  98. dvt/context/context_config.py +350 -0
  99. dvt/context/docs.py +82 -0
  100. dvt/context/exceptions_jinja.py +179 -0
  101. dvt/context/macro_resolver.py +195 -0
  102. dvt/context/macros.py +171 -0
  103. dvt/context/manifest.py +73 -0
  104. dvt/context/providers.py +2198 -0
  105. dvt/context/query_header.py +14 -0
  106. dvt/context/secret.py +59 -0
  107. dvt/context/target.py +74 -0
  108. dvt/contracts/__init__.py +0 -0
  109. dvt/contracts/files.py +413 -0
  110. dvt/contracts/graph/__init__.py +0 -0
  111. dvt/contracts/graph/manifest.py +1904 -0
  112. dvt/contracts/graph/metrics.py +98 -0
  113. dvt/contracts/graph/model_config.py +71 -0
  114. dvt/contracts/graph/node_args.py +42 -0
  115. dvt/contracts/graph/nodes.py +1806 -0
  116. dvt/contracts/graph/semantic_manifest.py +233 -0
  117. dvt/contracts/graph/unparsed.py +812 -0
  118. dvt/contracts/project.py +417 -0
  119. dvt/contracts/results.py +53 -0
  120. dvt/contracts/selection.py +23 -0
  121. dvt/contracts/sql.py +86 -0
  122. dvt/contracts/state.py +69 -0
  123. dvt/contracts/util.py +46 -0
  124. dvt/deprecations.py +347 -0
  125. dvt/deps/__init__.py +0 -0
  126. dvt/deps/base.py +153 -0
  127. dvt/deps/git.py +196 -0
  128. dvt/deps/local.py +80 -0
  129. dvt/deps/registry.py +131 -0
  130. dvt/deps/resolver.py +149 -0
  131. dvt/deps/tarball.py +121 -0
  132. dvt/docs/source/_ext/dbt_click.py +118 -0
  133. dvt/docs/source/conf.py +32 -0
  134. dvt/env_vars.py +64 -0
  135. dvt/event_time/event_time.py +40 -0
  136. dvt/event_time/sample_window.py +60 -0
  137. dvt/events/__init__.py +16 -0
  138. dvt/events/base_types.py +37 -0
  139. dvt/events/core_types_pb2.py +2 -0
  140. dvt/events/logging.py +109 -0
  141. dvt/events/types.py +2534 -0
  142. dvt/exceptions.py +1487 -0
  143. dvt/flags.py +89 -0
  144. dvt/graph/__init__.py +11 -0
  145. dvt/graph/cli.py +248 -0
  146. dvt/graph/graph.py +172 -0
  147. dvt/graph/queue.py +213 -0
  148. dvt/graph/selector.py +375 -0
  149. dvt/graph/selector_methods.py +976 -0
  150. dvt/graph/selector_spec.py +223 -0
  151. dvt/graph/thread_pool.py +18 -0
  152. dvt/hooks.py +21 -0
  153. dvt/include/README.md +49 -0
  154. dvt/include/__init__.py +3 -0
  155. dvt/include/global_project.py +4 -0
  156. dvt/include/starter_project/.gitignore +4 -0
  157. dvt/include/starter_project/README.md +15 -0
  158. dvt/include/starter_project/__init__.py +3 -0
  159. dvt/include/starter_project/analyses/.gitkeep +0 -0
  160. dvt/include/starter_project/dvt_project.yml +36 -0
  161. dvt/include/starter_project/macros/.gitkeep +0 -0
  162. dvt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  163. dvt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  164. dvt/include/starter_project/models/example/schema.yml +21 -0
  165. dvt/include/starter_project/seeds/.gitkeep +0 -0
  166. dvt/include/starter_project/snapshots/.gitkeep +0 -0
  167. dvt/include/starter_project/tests/.gitkeep +0 -0
  168. dvt/internal_deprecations.py +27 -0
  169. dvt/jsonschemas/__init__.py +3 -0
  170. dvt/jsonschemas/jsonschemas.py +309 -0
  171. dvt/jsonschemas/project/0.0.110.json +4717 -0
  172. dvt/jsonschemas/project/0.0.85.json +2015 -0
  173. dvt/jsonschemas/resources/0.0.110.json +2636 -0
  174. dvt/jsonschemas/resources/0.0.85.json +2536 -0
  175. dvt/jsonschemas/resources/latest.json +6773 -0
  176. dvt/links.py +4 -0
  177. dvt/materializations/__init__.py +0 -0
  178. dvt/materializations/incremental/__init__.py +0 -0
  179. dvt/materializations/incremental/microbatch.py +235 -0
  180. dvt/mp_context.py +8 -0
  181. dvt/node_types.py +37 -0
  182. dvt/parser/__init__.py +23 -0
  183. dvt/parser/analysis.py +21 -0
  184. dvt/parser/base.py +549 -0
  185. dvt/parser/common.py +267 -0
  186. dvt/parser/docs.py +52 -0
  187. dvt/parser/fixtures.py +51 -0
  188. dvt/parser/functions.py +30 -0
  189. dvt/parser/generic_test.py +100 -0
  190. dvt/parser/generic_test_builders.py +334 -0
  191. dvt/parser/hooks.py +119 -0
  192. dvt/parser/macros.py +137 -0
  193. dvt/parser/manifest.py +2204 -0
  194. dvt/parser/models.py +574 -0
  195. dvt/parser/partial.py +1179 -0
  196. dvt/parser/read_files.py +445 -0
  197. dvt/parser/schema_generic_tests.py +423 -0
  198. dvt/parser/schema_renderer.py +111 -0
  199. dvt/parser/schema_yaml_readers.py +936 -0
  200. dvt/parser/schemas.py +1467 -0
  201. dvt/parser/search.py +149 -0
  202. dvt/parser/seeds.py +28 -0
  203. dvt/parser/singular_test.py +20 -0
  204. dvt/parser/snapshots.py +44 -0
  205. dvt/parser/sources.py +557 -0
  206. dvt/parser/sql.py +63 -0
  207. dvt/parser/unit_tests.py +622 -0
  208. dvt/plugins/__init__.py +20 -0
  209. dvt/plugins/contracts.py +10 -0
  210. dvt/plugins/exceptions.py +2 -0
  211. dvt/plugins/manager.py +164 -0
  212. dvt/plugins/manifest.py +21 -0
  213. dvt/profiler.py +20 -0
  214. dvt/py.typed +1 -0
  215. dvt/runners/__init__.py +2 -0
  216. dvt/runners/exposure_runner.py +7 -0
  217. dvt/runners/no_op_runner.py +46 -0
  218. dvt/runners/saved_query_runner.py +7 -0
  219. dvt/selected_resources.py +8 -0
  220. dvt/task/__init__.py +0 -0
  221. dvt/task/base.py +504 -0
  222. dvt/task/build.py +197 -0
  223. dvt/task/clean.py +57 -0
  224. dvt/task/clone.py +162 -0
  225. dvt/task/compile.py +151 -0
  226. dvt/task/compute.py +366 -0
  227. dvt/task/debug.py +650 -0
  228. dvt/task/deps.py +280 -0
  229. dvt/task/docs/__init__.py +3 -0
  230. dvt/task/docs/generate.py +408 -0
  231. dvt/task/docs/index.html +250 -0
  232. dvt/task/docs/serve.py +28 -0
  233. dvt/task/freshness.py +323 -0
  234. dvt/task/function.py +122 -0
  235. dvt/task/group_lookup.py +46 -0
  236. dvt/task/init.py +374 -0
  237. dvt/task/list.py +237 -0
  238. dvt/task/printer.py +176 -0
  239. dvt/task/profiles.py +256 -0
  240. dvt/task/retry.py +175 -0
  241. dvt/task/run.py +1146 -0
  242. dvt/task/run_operation.py +142 -0
  243. dvt/task/runnable.py +802 -0
  244. dvt/task/seed.py +104 -0
  245. dvt/task/show.py +150 -0
  246. dvt/task/snapshot.py +57 -0
  247. dvt/task/sql.py +111 -0
  248. dvt/task/test.py +464 -0
  249. dvt/tests/fixtures/__init__.py +1 -0
  250. dvt/tests/fixtures/project.py +620 -0
  251. dvt/tests/util.py +651 -0
  252. dvt/tracking.py +529 -0
  253. dvt/utils/__init__.py +3 -0
  254. dvt/utils/artifact_upload.py +151 -0
  255. dvt/utils/utils.py +408 -0
  256. dvt/version.py +249 -0
  257. dvt_core-1.11.0b4.dist-info/METADATA +252 -0
  258. dvt_core-1.11.0b4.dist-info/RECORD +261 -0
  259. dvt_core-1.11.0b4.dist-info/WHEEL +5 -0
  260. dvt_core-1.11.0b4.dist-info/entry_points.txt +2 -0
  261. dvt_core-1.11.0b4.dist-info/top_level.txt +1 -0
dvt/task/init.py ADDED
@@ -0,0 +1,374 @@
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 dvt.config
10
+ import yaml
11
+ from dvt.config.profile import read_profile
12
+ from dvt.constants import DBT_PROJECT_FILE_NAME, DVT_PROJECT_FILE_NAME
13
+ from dvt.contracts.util import Identifier as ProjectName
14
+ from dvt.events.types import (
15
+ ConfigFolderDirectory,
16
+ InvalidProfileTemplateYAML,
17
+ NoSampleProfileFound,
18
+ ProfileWrittenWithProjectTemplateYAML,
19
+ ProfileWrittenWithSample,
20
+ ProfileWrittenWithTargetTemplateYAML,
21
+ ProjectCreated,
22
+ ProjectNameAlreadyExists,
23
+ SettingUpProfile,
24
+ StarterProjectPath,
25
+ )
26
+ from dvt.flags import get_flags
27
+ from dvt.task.base import BaseTask, move_to_nearest_project_dir
28
+ from dvt.version import _get_adapter_plugin_names
29
+
30
+ import dbt_common.clients.system
31
+ from dbt.adapters.factory import get_include_paths, load_plugin
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
+ # This file is not needed for the starter project but exists for finding the resource path
39
+ IGNORE_FILES = ["__init__.py", "__pycache__"]
40
+
41
+
42
+ # https://click.palletsprojects.com/en/8.0.x/api/#types
43
+ # click v7.0 has UNPROCESSED, STRING, INT, FLOAT, BOOL, and UUID available.
44
+ click_type_mapping = {
45
+ "string": click.STRING,
46
+ "int": click.INT,
47
+ "float": click.FLOAT,
48
+ "bool": click.BOOL,
49
+ None: None,
50
+ }
51
+
52
+
53
+ class InitTask(BaseTask):
54
+ def copy_starter_repo(self, project_name: str) -> None:
55
+ # Lazy import to avoid ModuleNotFoundError
56
+ from dvt.include.starter_project import (
57
+ PACKAGE_PATH as starter_project_directory,
58
+ )
59
+
60
+ fire_event(StarterProjectPath(dir=starter_project_directory))
61
+ shutil.copytree(
62
+ starter_project_directory, project_name, ignore=shutil.ignore_patterns(*IGNORE_FILES)
63
+ )
64
+
65
+ def create_profiles_dir(self, profiles_dir: str) -> bool:
66
+ """Create the user's profiles directory if it doesn't already exist."""
67
+ profiles_path = Path(profiles_dir)
68
+ if not profiles_path.exists():
69
+ fire_event(ConfigFolderDirectory(dir=str(profiles_dir)))
70
+ dbt_common.clients.system.make_directory(profiles_dir)
71
+ return True
72
+ return False
73
+
74
+ def create_profile_from_sample(self, adapter: str, profile_name: str):
75
+ """Create a profile entry using the adapter's sample_profiles.yml
76
+
77
+ Renames the profile in sample_profiles.yml to match that of the project."""
78
+ # Line below raises an exception if the specified adapter is not found
79
+ load_plugin(adapter)
80
+ adapter_path = get_include_paths(adapter)[0]
81
+ sample_profiles_path = adapter_path / "sample_profiles.yml"
82
+
83
+ if not sample_profiles_path.exists():
84
+ fire_event(NoSampleProfileFound(adapter=adapter))
85
+ else:
86
+ with open(sample_profiles_path, "r") as f:
87
+ sample_profile = f.read()
88
+ sample_profile_name = list(yaml.safe_load(sample_profile).keys())[0]
89
+ # Use a regex to replace the name of the sample_profile with
90
+ # that of the project without losing any comments from the sample
91
+ sample_profile = re.sub(f"^{sample_profile_name}:", f"{profile_name}:", sample_profile)
92
+ profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
93
+ if profiles_filepath.exists():
94
+ with open(profiles_filepath, "a") as f:
95
+ f.write("\n" + sample_profile)
96
+ else:
97
+ with open(profiles_filepath, "w") as f:
98
+ f.write(sample_profile)
99
+ fire_event(
100
+ ProfileWrittenWithSample(name=profile_name, path=str(profiles_filepath))
101
+ )
102
+
103
+ def generate_target_from_input(self, profile_template: dict, target: dict = {}) -> dict:
104
+ """Generate a target configuration from profile_template and user input."""
105
+ profile_template_local = copy.deepcopy(profile_template)
106
+ for key, value in profile_template_local.items():
107
+ if key.startswith("_choose"):
108
+ choice_type = key[8:].replace("_", " ")
109
+ option_list = list(value.keys())
110
+ prompt_msg = (
111
+ "\n".join([f"[{n+1}] {v}" for n, v in enumerate(option_list)])
112
+ + f"\nDesired {choice_type} option (enter a number)"
113
+ )
114
+ numeric_choice = click.prompt(prompt_msg, type=click.INT)
115
+ choice = option_list[numeric_choice - 1]
116
+ # Complete the chosen option's values in a recursive call
117
+ target = self.generate_target_from_input(
118
+ profile_template_local[key][choice], target
119
+ )
120
+ else:
121
+ if key.startswith("_fixed"):
122
+ # _fixed prefixed keys are not presented to the user
123
+ target[key[7:]] = value
124
+ else:
125
+ hide_input = value.get("hide_input", False)
126
+ default = value.get("default", None)
127
+ hint = value.get("hint", None)
128
+ type = click_type_mapping[value.get("type", None)]
129
+ text = key + (f" ({hint})" if hint else "")
130
+ target[key] = click.prompt(
131
+ text, default=default, hide_input=hide_input, type=type
132
+ )
133
+ return target
134
+
135
+ def get_profile_name_from_current_project(self) -> str:
136
+ """Reads dvt_project.yml (or dbt_project.yml for backward compatibility)
137
+ in the current directory to retrieve the profile name.
138
+ """
139
+ from dbt_common.clients.system import path_exists
140
+
141
+ # Try new filename first
142
+ if path_exists(DVT_PROJECT_FILE_NAME):
143
+ project_file = DVT_PROJECT_FILE_NAME
144
+ else:
145
+ project_file = DBT_PROJECT_FILE_NAME
146
+
147
+ with open(project_file) as f:
148
+ project_dict = yaml.safe_load(f)
149
+ return project_dict["profile"]
150
+
151
+ def write_profile(self, profile: dict, profile_name: str):
152
+ """Given a profile, write it to the current project's profiles.yml.
153
+ This will overwrite any profile with a matching name."""
154
+ # Create the profile directory if it doesn't exist
155
+ profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
156
+
157
+ profiles = {profile_name: profile}
158
+
159
+ if profiles_filepath.exists():
160
+ with open(profiles_filepath, "r") as f:
161
+ profiles = yaml.safe_load(f) or {}
162
+ profiles[profile_name] = profile
163
+
164
+ # Write the profiles dictionary to a brand-new or pre-existing file
165
+ with open(profiles_filepath, "w") as f:
166
+ yaml.dump(profiles, f)
167
+
168
+ def create_profile_from_profile_template(self, profile_template: dict, profile_name: str):
169
+ """Create and write a profile using the supplied profile_template."""
170
+ initial_target = profile_template.get("fixed", {})
171
+ prompts = profile_template.get("prompts", {})
172
+ target = self.generate_target_from_input(prompts, initial_target)
173
+ target_name = target.pop("target", "dev")
174
+ profile = {"outputs": {target_name: target}, "target": target_name}
175
+ self.write_profile(profile, profile_name)
176
+
177
+ def create_profile_from_target(self, adapter: str, profile_name: str):
178
+ """Create a profile without defaults using target's profile_template.yml if available, or
179
+ sample_profiles.yml as a fallback."""
180
+ # Line below raises an exception if the specified adapter is not found
181
+ load_plugin(adapter)
182
+ adapter_path = get_include_paths(adapter)[0]
183
+ profile_template_path = adapter_path / "profile_template.yml"
184
+
185
+ if profile_template_path.exists():
186
+ with open(profile_template_path) as f:
187
+ profile_template = yaml.safe_load(f)
188
+ self.create_profile_from_profile_template(profile_template, profile_name)
189
+ profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
190
+ fire_event(
191
+ ProfileWrittenWithTargetTemplateYAML(
192
+ name=profile_name, path=str(profiles_filepath)
193
+ )
194
+ )
195
+ else:
196
+ # For adapters without a profile_template.yml defined, fallback on
197
+ # sample_profiles.yml
198
+ self.create_profile_from_sample(adapter, profile_name)
199
+
200
+ def check_if_profile_exists(self, profile_name: str) -> bool:
201
+ """
202
+ Validate that the specified profile exists. Can't use the regular profile validation
203
+ routine because it assumes the project file exists
204
+ """
205
+ profiles_dir = get_flags().PROFILES_DIR
206
+ raw_profiles = read_profile(profiles_dir)
207
+ return profile_name in raw_profiles
208
+
209
+ def check_if_can_write_profile(self, profile_name: Optional[str] = None) -> bool:
210
+ """Using either a provided profile name or that specified in project file,
211
+ check if the profile already exists in profiles.yml, and if so ask the
212
+ user whether to proceed and overwrite it."""
213
+ profiles_file = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
214
+ if not profiles_file.exists():
215
+ return True
216
+ profile_name = profile_name or self.get_profile_name_from_current_project()
217
+ with open(profiles_file, "r") as f:
218
+ profiles = yaml.safe_load(f) or {}
219
+ if profile_name in profiles.keys():
220
+ response = click.confirm(
221
+ f"The profile {profile_name} already exists in "
222
+ f"{profiles_file}. Continue and overwrite it?"
223
+ )
224
+ return response
225
+ else:
226
+ return True
227
+
228
+ def create_profile_using_project_profile_template(self, profile_name):
229
+ """Create a profile using the project's profile_template.yml"""
230
+ with open("profile_template.yml") as f:
231
+ profile_template = yaml.safe_load(f)
232
+ self.create_profile_from_profile_template(profile_template, profile_name)
233
+ profiles_filepath = Path(get_flags().PROFILES_DIR) / Path("profiles.yml")
234
+ fire_event(
235
+ ProfileWrittenWithProjectTemplateYAML(name=profile_name, path=str(profiles_filepath))
236
+ )
237
+
238
+ def ask_for_adapter_choice(self) -> str:
239
+ """Ask the user which adapter (database) they'd like to use."""
240
+ available_adapters = list(_get_adapter_plugin_names())
241
+
242
+ if not available_adapters:
243
+ raise dbt.exceptions.NoAdaptersAvailableError()
244
+
245
+ prompt_msg = (
246
+ "Which database would you like to use?\n"
247
+ + "\n".join([f"[{n+1}] {v}" for n, v in enumerate(available_adapters)])
248
+ + "\n\n(Don't see the one you want? https://docs.getdbt.com/docs/available-adapters)"
249
+ + "\n\nEnter a number"
250
+ )
251
+ numeric_choice = click.prompt(prompt_msg, type=click.INT)
252
+ return available_adapters[numeric_choice - 1]
253
+
254
+ def setup_profile(self, profile_name: str) -> None:
255
+ """Set up a new profile for a project"""
256
+ fire_event(SettingUpProfile())
257
+ if not self.check_if_can_write_profile(profile_name=profile_name):
258
+ return
259
+ # If a profile_template.yml exists in the project root, that effectively
260
+ # overrides the profile_template.yml for the given target.
261
+ profile_template_path = Path("profile_template.yml")
262
+ if profile_template_path.exists():
263
+ try:
264
+ # This relies on a valid profile_template.yml from the user,
265
+ # so use a try: except to fall back to the default on failure
266
+ self.create_profile_using_project_profile_template(profile_name)
267
+ return
268
+ except Exception:
269
+ fire_event(InvalidProfileTemplateYAML())
270
+ adapter = self.ask_for_adapter_choice()
271
+ self.create_profile_from_target(adapter, profile_name=profile_name)
272
+
273
+ def get_valid_project_name(self) -> str:
274
+ """Returns a valid project name, either from CLI arg or user prompt."""
275
+
276
+ # Lazy import to avoid ModuleNotFoundError
277
+ from dvt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
278
+
279
+ name = self.args.project_name
280
+ internal_package_names = {GLOBAL_PROJECT_NAME}
281
+ available_adapters = list(_get_adapter_plugin_names())
282
+ for adapter_name in available_adapters:
283
+ internal_package_names.update(f"dbt_{adapter_name}")
284
+ while not ProjectName.is_valid(name) or name in internal_package_names:
285
+ if name:
286
+ click.echo(name + " is not a valid project name.")
287
+ name = click.prompt("Enter a name for your project (letters, digits, underscore)")
288
+
289
+ return name
290
+
291
+ def create_new_project(self, project_name: str, profile_name: str):
292
+ from dbt_common.clients.system import path_exists
293
+
294
+ self.copy_starter_repo(project_name)
295
+ os.chdir(project_name)
296
+
297
+ # Rename dbt_project.yml to dvt_project.yml if it exists (from starter project)
298
+ if path_exists(DBT_PROJECT_FILE_NAME):
299
+ with open(DBT_PROJECT_FILE_NAME, "r") as f:
300
+ content = f"{f.read()}".format(
301
+ project_name=project_name, profile_name=profile_name
302
+ )
303
+ # Write to new filename
304
+ with open(DVT_PROJECT_FILE_NAME, "w") as f:
305
+ f.write(content)
306
+ # Remove old filename
307
+ os.remove(DBT_PROJECT_FILE_NAME)
308
+ else:
309
+ # Fallback if starter project already has dvt_project.yml
310
+ with open(DVT_PROJECT_FILE_NAME, "r") as f:
311
+ content = f"{f.read()}".format(
312
+ project_name=project_name, profile_name=profile_name
313
+ )
314
+ with open(DVT_PROJECT_FILE_NAME, "w") as f:
315
+ f.write(content)
316
+
317
+ fire_event(
318
+ ProjectCreated(
319
+ project_name=project_name,
320
+ docs_url=DOCS_URL,
321
+ slack_url=SLACK_URL,
322
+ )
323
+ )
324
+
325
+ def run(self):
326
+ """Entry point for the init task."""
327
+ profiles_dir = get_flags().PROFILES_DIR
328
+ self.create_profiles_dir(profiles_dir)
329
+
330
+ try:
331
+ move_to_nearest_project_dir(self.args.project_dir)
332
+ in_project = True
333
+ except dbt_common.exceptions.DbtRuntimeError:
334
+ in_project = False
335
+
336
+ if in_project:
337
+ # If --profile was specified, it means use an existing profile, which is not
338
+ # applicable to this case
339
+ if self.args.profile:
340
+ raise DbtRuntimeError(
341
+ msg="Can not init existing project with specified profile, edit project file instead"
342
+ )
343
+
344
+ # When dbt init is run inside an existing project,
345
+ # just setup the user's profile.
346
+ if not self.args.skip_profile_setup:
347
+ profile_name = self.get_profile_name_from_current_project()
348
+ self.setup_profile(profile_name)
349
+ else:
350
+ # When dbt init is run outside of an existing project,
351
+ # create a new project and set up the user's profile.
352
+ project_name = self.get_valid_project_name()
353
+ project_path = Path(project_name)
354
+ if project_path.exists():
355
+ fire_event(ProjectNameAlreadyExists(name=project_name))
356
+ return
357
+
358
+ # If the user specified an existing profile to use, use it instead of generating a new one
359
+ user_profile_name = self.args.profile
360
+ if user_profile_name:
361
+ if not self.check_if_profile_exists(user_profile_name):
362
+ raise DbtRuntimeError(
363
+ msg="Could not find profile named '{}'".format(user_profile_name)
364
+ )
365
+ self.create_new_project(project_name, user_profile_name)
366
+ else:
367
+ profile_name = project_name
368
+ # Create the profile after creating the project to avoid leaving a random profile
369
+ # if the former fails.
370
+ self.create_new_project(project_name, profile_name)
371
+
372
+ # Ask for adapter only if skip_profile_setup flag is not provided
373
+ if not self.args.skip_profile_setup:
374
+ self.setup_profile(profile_name)
dvt/task/list.py ADDED
@@ -0,0 +1,237 @@
1
+ import json
2
+ from typing import Iterator, List
3
+
4
+ from dvt.cli.flags import Flags
5
+ from dvt.config.runtime import RuntimeConfig
6
+ from dvt.contracts.graph.manifest import Manifest
7
+ from dvt.contracts.graph.nodes import (
8
+ Exposure,
9
+ Metric,
10
+ SavedQuery,
11
+ SemanticModel,
12
+ SourceDefinition,
13
+ UnitTestDefinition,
14
+ )
15
+ from dvt.events.types import NoNodesSelected
16
+ from dvt.graph import ResourceTypeSelector
17
+ from dvt.node_types import NodeType
18
+ from dvt.task.base import resource_types_from_args
19
+ from dvt.task.runnable import GraphRunnableTask
20
+ from dvt.utils import JSONEncoder
21
+
22
+ from dbt_common.events.contextvars import task_contextvars
23
+ from dbt_common.events.functions import fire_event, warn_or_error
24
+ from dbt_common.events.types import PrintEvent
25
+ from dbt_common.exceptions import DbtInternalError, DbtRuntimeError
26
+
27
+
28
+ class ListTask(GraphRunnableTask):
29
+ DEFAULT_RESOURCE_VALUES = frozenset(
30
+ (
31
+ NodeType.Model,
32
+ NodeType.Snapshot,
33
+ NodeType.Seed,
34
+ NodeType.Test,
35
+ NodeType.Source,
36
+ NodeType.Exposure,
37
+ NodeType.Metric,
38
+ NodeType.SavedQuery,
39
+ NodeType.SemanticModel,
40
+ NodeType.Unit,
41
+ NodeType.Function,
42
+ )
43
+ )
44
+ ALL_RESOURCE_VALUES = DEFAULT_RESOURCE_VALUES | frozenset((NodeType.Analysis,))
45
+ ALLOWED_KEYS = frozenset(
46
+ (
47
+ "alias",
48
+ "name",
49
+ "package_name",
50
+ "depends_on",
51
+ "tags",
52
+ "config",
53
+ "resource_type",
54
+ "source_name",
55
+ "original_file_path",
56
+ "unique_id",
57
+ )
58
+ )
59
+
60
+ def __init__(self, args: Flags, config: RuntimeConfig, manifest: Manifest) -> None:
61
+ super().__init__(args, config, manifest)
62
+ if self.args.models:
63
+ if self.args.select:
64
+ raise DbtRuntimeError('"models" and "select" are mutually exclusive arguments')
65
+ if self.args.resource_types:
66
+ raise DbtRuntimeError(
67
+ '"models" and "resource_type" are mutually exclusive ' "arguments"
68
+ )
69
+
70
+ def _iterate_selected_nodes(self):
71
+ selector = self.get_node_selector()
72
+ spec = self.get_selection_spec()
73
+ unique_ids = sorted(selector.get_selected(spec))
74
+ if not unique_ids:
75
+ warn_or_error(NoNodesSelected())
76
+ return
77
+ if self.manifest is None:
78
+ raise DbtInternalError("manifest is None in _iterate_selected_nodes")
79
+ for unique_id in unique_ids:
80
+ if unique_id in self.manifest.nodes:
81
+ yield self.manifest.nodes[unique_id]
82
+ elif unique_id in self.manifest.sources:
83
+ yield self.manifest.sources[unique_id]
84
+ elif unique_id in self.manifest.exposures:
85
+ yield self.manifest.exposures[unique_id]
86
+ elif unique_id in self.manifest.metrics:
87
+ yield self.manifest.metrics[unique_id]
88
+ elif unique_id in self.manifest.semantic_models:
89
+ yield self.manifest.semantic_models[unique_id]
90
+ elif unique_id in self.manifest.unit_tests:
91
+ yield self.manifest.unit_tests[unique_id]
92
+ elif unique_id in self.manifest.saved_queries:
93
+ yield self.manifest.saved_queries[unique_id]
94
+ elif unique_id in self.manifest.functions:
95
+ yield self.manifest.functions[unique_id]
96
+ else:
97
+ raise DbtRuntimeError(
98
+ f'Got an unexpected result from node selection: "{unique_id}"'
99
+ f"Listing this node type is not yet supported!"
100
+ )
101
+
102
+ def generate_selectors(self):
103
+ for node in self._iterate_selected_nodes():
104
+ if node.resource_type == NodeType.Source:
105
+ assert isinstance(node, SourceDefinition)
106
+ # sources are searched for by pkg.source_name.table_name
107
+ source_selector = ".".join([node.package_name, node.source_name, node.name])
108
+ yield f"source:{source_selector}"
109
+ elif node.resource_type == NodeType.Exposure:
110
+ assert isinstance(node, Exposure)
111
+ # exposures are searched for by pkg.exposure_name
112
+ exposure_selector = ".".join([node.package_name, node.name])
113
+ yield f"exposure:{exposure_selector}"
114
+ elif node.resource_type == NodeType.Metric:
115
+ assert isinstance(node, Metric)
116
+ # metrics are searched for by pkg.metric_name
117
+ metric_selector = ".".join([node.package_name, node.name])
118
+ yield f"metric:{metric_selector}"
119
+ elif node.resource_type == NodeType.SavedQuery:
120
+ assert isinstance(node, SavedQuery)
121
+ saved_query_selector = ".".join([node.package_name, node.name])
122
+ yield f"saved_query:{saved_query_selector}"
123
+ elif node.resource_type == NodeType.SemanticModel:
124
+ assert isinstance(node, SemanticModel)
125
+ semantic_model_selector = ".".join([node.package_name, node.name])
126
+ yield f"semantic_model:{semantic_model_selector}"
127
+ elif node.resource_type == NodeType.Unit:
128
+ assert isinstance(node, UnitTestDefinition)
129
+ unit_test_selector = ".".join([node.package_name, node.versioned_name])
130
+ yield f"unit_test:{unit_test_selector}"
131
+ else:
132
+ # everything else is from `fqn`
133
+ yield ".".join(node.fqn)
134
+
135
+ def generate_names(self):
136
+ for node in self._iterate_selected_nodes():
137
+ yield node.search_name
138
+
139
+ def _get_nested_value(self, data, key_path):
140
+ """Get nested value using dot notation (e.g., 'config.materialized')"""
141
+ keys = key_path.split(".")
142
+ current = data
143
+ for key in keys:
144
+ if isinstance(current, dict) and key in current:
145
+ current = current[key]
146
+ else:
147
+ return None
148
+ return current
149
+
150
+ def generate_json(self):
151
+ for node in self._iterate_selected_nodes():
152
+ node_dict = node.to_dict(omit_none=False)
153
+
154
+ if self.args.output_keys:
155
+ # Handle both nested and regular keys
156
+ result = {}
157
+ for key in self.args.output_keys:
158
+ if "." in key:
159
+ # Handle nested key (e.g., 'config.materialized')
160
+ value = self._get_nested_value(node_dict, key)
161
+ if value is not None:
162
+ result[key] = value
163
+ else:
164
+ # Handle regular key
165
+ if key in node_dict:
166
+ result[key] = node_dict[key]
167
+ else:
168
+ # Use default allowed keys
169
+ result = {k: v for k, v in node_dict.items() if k in self.ALLOWED_KEYS}
170
+
171
+ yield json.dumps(result, cls=JSONEncoder)
172
+
173
+ def generate_paths(self) -> Iterator[str]:
174
+ for node in self._iterate_selected_nodes():
175
+ yield node.original_file_path
176
+
177
+ def run(self):
178
+ # We set up a context manager here with "task_contextvars" because we
179
+ # we need the project_root in compile_manifest.
180
+ with task_contextvars(project_root=self.config.project_root):
181
+ self.compile_manifest()
182
+ output = self.args.output
183
+ if output == "selector":
184
+ generator = self.generate_selectors
185
+ elif output == "name":
186
+ generator = self.generate_names
187
+ elif output == "json":
188
+ generator = self.generate_json
189
+ elif output == "path":
190
+ generator = self.generate_paths
191
+ else:
192
+ raise DbtInternalError("Invalid output {}".format(output))
193
+
194
+ return self.output_results(generator())
195
+
196
+ def output_results(self, results):
197
+ """Log, or output a plain, newline-delimited, and ready-to-pipe list of nodes found."""
198
+ for result in results:
199
+ self.node_results.append(result)
200
+ # No formatting, still get to stdout when --quiet is used
201
+ fire_event(PrintEvent(msg=result))
202
+ return self.node_results
203
+
204
+ @property
205
+ def resource_types(self) -> List[NodeType]:
206
+ if self.args.models:
207
+ return [NodeType.Model]
208
+
209
+ resource_types = resource_types_from_args(
210
+ self.args, set(self.ALL_RESOURCE_VALUES), set(self.DEFAULT_RESOURCE_VALUES)
211
+ )
212
+
213
+ return list(resource_types)
214
+
215
+ @property
216
+ def selection_arg(self):
217
+ # for backwards compatibility, list accepts both --models and --select,
218
+ # with slightly different behavior: --models implies --resource-type model
219
+ if self.args.models:
220
+ return self.args.models
221
+ else:
222
+ return self.args.select
223
+
224
+ def get_node_selector(self) -> ResourceTypeSelector:
225
+ if self.manifest is None or self.graph is None:
226
+ raise DbtInternalError("manifest and graph must be set to get perform node selection")
227
+ return ResourceTypeSelector(
228
+ graph=self.graph,
229
+ manifest=self.manifest,
230
+ previous_state=self.previous_state,
231
+ resource_types=self.resource_types,
232
+ include_empty_nodes=True,
233
+ )
234
+
235
+ def interpret_results(self, results):
236
+ # list command should always return 0 as exit code
237
+ return True