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/parser/base.py ADDED
@@ -0,0 +1,549 @@
1
+ import abc
2
+ import itertools
3
+ import os
4
+ from typing import Any, Dict, Generic, List, Optional, TypeVar
5
+
6
+ from dvt import deprecations, hooks, utils
7
+ from dvt.artifacts.resources import Contract
8
+ from dvt.clients.jinja import MacroGenerator, get_rendered
9
+ from dvt.config import RuntimeConfig
10
+ from dvt.context.context_config import ContextConfig
11
+ from dvt.context.providers import (
12
+ generate_generate_name_macro_context,
13
+ generate_parser_model_context,
14
+ )
15
+ from dvt.contracts.files import SchemaSourceFile
16
+ from dvt.contracts.graph.manifest import Manifest
17
+ from dvt.contracts.graph.nodes import BaseNode, ManifestNode
18
+ from dvt.contracts.graph.unparsed import Docs, UnparsedNode
19
+ from dvt.exceptions import (
20
+ ConfigUpdateError,
21
+ DbtInternalError,
22
+ DictParseError,
23
+ InvalidAccessTypeError,
24
+ )
25
+ from dvt.flags import get_flags
26
+ from dvt.jsonschemas.jsonschemas import validate_model_config
27
+ from dvt.node_types import AccessType, ModelLanguage, NodeType
28
+ from dvt.parser.common import resource_types_to_schema_file_keys
29
+ from dvt.parser.search import FileBlock
30
+
31
+ from dbt.adapters.factory import get_adapter # noqa: F401
32
+ from dbt_common.clients._jinja_blocks import ExtractWarning
33
+ from dbt_common.dataclass_schema import ValidationError
34
+ from dbt_common.events.base_types import EventLevel
35
+ from dbt_common.events.functions import fire_event
36
+ from dbt_common.events.types import Note
37
+ from dbt_common.utils import deep_merge
38
+
39
+ # internally, the parser may store a less-restrictive type that will be
40
+ # transformed into the final type. But it will have to be derived from
41
+ # ParsedNode to be operable.
42
+ FinalValue = TypeVar("FinalValue", bound=BaseNode)
43
+ IntermediateValue = TypeVar("IntermediateValue", bound=BaseNode)
44
+
45
+ FinalNode = TypeVar("FinalNode", bound=ManifestNode)
46
+
47
+
48
+ ConfiguredBlockType = TypeVar("ConfiguredBlockType", bound=FileBlock)
49
+
50
+
51
+ class BaseParser(Generic[FinalValue]):
52
+ def __init__(self, project: RuntimeConfig, manifest: Manifest) -> None:
53
+ self.project: RuntimeConfig = project
54
+ self.manifest: Manifest = manifest
55
+
56
+ @abc.abstractmethod
57
+ def parse_file(self, block: FileBlock) -> None:
58
+ pass
59
+
60
+ @abc.abstractproperty
61
+ def resource_type(self) -> NodeType:
62
+ pass
63
+
64
+ def generate_unique_id(self, resource_name: str, hash: Optional[str] = None) -> str:
65
+ """Returns a unique identifier for a resource
66
+ An optional hash may be passed in to ensure uniqueness for edge cases"""
67
+
68
+ return ".".join(
69
+ filter(None, [self.resource_type, self.project.project_name, resource_name, hash])
70
+ )
71
+
72
+ def _handle_extract_warning(self, warning: ExtractWarning, file: str) -> None:
73
+ deprecations.warn("unexpected-jinja-block-deprecation", msg=warning.msg, file=file)
74
+
75
+
76
+ class Parser(BaseParser[FinalValue], Generic[FinalValue]):
77
+ def __init__(
78
+ self,
79
+ project: RuntimeConfig,
80
+ manifest: Manifest,
81
+ root_project: RuntimeConfig,
82
+ ) -> None:
83
+ super().__init__(project, manifest)
84
+ self.root_project = root_project
85
+
86
+
87
+ class RelationUpdate:
88
+ # "component" is database, schema or alias
89
+ def __init__(self, config: RuntimeConfig, manifest: Manifest, component: str) -> None:
90
+ default_macro = manifest.find_generate_macro_by_name(
91
+ component=component,
92
+ root_project_name=config.project_name,
93
+ )
94
+ if default_macro is None:
95
+ raise DbtInternalError(f"No macro with name generate_{component}_name found")
96
+
97
+ default_macro_context = generate_generate_name_macro_context(
98
+ default_macro, config, manifest
99
+ )
100
+ self.default_updater = MacroGenerator(default_macro, default_macro_context)
101
+
102
+ package_names = config.dependencies.keys() if config.dependencies else {}
103
+ package_updaters = {}
104
+ for package_name in package_names:
105
+ package_macro = manifest.find_generate_macro_by_name(
106
+ component=component,
107
+ root_project_name=config.project_name,
108
+ imported_package=package_name,
109
+ )
110
+ if package_macro:
111
+ imported_macro_context = generate_generate_name_macro_context(
112
+ package_macro, config, manifest
113
+ )
114
+ package_updaters[package_macro.package_name] = MacroGenerator(
115
+ package_macro, imported_macro_context
116
+ )
117
+
118
+ self.package_updaters = package_updaters
119
+ self.component = component
120
+
121
+ def __call__(self, parsed_node: Any, override: Optional[str]) -> None:
122
+ if getattr(parsed_node, "package_name", None) in self.package_updaters:
123
+ new_value = self.package_updaters[parsed_node.package_name](override, parsed_node)
124
+ else:
125
+ new_value = self.default_updater(override, parsed_node)
126
+
127
+ if isinstance(new_value, str):
128
+ new_value = new_value.strip()
129
+ setattr(parsed_node, self.component, new_value)
130
+
131
+
132
+ class ConfiguredParser(
133
+ Parser[FinalNode],
134
+ Generic[ConfiguredBlockType, FinalNode],
135
+ ):
136
+ def __init__(
137
+ self,
138
+ project: RuntimeConfig,
139
+ manifest: Manifest,
140
+ root_project: RuntimeConfig,
141
+ ) -> None:
142
+ super().__init__(project, manifest, root_project)
143
+
144
+ # this sets callables from RelationUpdate
145
+ self._update_node_database = RelationUpdate(
146
+ manifest=manifest, config=root_project, component="database"
147
+ )
148
+ self._update_node_schema = RelationUpdate(
149
+ manifest=manifest, config=root_project, component="schema"
150
+ )
151
+ self._update_node_alias = RelationUpdate(
152
+ manifest=manifest, config=root_project, component="alias"
153
+ )
154
+
155
+ @classmethod
156
+ @abc.abstractmethod
157
+ def get_compiled_path(cls, block: ConfiguredBlockType) -> str:
158
+ pass
159
+
160
+ @abc.abstractmethod
161
+ def parse_from_dict(self, dict, validate=True) -> FinalNode:
162
+ pass
163
+
164
+ @abc.abstractproperty
165
+ def resource_type(self) -> NodeType:
166
+ pass
167
+
168
+ @property
169
+ def default_schema(self):
170
+ return self.root_project.credentials.schema
171
+
172
+ @property
173
+ def default_database(self):
174
+ return self.root_project.credentials.database
175
+
176
+ def get_fqn_prefix(self, path: str) -> List[str]:
177
+ no_ext = os.path.splitext(path)[0]
178
+ fqn = [self.project.project_name]
179
+ fqn.extend(utils.split_path(no_ext)[:-1])
180
+ return fqn
181
+
182
+ def get_fqn(self, path: str, name: str) -> List[str]:
183
+ """Get the FQN for the node. This impacts node selection and config
184
+ application.
185
+ """
186
+ fqn = self.get_fqn_prefix(path)
187
+ fqn.append(name)
188
+ return fqn
189
+
190
+ def _mangle_hooks(self, config):
191
+ """Given a config dict that may have `pre-hook`/`post-hook` keys,
192
+ convert it from the yucky maybe-a-string, maybe-a-dict to a dict.
193
+ """
194
+ # Like most of parsing, this is a horrible hack :(
195
+ for key in hooks.ModelHookType:
196
+ if key in config:
197
+ config[key] = [hooks.get_hook_dict(h) for h in config[key]]
198
+
199
+ def _create_error_node(
200
+ self, name: str, path: str, original_file_path: str, raw_code: str, language: str = "sql"
201
+ ) -> UnparsedNode:
202
+ """If we hit an error before we've actually parsed a node, provide some
203
+ level of useful information by attaching this to the exception.
204
+ """
205
+ # this is a bit silly, but build an UnparsedNode just for error
206
+ # message reasons
207
+ return UnparsedNode(
208
+ name=name,
209
+ resource_type=self.resource_type,
210
+ path=path,
211
+ original_file_path=original_file_path,
212
+ package_name=self.project.project_name,
213
+ raw_code=raw_code,
214
+ language=language,
215
+ )
216
+
217
+ def _create_parsetime_node(
218
+ self,
219
+ block: ConfiguredBlockType,
220
+ path: str,
221
+ config: ContextConfig,
222
+ fqn: List[str],
223
+ name=None,
224
+ **kwargs,
225
+ ) -> FinalNode:
226
+ """Create the node that will be passed in to the parser context for
227
+ "rendering". Some information may be partial, as it'll be updated by
228
+ config() and any ref()/source() calls discovered during rendering.
229
+ """
230
+ if name is None:
231
+ name = block.name
232
+ if block.path.relative_path.endswith(".py"):
233
+ language = ModelLanguage.python
234
+ else:
235
+ # this is not ideal but we have a lot of tests to adjust if don't do it
236
+ language = ModelLanguage.sql
237
+
238
+ dct = {
239
+ "alias": name,
240
+ "schema": self.default_schema,
241
+ "database": self.default_database,
242
+ "fqn": fqn,
243
+ "name": name,
244
+ "resource_type": self.resource_type,
245
+ "path": path,
246
+ "original_file_path": block.path.original_file_path,
247
+ "package_name": self.project.project_name,
248
+ "raw_code": block.contents or "",
249
+ "language": language,
250
+ "unique_id": self.generate_unique_id(name),
251
+ "config": self.config_dict(config),
252
+ "checksum": block.file.checksum.to_dict(omit_none=True),
253
+ }
254
+ dct.update(kwargs)
255
+
256
+ # TODO: we're doing this becaus return type is _required_ for the FunctionNode
257
+ # but we don't get the return type until we patch the node with the yml definition
258
+ # so we need to set it to a default value here.
259
+ if self.resource_type == NodeType.Function:
260
+ dct["returns"] = {"data_type": "INVALID_TYPE"}
261
+
262
+ try:
263
+ return self.parse_from_dict(dct, validate=True)
264
+ except ValidationError as exc:
265
+ # this is a bit silly, but build an UnparsedNode just for error
266
+ # message reasons
267
+ node = self._create_error_node(
268
+ name=block.name,
269
+ path=path,
270
+ original_file_path=block.path.original_file_path,
271
+ raw_code=block.contents,
272
+ )
273
+ raise DictParseError(exc, node=node)
274
+
275
+ def _context_for(self, parsed_node: FinalNode, config: ContextConfig) -> Dict[str, Any]:
276
+ return generate_parser_model_context(parsed_node, self.root_project, self.manifest, config)
277
+
278
+ def render_with_context(self, parsed_node: FinalNode, config: ContextConfig):
279
+ # Given the parsed node and a ContextConfig to use during parsing,
280
+ # render the node's sql with macro capture enabled.
281
+ # Note: this mutates the config object when config calls are rendered.
282
+ context = self._context_for(parsed_node, config)
283
+
284
+ # this goes through the process of rendering, but just throws away
285
+ # the rendered result. The "macro capture" is the point?
286
+ get_rendered(parsed_node.raw_code, context, parsed_node, capture_macros=True)
287
+ return context
288
+
289
+ # This is taking the original config for the node, converting it to a dict,
290
+ # updating the config with new config passed in, then re-creating the
291
+ # config from the dict in the node.
292
+ def update_parsed_node_config_dict(
293
+ self, parsed_node: FinalNode, config_dict: Dict[str, Any]
294
+ ) -> None:
295
+ # Overwrite node config
296
+ final_config_dict = parsed_node.config.to_dict(omit_none=True)
297
+ final_config_dict.update({k.strip(): v for (k, v) in config_dict.items()})
298
+ # re-mangle hooks, in case we got new ones
299
+ self._mangle_hooks(final_config_dict)
300
+ parsed_node.config = parsed_node.config.from_dict(final_config_dict)
301
+
302
+ def update_parsed_node_relation_names(
303
+ self, parsed_node: FinalNode, config_dict: Dict[str, Any]
304
+ ) -> None:
305
+
306
+ # These call the RelationUpdate callable to go through generate_name macros
307
+ self._update_node_database(parsed_node, config_dict.get("database"))
308
+ self._update_node_schema(parsed_node, config_dict.get("schema"))
309
+ if parsed_node.schema is None:
310
+ fire_event(
311
+ Note(
312
+ msg=f"Node schema set to None from generate_schema_name call for node '{parsed_node.unique_id}'."
313
+ ),
314
+ level=EventLevel.DEBUG,
315
+ )
316
+
317
+ self._update_node_alias(parsed_node, config_dict.get("alias"))
318
+
319
+ # Snapshot nodes use special "target_database" and "target_schema" fields
320
+ # for backward compatibility
321
+ # We have to do getattr here because saved_query parser calls this method with
322
+ # Export object instead of a node.
323
+ if getattr(parsed_node, "resource_type", None) == NodeType.Snapshot:
324
+ if "target_database" in config_dict and config_dict["target_database"]:
325
+ parsed_node.database = config_dict["target_database"]
326
+ if "target_schema" in config_dict and config_dict["target_schema"]:
327
+ parsed_node.schema = config_dict["target_schema"]
328
+
329
+ self._update_node_relation_name(parsed_node)
330
+
331
+ def update_parsed_node_config(
332
+ self,
333
+ parsed_node: FinalNode,
334
+ config: ContextConfig,
335
+ context=None,
336
+ patch_config_dict=None,
337
+ patch_file_id=None,
338
+ validate_config_call_dict: bool = False,
339
+ ) -> None:
340
+ """Given the ContextConfig used for parsing and the parsed node,
341
+ generate and set the true values to use, overriding the temporary parse
342
+ values set in _build_intermediate_parsed_node.
343
+ """
344
+
345
+ # build_config_dict takes the config_call_dict in the ContextConfig object
346
+ # and calls calculate_node_config to combine dbt_project configs and
347
+ # config calls from SQL files, plus patch configs (from schema files)
348
+ # This normalize the config for a model node due #8520; should be improved latter
349
+ if not patch_config_dict:
350
+ patch_config_dict = {}
351
+ if (
352
+ parsed_node.resource_type == NodeType.Model
353
+ and parsed_node.language == ModelLanguage.python
354
+ ):
355
+ if "materialized" not in patch_config_dict:
356
+ patch_config_dict["materialized"] = "table"
357
+ config_dict = config.build_config_dict(patch_config_dict=patch_config_dict)
358
+
359
+ # Set tags on node provided in config blocks. Tags are additive, so even if
360
+ # config has been built before, we don't have to reset tags in the parsed_node.
361
+ model_tags = config_dict.get("tags", [])
362
+ for tag in model_tags:
363
+ if tag not in parsed_node.tags:
364
+ parsed_node.tags.append(tag)
365
+
366
+ # If we have meta in the config, copy to node level, for backwards
367
+ # compatibility with earlier node-only config.
368
+ if "meta" in config_dict and config_dict["meta"]:
369
+ parsed_node.meta = config_dict["meta"]
370
+
371
+ # If we have group in the config, copy to node level
372
+ if "group" in config_dict and config_dict["group"]:
373
+ parsed_node.group = config_dict["group"]
374
+
375
+ # If we have access in the config, copy to node level
376
+ if parsed_node.resource_type == NodeType.Model and config_dict.get("access", None):
377
+ if AccessType.is_valid(config_dict["access"]):
378
+ assert hasattr(parsed_node, "access")
379
+ parsed_node.access = AccessType(config_dict["access"])
380
+ else:
381
+ raise InvalidAccessTypeError(
382
+ unique_id=parsed_node.unique_id, field_value=config_dict["access"]
383
+ )
384
+
385
+ # If we have docs in the config, merge with the node level, for backwards
386
+ # compatibility with earlier node-only config.
387
+ if "docs" in config_dict and config_dict["docs"]:
388
+ # we set show at the value of the config if it is set, otherwise, inherit the value
389
+ docs_show = (
390
+ config_dict["docs"]["show"]
391
+ if "show" in config_dict["docs"]
392
+ else parsed_node.docs.show
393
+ )
394
+ if "node_color" in config_dict["docs"]:
395
+ parsed_node.docs = Docs(
396
+ show=docs_show, node_color=config_dict["docs"]["node_color"]
397
+ )
398
+ else:
399
+ parsed_node.docs = Docs(show=docs_show)
400
+
401
+ # If we have contract in the config, copy to node level
402
+ if "contract" in config_dict and config_dict["contract"]:
403
+ contract_dct = config_dict["contract"]
404
+ Contract.validate(contract_dct)
405
+ # Seed node has contract config (from NodeConfig) but no contract in SeedNode
406
+ if hasattr(parsed_node, "contract"):
407
+ parsed_node.contract = Contract.from_dict(contract_dct)
408
+
409
+ if get_flags().state_modified_compare_more_unrendered_values:
410
+ # Use the patch_file.unrendered_configs if available to update patch_dict_config,
411
+ # as provided patch_config_dict may actually already be rendered and thus sensitive to jinja evaluations
412
+ if patch_file_id:
413
+ patch_file = self.manifest.files.get(patch_file_id, None)
414
+ if patch_file and isinstance(patch_file, SchemaSourceFile):
415
+ schema_key = resource_types_to_schema_file_keys.get(parsed_node.resource_type)
416
+ if schema_key:
417
+ if unrendered_patch_config := patch_file.get_unrendered_config(
418
+ schema_key, parsed_node.name, getattr(parsed_node, "version", None)
419
+ ):
420
+ patch_config_dict = deep_merge(
421
+ patch_config_dict, unrendered_patch_config
422
+ )
423
+
424
+ # unrendered_config is used to compare the original database/schema/alias
425
+ # values and to handle 'same_config' and 'same_contents' calls
426
+ parsed_node.unrendered_config = config.build_config_dict(
427
+ rendered=False, patch_config_dict=patch_config_dict
428
+ )
429
+
430
+ # We validate the _config_call_dict here because there is more than
431
+ # one way that the _config_call_dict can be set and also, later it gets
432
+ # read multiple times. Doing the validation here ensures that the config
433
+ # is only validated once.
434
+ if parsed_node.resource_type == NodeType.Model and validate_config_call_dict:
435
+ validate_model_config(config._config_call_dict, parsed_node.original_file_path)
436
+
437
+ parsed_node.config_call_dict = config._config_call_dict
438
+ parsed_node.unrendered_config_call_dict = config._unrendered_config_call_dict
439
+
440
+ # do this once before we parse the node database/schema/alias, so
441
+ # parsed_node.config is what it would be if they did nothing
442
+ self.update_parsed_node_config_dict(parsed_node, config_dict)
443
+ # This updates the node database/schema/alias/relation_name
444
+ self.update_parsed_node_relation_names(parsed_node, config_dict)
445
+
446
+ # tests don't have hooks
447
+ if parsed_node.resource_type == NodeType.Test:
448
+ return
449
+
450
+ # at this point, we've collected our hooks. Use the node context to
451
+ # render each hook and collect refs/sources
452
+ assert hasattr(parsed_node.config, "pre_hook") and hasattr(parsed_node.config, "post_hook")
453
+ hooks = list(itertools.chain(parsed_node.config.pre_hook, parsed_node.config.post_hook))
454
+ # skip context rebuilding if there aren't any hooks
455
+ if not hooks:
456
+ return
457
+ if not context:
458
+ context = self._context_for(parsed_node, config)
459
+ for hook in hooks:
460
+ get_rendered(hook.sql, context, parsed_node, capture_macros=True)
461
+
462
+ def initial_config(self, fqn: List[str]) -> ContextConfig:
463
+ config_version = min([self.project.config_version, self.root_project.config_version])
464
+ if config_version == 2:
465
+ return ContextConfig(
466
+ self.root_project,
467
+ fqn,
468
+ self.resource_type,
469
+ self.project.project_name,
470
+ )
471
+ else:
472
+ raise DbtInternalError(
473
+ f"Got an unexpected project version={config_version}, expected 2"
474
+ )
475
+
476
+ def config_dict(
477
+ self,
478
+ config: ContextConfig,
479
+ ) -> Dict[str, Any]:
480
+ config_dict = config.build_config_dict(base=True)
481
+ self._mangle_hooks(config_dict)
482
+ return config_dict
483
+
484
+ def render_update(
485
+ self, node: FinalNode, config: ContextConfig, validate_config_call_dict: bool = False
486
+ ) -> None:
487
+ try:
488
+ context = self.render_with_context(node, config)
489
+ self.update_parsed_node_config(
490
+ node, config, context=context, validate_config_call_dict=validate_config_call_dict
491
+ )
492
+ except ValidationError as exc:
493
+ # we got a ValidationError - probably bad types in config()
494
+ raise ConfigUpdateError(exc, node=node) from exc
495
+
496
+ def add_result_node(self, block: FileBlock, node: ManifestNode):
497
+ if node.config.enabled:
498
+ self.manifest.add_node(block.file, node)
499
+ else:
500
+ self.manifest.add_disabled(block.file, node)
501
+
502
+ def parse_node(self, block: ConfiguredBlockType) -> FinalNode:
503
+ compiled_path: str = self.get_compiled_path(block)
504
+ fqn = self.get_fqn(compiled_path, block.name)
505
+
506
+ config: ContextConfig = self.initial_config(fqn)
507
+
508
+ node = self._create_parsetime_node(
509
+ block=block,
510
+ path=compiled_path,
511
+ config=config,
512
+ fqn=fqn,
513
+ )
514
+ self.render_update(node, config)
515
+ self.add_result_node(block, node)
516
+ return node
517
+
518
+ def _update_node_relation_name(self, node: ManifestNode):
519
+ # Seed and Snapshot nodes and Models that are not ephemeral,
520
+ # and TestNodes that store_failures.
521
+ # TestNodes do not get a relation_name without store failures
522
+ # because no schema is created.
523
+ if getattr(node, "is_relational", None) and not getattr(node, "is_ephemeral_model", None):
524
+ adapter = get_adapter(self.root_project)
525
+ relation_cls = adapter.Relation
526
+ node.relation_name = str(relation_cls.create_from(self.root_project, node))
527
+ else:
528
+ # Set it to None in case it changed with a config update
529
+ node.relation_name = None
530
+
531
+ @abc.abstractmethod
532
+ def parse_file(self, file_block: FileBlock) -> None:
533
+ pass
534
+
535
+
536
+ class SimpleParser(
537
+ ConfiguredParser[ConfiguredBlockType, FinalNode],
538
+ Generic[ConfiguredBlockType, FinalNode],
539
+ ):
540
+ pass
541
+
542
+
543
+ class SQLParser(ConfiguredParser[FileBlock, FinalNode], Generic[FinalNode]):
544
+ def parse_file(self, file_block: FileBlock) -> None:
545
+ self.parse_node(file_block)
546
+
547
+
548
+ class SimpleSQLParser(SQLParser[FinalNode]):
549
+ pass