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/config/runtime.py ADDED
@@ -0,0 +1,491 @@
1
+ import itertools
2
+ import os
3
+ from copy import deepcopy
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import (
7
+ Any,
8
+ Dict,
9
+ Iterable,
10
+ Iterator,
11
+ Mapping,
12
+ MutableSet,
13
+ Optional,
14
+ Tuple,
15
+ Type,
16
+ )
17
+
18
+ from dvt import tracking
19
+ from dvt.artifacts.resources import Quoting
20
+ from dvt.config.project import load_raw_project
21
+ from dvt.contracts.graph.manifest import ManifestMetadata
22
+ from dvt.contracts.project import Configuration
23
+ from dvt.events.types import UnusedResourceConfigPath
24
+ from dvt.exceptions import (
25
+ ConfigContractBrokenError,
26
+ DbtProjectError,
27
+ DbtRuntimeError,
28
+ NonUniquePackageNameError,
29
+ UninstalledPackagesFoundError,
30
+ )
31
+ from dvt.flags import get_flags
32
+
33
+ from dbt.adapters.contracts.connection import (
34
+ AdapterRequiredConfig,
35
+ Credentials,
36
+ HasCredentials,
37
+ )
38
+ from dbt.adapters.contracts.relation import ComponentName
39
+ from dbt.adapters.factory import get_include_paths, get_relation_class_by_name
40
+ from dbt_common.dataclass_schema import ValidationError
41
+ from dbt_common.events.functions import warn_or_error
42
+ from dbt_common.helper_types import DictDefaultEmptyStr, FQNPath, PathSet
43
+
44
+ from .profile import Profile
45
+ from .project import Project
46
+ from .renderer import DbtProjectYamlRenderer, ProfileRenderer
47
+
48
+
49
+ # Called by RuntimeConfig.collect_parts class method
50
+ def load_project(
51
+ project_root: str,
52
+ version_check: bool,
53
+ profile: HasCredentials,
54
+ cli_vars: Optional[Dict[str, Any]] = None,
55
+ validate: bool = False,
56
+ ) -> Project:
57
+ # get the project with all of the provided information
58
+ project_renderer = DbtProjectYamlRenderer(profile, cli_vars)
59
+ project = Project.from_project_root(
60
+ project_root, project_renderer, verify_version=version_check, validate=validate
61
+ )
62
+
63
+ # Save env_vars encountered in rendering for partial parsing
64
+ project.project_env_vars = project_renderer.ctx_obj.env_vars
65
+ return project
66
+
67
+
68
+ def load_profile(
69
+ project_root: str,
70
+ cli_vars: Dict[str, Any],
71
+ profile_name_override: Optional[str] = None,
72
+ target_override: Optional[str] = None,
73
+ threads_override: Optional[int] = None,
74
+ ) -> Profile:
75
+ raw_project = load_raw_project(project_root)
76
+ raw_profile_name = raw_project.get("profile")
77
+ profile_renderer = ProfileRenderer(cli_vars)
78
+ profile_name = profile_renderer.render_value(raw_profile_name)
79
+ profile = Profile.render(
80
+ profile_renderer, profile_name, profile_name_override, target_override, threads_override
81
+ )
82
+ # Save env_vars encountered in rendering for partial parsing
83
+ profile.profile_env_vars = profile_renderer.ctx_obj.env_vars
84
+ return profile
85
+
86
+
87
+ def _project_quoting_dict(proj: Project, profile: Profile) -> Dict[ComponentName, bool]:
88
+ src: Dict[str, Any] = profile.credentials.translate_aliases(proj.quoting)
89
+ result: Dict[ComponentName, bool] = {}
90
+ for key in ComponentName:
91
+ if key in src:
92
+ value = src[key]
93
+ if isinstance(value, bool):
94
+ result[key] = value
95
+ return result
96
+
97
+
98
+ @dataclass
99
+ class RuntimeConfig(Project, Profile, AdapterRequiredConfig):
100
+ args: Any
101
+ profile_name: str
102
+ cli_vars: Dict[str, Any]
103
+ dependencies: Optional[Mapping[str, "RuntimeConfig"]] = None
104
+
105
+ def __post_init__(self):
106
+ self.validate()
107
+
108
+ @classmethod
109
+ def get_profile(
110
+ cls,
111
+ project_root: str,
112
+ cli_vars: Dict[str, Any],
113
+ args: Any,
114
+ ) -> Profile:
115
+ return load_profile(
116
+ project_root,
117
+ cli_vars,
118
+ args.profile,
119
+ args.target,
120
+ args.threads,
121
+ )
122
+
123
+ # Called by 'new_project' and 'from_args'
124
+ @classmethod
125
+ def from_parts(
126
+ cls,
127
+ project: Project,
128
+ profile: Profile,
129
+ args: Any,
130
+ dependencies: Optional[Mapping[str, "RuntimeConfig"]] = None,
131
+ ) -> "RuntimeConfig":
132
+ """Instantiate a RuntimeConfig from its components.
133
+
134
+ :param profile: A parsed dbt Profile.
135
+ :param project: A parsed dbt Project.
136
+ :param args: The parsed command-line arguments.
137
+ :returns RuntimeConfig: The new configuration.
138
+ """
139
+ quoting: Dict[str, Any] = (
140
+ get_relation_class_by_name(profile.credentials.type)
141
+ .get_default_quote_policy()
142
+ .replace_dict(_project_quoting_dict(project, profile))
143
+ ).to_dict(omit_none=True)
144
+
145
+ cli_vars: Dict[str, Any] = getattr(args, "vars", {})
146
+ log_cache_events: bool = getattr(args, "log_cache_events", profile.log_cache_events)
147
+
148
+ return cls(
149
+ project_name=project.project_name,
150
+ version=project.version,
151
+ project_root=project.project_root,
152
+ model_paths=project.model_paths,
153
+ macro_paths=project.macro_paths,
154
+ seed_paths=project.seed_paths,
155
+ test_paths=project.test_paths,
156
+ analysis_paths=project.analysis_paths,
157
+ docs_paths=project.docs_paths,
158
+ asset_paths=project.asset_paths,
159
+ function_paths=project.function_paths,
160
+ target_path=project.target_path,
161
+ snapshot_paths=project.snapshot_paths,
162
+ clean_targets=project.clean_targets,
163
+ log_path=project.log_path,
164
+ packages_install_path=project.packages_install_path,
165
+ packages_specified_path=project.packages_specified_path,
166
+ quoting=quoting,
167
+ models=project.models,
168
+ on_run_start=project.on_run_start,
169
+ on_run_end=project.on_run_end,
170
+ dispatch=project.dispatch,
171
+ seeds=project.seeds,
172
+ snapshots=project.snapshots,
173
+ dbt_version=project.dbt_version,
174
+ packages=project.packages,
175
+ manifest_selectors=project.manifest_selectors,
176
+ selectors=project.selectors,
177
+ query_comment=project.query_comment,
178
+ sources=project.sources,
179
+ data_tests=project.data_tests,
180
+ unit_tests=project.unit_tests,
181
+ metrics=project.metrics,
182
+ semantic_models=project.semantic_models,
183
+ saved_queries=project.saved_queries,
184
+ exposures=project.exposures,
185
+ functions=project.functions,
186
+ vars=project.vars,
187
+ config_version=project.config_version,
188
+ unrendered=project.unrendered,
189
+ project_env_vars=project.project_env_vars,
190
+ restrict_access=project.restrict_access,
191
+ profile_env_vars=profile.profile_env_vars,
192
+ profile_name=profile.profile_name,
193
+ target_name=profile.target_name,
194
+ threads=profile.threads,
195
+ credentials=profile.credentials,
196
+ args=args,
197
+ cli_vars=cli_vars,
198
+ log_cache_events=log_cache_events,
199
+ dependencies=dependencies,
200
+ dbt_cloud=project.dbt_cloud,
201
+ flags=project.flags,
202
+ )
203
+
204
+ # Called by 'load_projects' in this class
205
+ def new_project(self, project_root: str) -> "RuntimeConfig":
206
+ """Given a new project root, read in its project dictionary, supply the
207
+ existing project's profile info, and create a new project file.
208
+
209
+ :param project_root: A filepath to a dbt project.
210
+ :raises DbtProfileError: If the profile is invalid.
211
+ :raises DbtProjectError: If project is missing or invalid.
212
+ :returns: The new configuration.
213
+ """
214
+ # copy profile
215
+ profile = Profile(**self.to_profile_info())
216
+ profile.validate()
217
+
218
+ # load the new project and its packages. Don't pass cli variables.
219
+ renderer = DbtProjectYamlRenderer(profile)
220
+ project = Project.from_project_root(
221
+ project_root,
222
+ renderer,
223
+ verify_version=bool(getattr(self.args, "VERSION_CHECK", True)),
224
+ )
225
+
226
+ runtime_config = self.from_parts(
227
+ project=project,
228
+ profile=profile,
229
+ args=deepcopy(self.args),
230
+ )
231
+ # force our quoting back onto the new project.
232
+ runtime_config.quoting = deepcopy(self.quoting)
233
+ return runtime_config
234
+
235
+ def serialize(self) -> Dict[str, Any]:
236
+ """Serialize the full configuration to a single dictionary. For any
237
+ instance that has passed validate() (which happens in __init__), it
238
+ matches the Configuration contract.
239
+
240
+ Note that args are not serialized.
241
+
242
+ :returns dict: The serialized configuration.
243
+ """
244
+ result = self.to_project_config(with_packages=True)
245
+ result.update(self.to_profile_info(serialize_credentials=True))
246
+ result["cli_vars"] = deepcopy(self.cli_vars)
247
+ return result
248
+
249
+ def validate(self):
250
+ """Validate the configuration against its contract.
251
+
252
+ :raises DbtProjectError: If the configuration fails validation.
253
+ """
254
+ try:
255
+ Configuration.validate(self.serialize())
256
+ except ValidationError as e:
257
+ raise ConfigContractBrokenError(e) from e
258
+
259
+ # Called by RuntimeConfig.from_args
260
+ @classmethod
261
+ def collect_parts(cls: Type["RuntimeConfig"], args: Any) -> Tuple[Project, Profile]:
262
+ # profile_name from the project
263
+ project_root = args.project_dir if args.project_dir else os.getcwd()
264
+ cli_vars: Dict[str, Any] = getattr(args, "vars", {})
265
+ profile = cls.get_profile(
266
+ project_root,
267
+ cli_vars,
268
+ args,
269
+ )
270
+ flags = get_flags()
271
+ project = load_project(project_root, bool(flags.VERSION_CHECK), profile, cli_vars)
272
+ return project, profile
273
+
274
+ # Called in task/base.py, in BaseTask.from_args
275
+ @classmethod
276
+ def from_args(cls, args: Any) -> "RuntimeConfig":
277
+ """Given arguments, read in dbt_project.yml from the current directory,
278
+ read in packages.yml if it exists, and use them to find the profile to
279
+ load.
280
+
281
+ :param args: The arguments as parsed from the cli.
282
+ :raises DbtProjectError: If the project is invalid or missing.
283
+ :raises DbtProfileError: If the profile is invalid or missing.
284
+ :raises DbtValidationError: If the cli variables are invalid.
285
+ """
286
+ project, profile = cls.collect_parts(args)
287
+
288
+ return cls.from_parts(
289
+ project=project,
290
+ profile=profile,
291
+ args=args,
292
+ )
293
+
294
+ def get_metadata(self) -> ManifestMetadata:
295
+ return ManifestMetadata(
296
+ project_name=self.project_name,
297
+ project_id=self.hashed_name(),
298
+ user_id=tracking.active_user.id if tracking.active_user else None,
299
+ send_anonymous_usage_stats=(
300
+ get_flags().SEND_ANONYMOUS_USAGE_STATS if tracking.active_user else None
301
+ ),
302
+ adapter_type=self.credentials.type,
303
+ quoting=Quoting(
304
+ database=self.quoting.get("database", None),
305
+ schema=self.quoting.get("schema", None),
306
+ identifier=self.quoting.get("identifier", None),
307
+ column=self.quoting.get("column", None),
308
+ ),
309
+ run_started_at=(
310
+ tracking.active_user.run_started_at if tracking.active_user is not None else None
311
+ ),
312
+ )
313
+
314
+ def _get_v2_config_paths(
315
+ self,
316
+ config,
317
+ path: FQNPath,
318
+ paths: MutableSet[FQNPath],
319
+ ) -> PathSet:
320
+ for key, value in config.items():
321
+ if isinstance(value, dict) and not key.startswith("+"):
322
+ self._get_config_paths(value, path + (key,), paths)
323
+ else:
324
+ paths.add(path)
325
+ return frozenset(paths)
326
+
327
+ def _get_config_paths(
328
+ self,
329
+ config: Dict[str, Any],
330
+ path: FQNPath = (),
331
+ paths: Optional[MutableSet[FQNPath]] = None,
332
+ ) -> PathSet:
333
+ if paths is None:
334
+ paths = set()
335
+
336
+ for key, value in config.items():
337
+ if isinstance(value, dict) and not key.startswith("+"):
338
+ self._get_v2_config_paths(value, path + (key,), paths)
339
+ else:
340
+ paths.add(path)
341
+ return frozenset(paths)
342
+
343
+ def get_resource_config_paths(self) -> Dict[str, PathSet]:
344
+ """Return a dictionary with resource type keys whose values are
345
+ lists of lists of strings, where each inner list of strings represents
346
+ a configured path in the resource.
347
+ """
348
+ return {
349
+ "models": self._get_config_paths(self.models),
350
+ "seeds": self._get_config_paths(self.seeds),
351
+ "snapshots": self._get_config_paths(self.snapshots),
352
+ "sources": self._get_config_paths(self.sources),
353
+ "data_tests": self._get_config_paths(self.data_tests),
354
+ "unit_tests": self._get_config_paths(self.unit_tests),
355
+ "metrics": self._get_config_paths(self.metrics),
356
+ "semantic_models": self._get_config_paths(self.semantic_models),
357
+ "saved_queries": self._get_config_paths(self.saved_queries),
358
+ "exposures": self._get_config_paths(self.exposures),
359
+ "functions": self._get_config_paths(self.functions),
360
+ }
361
+
362
+ def warn_for_unused_resource_config_paths(
363
+ self,
364
+ resource_fqns: Mapping[str, PathSet],
365
+ disabled: PathSet,
366
+ ) -> None:
367
+ """Return a list of lists of strings, where each inner list of strings
368
+ represents a type + FQN path of a resource configuration that is not
369
+ used.
370
+ """
371
+ disabled_fqns = frozenset(tuple(fqn) for fqn in disabled)
372
+ resource_config_paths = self.get_resource_config_paths()
373
+ unused_resource_config_paths = []
374
+ for resource_type, config_paths in resource_config_paths.items():
375
+ used_fqns = resource_fqns.get(resource_type, frozenset())
376
+ fqns = used_fqns | disabled_fqns
377
+
378
+ for config_path in config_paths:
379
+ if not _is_config_used(config_path, fqns):
380
+ resource_path = ".".join(i for i in ((resource_type,) + config_path))
381
+ unused_resource_config_paths.append(resource_path)
382
+
383
+ if len(unused_resource_config_paths) == 0:
384
+ return
385
+
386
+ warn_or_error(UnusedResourceConfigPath(unused_config_paths=unused_resource_config_paths))
387
+
388
+ def load_dependencies(self, base_only=False) -> Mapping[str, "RuntimeConfig"]:
389
+ if self.dependencies is None:
390
+ all_projects = {self.project_name: self}
391
+ internal_packages = get_include_paths(self.credentials.type)
392
+ if base_only:
393
+ # Test setup -- we want to load macros without dependencies
394
+ project_paths = itertools.chain(internal_packages)
395
+ else:
396
+ # raise exception if fewer installed packages than in packages.yml
397
+ count_packages_specified = len(self.packages.packages) # type: ignore
398
+ count_packages_installed = len(tuple(self._get_project_directories()))
399
+ if count_packages_specified > count_packages_installed:
400
+ raise UninstalledPackagesFoundError(
401
+ count_packages_specified,
402
+ count_packages_installed,
403
+ self.packages_specified_path,
404
+ self.packages_install_path,
405
+ )
406
+ project_paths = itertools.chain(internal_packages, self._get_project_directories())
407
+ for project_name, project in self.load_projects(project_paths):
408
+ if project_name in all_projects:
409
+ raise NonUniquePackageNameError(project_name)
410
+ all_projects[project_name] = project
411
+ self.dependencies = all_projects
412
+ return self.dependencies
413
+
414
+ def clear_dependencies(self):
415
+ self.dependencies = None
416
+
417
+ # Called by 'load_dependencies' in this class
418
+ def load_projects(self, paths: Iterable[Path]) -> Iterator[Tuple[str, "RuntimeConfig"]]:
419
+ for path in paths:
420
+ try:
421
+ project = self.new_project(str(path))
422
+ except DbtProjectError as e:
423
+ raise DbtProjectError(
424
+ f"Failed to read package: {e}",
425
+ result_type="invalid_project",
426
+ path=path,
427
+ ) from e
428
+ else:
429
+ yield project.project_name, project
430
+
431
+ def _get_project_directories(self) -> Iterator[Path]:
432
+ root = Path(self.project_root) / self.packages_install_path
433
+
434
+ if root.exists():
435
+ for path in root.iterdir():
436
+ if path.is_dir() and not path.name.startswith("__"):
437
+ yield path
438
+
439
+
440
+ class UnsetCredentials(Credentials):
441
+ def __init__(self) -> None:
442
+ super().__init__("", "")
443
+
444
+ @property
445
+ def type(self):
446
+ return None
447
+
448
+ @property
449
+ def unique_field(self):
450
+ return None
451
+
452
+ def connection_info(self, *args, **kwargs):
453
+ return {}
454
+
455
+ def _connection_keys(self):
456
+ return ()
457
+
458
+
459
+ # This is used by commands which do not require
460
+ # a profile, i.e. dbt deps and clean
461
+ class UnsetProfile(Profile):
462
+ def __init__(self):
463
+ self.credentials = UnsetCredentials()
464
+ self.profile_name = ""
465
+ self.target_name = ""
466
+ self.threads = -1
467
+
468
+ def to_target_dict(self):
469
+ return DictDefaultEmptyStr({})
470
+
471
+ def __getattribute__(self, name):
472
+ if name in {"profile_name", "target_name", "threads"}:
473
+ raise DbtRuntimeError(f'Error: disallowed attribute "{name}" - no profile!')
474
+
475
+ return Profile.__getattribute__(self, name)
476
+
477
+
478
+ UNUSED_RESOURCE_CONFIGURATION_PATH_MESSAGE = """\
479
+ Configuration paths exist in your dbt_project.yml file which do not \
480
+ apply to any resources.
481
+ There are {} unused configuration paths:
482
+ {}
483
+ """
484
+
485
+
486
+ def _is_config_used(path, fqns):
487
+ if fqns:
488
+ for fqn in fqns:
489
+ if len(path) <= len(fqn) and fqn[: len(path)] == path:
490
+ return True
491
+ return False
@@ -0,0 +1,209 @@
1
+ from copy import deepcopy
2
+ from pathlib import Path
3
+ from typing import Any, Dict, Optional, Union
4
+
5
+ from dvt.clients.yaml_helper import Dumper, Loader, load_yaml_text, yaml # noqa: F401
6
+ from dvt.contracts.selection import SelectorFile
7
+ from dvt.exceptions import DbtSelectorsError
8
+ from dvt.graph import SelectionSpec, parse_from_selectors_definition
9
+ from dvt.graph.selector_spec import SelectionCriteria
10
+
11
+ from dbt_common.clients.system import (
12
+ load_file_contents,
13
+ path_exists,
14
+ resolve_path_from_base,
15
+ )
16
+ from dbt_common.dataclass_schema import ValidationError
17
+ from dbt_common.exceptions import DbtRuntimeError
18
+
19
+ from .renderer import BaseRenderer
20
+
21
+ MALFORMED_SELECTOR_ERROR = """\
22
+ The selectors.yml file in this project is malformed. Please double check
23
+ the contents of this file and fix any errors before retrying.
24
+
25
+ You can find more information on the syntax for this file here:
26
+ https://docs.getdbt.com/reference/node-selection/yaml-selectors
27
+
28
+ Validator Error:
29
+ {error}
30
+ """
31
+
32
+
33
+ class SelectorConfig(Dict[str, Dict[str, Union[SelectionSpec, bool]]]):
34
+ @classmethod
35
+ def selectors_from_dict(cls, data: Dict[str, Any]) -> "SelectorConfig":
36
+ try:
37
+ SelectorFile.validate(data)
38
+ selector_file = SelectorFile.from_dict(data)
39
+ validate_selector_default(selector_file)
40
+ selectors = parse_from_selectors_definition(selector_file)
41
+ except ValidationError as exc:
42
+ yaml_sel_cfg = yaml.dump(exc.instance)
43
+ raise DbtSelectorsError(
44
+ f"Could not parse selector file data: \n{yaml_sel_cfg}\n"
45
+ f"Valid root-level selector definitions: "
46
+ f"union, intersection, string, dictionary. No lists. "
47
+ f"\nhttps://docs.getdbt.com/reference/node-selection/"
48
+ f"yaml-selectors",
49
+ result_type="invalid_selector",
50
+ ) from exc
51
+ except DbtRuntimeError as exc:
52
+ raise DbtSelectorsError(
53
+ f"Could not read selector file data: {exc}",
54
+ result_type="invalid_selector",
55
+ ) from exc
56
+
57
+ return cls(selectors)
58
+
59
+ @classmethod
60
+ def render_from_dict(
61
+ cls,
62
+ data: Dict[str, Any],
63
+ renderer: BaseRenderer,
64
+ ) -> "SelectorConfig":
65
+ try:
66
+ rendered = renderer.render_data(data)
67
+ except (ValidationError, DbtRuntimeError) as exc:
68
+ raise DbtSelectorsError(
69
+ f"Could not render selector data: {exc}",
70
+ result_type="invalid_selector",
71
+ ) from exc
72
+ return cls.selectors_from_dict(rendered)
73
+
74
+ @classmethod
75
+ def from_path(
76
+ cls,
77
+ path: Path,
78
+ renderer: BaseRenderer,
79
+ ) -> "SelectorConfig":
80
+ try:
81
+ data = load_yaml_text(load_file_contents(str(path)))
82
+ if data is None:
83
+ raise ValidationError("No data found in selector file at path: {path}")
84
+ except (ValidationError, DbtRuntimeError) as exc:
85
+ raise DbtSelectorsError(
86
+ f"Could not read selector file: {exc}",
87
+ result_type="invalid_selector",
88
+ path=path,
89
+ ) from exc
90
+
91
+ try:
92
+ return cls.render_from_dict(data, renderer)
93
+ except DbtSelectorsError as exc:
94
+ exc.path = path
95
+ raise
96
+
97
+
98
+ def selector_data_from_root(project_root: str) -> Optional[Dict[str, Any]]:
99
+ selector_filepath = resolve_path_from_base("selectors.yml", project_root)
100
+
101
+ if path_exists(selector_filepath):
102
+ selectors_dict = load_yaml_text(load_file_contents(selector_filepath))
103
+ else:
104
+ selectors_dict = None
105
+ return selectors_dict
106
+
107
+
108
+ def selector_config_from_data(selectors_data: Dict[str, Any]) -> SelectorConfig:
109
+ if not selectors_data:
110
+ selectors_data = {"selectors": []}
111
+
112
+ try:
113
+ selectors = SelectorConfig.selectors_from_dict(selectors_data)
114
+ except ValidationError as e:
115
+ raise DbtSelectorsError(
116
+ MALFORMED_SELECTOR_ERROR.format(error=str(e.message)),
117
+ result_type="invalid_selector",
118
+ ) from e
119
+ return selectors
120
+
121
+
122
+ def validate_selector_default(selector_file: SelectorFile) -> None:
123
+ """Check if a selector.yml file has more than 1 default key set to true"""
124
+ default_set: bool = False
125
+ default_selector_name: Union[str, None] = None
126
+
127
+ for selector in selector_file.selectors:
128
+ if selector.default is True and default_set is False:
129
+ default_set = True
130
+ default_selector_name = selector.name
131
+ continue
132
+ if selector.default is True and default_set is True:
133
+ raise DbtSelectorsError(
134
+ "Error when parsing the selector file. "
135
+ "Found multiple selectors with `default: true`:"
136
+ f"{default_selector_name} and {selector.name}"
137
+ )
138
+
139
+
140
+ # These are utilities to clean up the dictionary created from
141
+ # selectors.yml by turning the cli-string format entries into
142
+ # normalized dictionary entries. It parallels the flow in
143
+ # dbt/graph/cli.py. If changes are made there, it might
144
+ # be necessary to make changes here. Ideally it would be
145
+ # good to combine the two flows into one at some point.
146
+ class SelectorDict:
147
+ @classmethod
148
+ def parse_dict_definition(cls, definition, selector_dict={}):
149
+ key = list(definition)[0]
150
+ value = definition[key]
151
+ if isinstance(value, list):
152
+ new_values = []
153
+ for sel_def in value:
154
+ new_value = cls.parse_from_definition(sel_def, selector_dict=selector_dict)
155
+ new_values.append(new_value)
156
+ value = new_values
157
+ if key == "exclude":
158
+ definition = {key: value}
159
+ elif len(definition) == 1:
160
+ definition = {"method": key, "value": value}
161
+ elif key == "method" and value == "selector":
162
+ sel_def = definition.get("value")
163
+ if sel_def not in selector_dict:
164
+ raise DbtSelectorsError(f"Existing selector definition for {sel_def} not found.")
165
+ return selector_dict[definition["value"]]["definition"]
166
+ return definition
167
+
168
+ @classmethod
169
+ def parse_a_definition(cls, def_type, definition, selector_dict={}):
170
+ # this definition must be a list
171
+ new_dict = {def_type: []}
172
+ for sel_def in definition[def_type]:
173
+ if isinstance(sel_def, dict):
174
+ sel_def = cls.parse_from_definition(sel_def, selector_dict=selector_dict)
175
+ new_dict[def_type].append(sel_def)
176
+ elif isinstance(sel_def, str):
177
+ sel_def = SelectionCriteria.dict_from_single_spec(sel_def)
178
+ new_dict[def_type].append(sel_def)
179
+ else:
180
+ new_dict[def_type].append(sel_def)
181
+ return new_dict
182
+
183
+ @classmethod
184
+ def parse_from_definition(cls, definition, selector_dict={}):
185
+ if isinstance(definition, str):
186
+ definition = SelectionCriteria.dict_from_single_spec(definition)
187
+ elif "union" in definition:
188
+ definition = cls.parse_a_definition("union", definition, selector_dict=selector_dict)
189
+ elif "intersection" in definition:
190
+ definition = cls.parse_a_definition(
191
+ "intersection", definition, selector_dict=selector_dict
192
+ )
193
+ elif isinstance(definition, dict):
194
+ definition = cls.parse_dict_definition(definition, selector_dict=selector_dict)
195
+ return definition
196
+
197
+ # This is the normal entrypoint of this code. Give it the
198
+ # list of selectors generated from the selectors.yml file.
199
+ @classmethod
200
+ def parse_from_selectors_list(cls, selectors):
201
+ selector_dict = {}
202
+ for selector in selectors:
203
+ sel_name = selector["name"]
204
+ selector_dict[sel_name] = selector
205
+ definition = cls.parse_from_definition(
206
+ selector["definition"], selector_dict=deepcopy(selector_dict)
207
+ )
208
+ selector_dict[sel_name]["definition"] = definition
209
+ return selector_dict