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
@@ -0,0 +1,1904 @@
1
+ import enum
2
+ from collections import defaultdict
3
+ from dataclasses import dataclass, field, replace
4
+ from itertools import chain
5
+ from multiprocessing.synchronize import Lock
6
+ from typing import (
7
+ Any,
8
+ Callable,
9
+ ClassVar,
10
+ DefaultDict,
11
+ Dict,
12
+ Generic,
13
+ List,
14
+ Mapping,
15
+ MutableMapping,
16
+ Optional,
17
+ Set,
18
+ Tuple,
19
+ TypeVar,
20
+ Union,
21
+ )
22
+
23
+ from dvt import deprecations, tracking
24
+
25
+ # to preserve import paths
26
+ from dvt.artifacts.resources import (
27
+ BaseResource,
28
+ DeferRelation,
29
+ NodeConfig,
30
+ NodeVersion,
31
+ RefArgs,
32
+ )
33
+ from dvt.artifacts.schemas.manifest import ManifestMetadata, UniqueID, WritableManifest
34
+ from dvt.clients.jinja_static import statically_parse_ref_or_source
35
+ from dvt.contracts.files import (
36
+ AnySourceFile,
37
+ FileHash,
38
+ FixtureSourceFile,
39
+ SchemaSourceFile,
40
+ SourceFile,
41
+ )
42
+ from dvt.contracts.graph.nodes import (
43
+ RESOURCE_CLASS_TO_NODE_CLASS,
44
+ BaseNode,
45
+ Documentation,
46
+ Exposure,
47
+ FunctionNode,
48
+ GenericTestNode,
49
+ GraphMemberNode,
50
+ Group,
51
+ Macro,
52
+ ManifestNode,
53
+ Metric,
54
+ ModelNode,
55
+ SavedQuery,
56
+ SeedNode,
57
+ SemanticModel,
58
+ SingularTestNode,
59
+ SnapshotNode,
60
+ SourceDefinition,
61
+ UnitTestDefinition,
62
+ UnitTestFileFixture,
63
+ UnpatchedSourceDefinition,
64
+ )
65
+ from dvt.contracts.graph.unparsed import SourcePatch, UnparsedVersion
66
+ from dvt.contracts.util import SourceKey
67
+ from dvt.events.types import ArtifactWritten, UnpinnedRefNewVersionAvailable
68
+ from dvt.exceptions import (
69
+ AmbiguousResourceNameRefError,
70
+ CompilationError,
71
+ DuplicateResourceNameError,
72
+ )
73
+ from dvt.flags import get_flags
74
+ from dvt.mp_context import get_mp_context
75
+ from dvt.node_types import (
76
+ REFABLE_NODE_TYPES,
77
+ VERSIONED_NODE_TYPES,
78
+ AccessType,
79
+ NodeType,
80
+ )
81
+ from typing_extensions import Protocol
82
+
83
+ import dbt_common.exceptions
84
+ import dbt_common.utils
85
+ from dbt.adapters.exceptions import (
86
+ DuplicateMacroInPackageError,
87
+ DuplicateMaterializationNameError,
88
+ )
89
+ from dbt.adapters.factory import get_adapter_package_names
90
+ from dbt_common.dataclass_schema import dbtClassMixin
91
+ from dbt_common.events.contextvars import get_node_info
92
+ from dbt_common.events.functions import fire_event
93
+ from dbt_common.helper_types import PathSet
94
+
95
+ PackageName = str
96
+ DocName = str
97
+ RefName = str
98
+
99
+
100
+ def find_unique_id_for_package(storage, key, package: Optional[PackageName]) -> Optional[UniqueID]:
101
+ if key not in storage:
102
+ return None
103
+
104
+ pkg_dct: Mapping[PackageName, UniqueID] = storage[key]
105
+
106
+ if package is None:
107
+ if not pkg_dct:
108
+ return None
109
+ else:
110
+ return next(iter(pkg_dct.values()))
111
+ elif package in pkg_dct:
112
+ return pkg_dct[package]
113
+ else:
114
+ return None
115
+
116
+
117
+ class DocLookup(dbtClassMixin):
118
+ def __init__(self, manifest: "Manifest") -> None:
119
+ self.storage: Dict[str, Dict[PackageName, UniqueID]] = {}
120
+ self.populate(manifest)
121
+
122
+ def get_unique_id(self, key, package: Optional[PackageName]):
123
+ return find_unique_id_for_package(self.storage, key, package)
124
+
125
+ def find(self, key, package: Optional[PackageName], manifest: "Manifest"):
126
+ unique_id = self.get_unique_id(key, package)
127
+ if unique_id is not None:
128
+ return self.perform_lookup(unique_id, manifest)
129
+ return None
130
+
131
+ def add_doc(self, doc: Documentation):
132
+ if doc.name not in self.storage:
133
+ self.storage[doc.name] = {}
134
+ self.storage[doc.name][doc.package_name] = doc.unique_id
135
+
136
+ def populate(self, manifest):
137
+ for doc in manifest.docs.values():
138
+ self.add_doc(doc)
139
+
140
+ def perform_lookup(self, unique_id: UniqueID, manifest) -> Documentation:
141
+ if unique_id not in manifest.docs:
142
+ raise dbt_common.exceptions.DbtInternalError(
143
+ f"Doc {unique_id} found in cache but not found in manifest"
144
+ )
145
+ return manifest.docs[unique_id]
146
+
147
+
148
+ class SourceLookup(dbtClassMixin):
149
+ def __init__(self, manifest: "Manifest") -> None:
150
+ self.storage: Dict[str, Dict[PackageName, UniqueID]] = {}
151
+ self.populate(manifest)
152
+
153
+ def get_unique_id(self, search_name, package: Optional[PackageName]):
154
+ return find_unique_id_for_package(self.storage, search_name, package)
155
+
156
+ def find(self, search_name, package: Optional[PackageName], manifest: "Manifest"):
157
+ unique_id = self.get_unique_id(search_name, package)
158
+ if unique_id is not None:
159
+ return self.perform_lookup(unique_id, manifest)
160
+ return None
161
+
162
+ def add_source(self, source: SourceDefinition):
163
+ if source.search_name not in self.storage:
164
+ self.storage[source.search_name] = {}
165
+
166
+ self.storage[source.search_name][source.package_name] = source.unique_id
167
+
168
+ def populate(self, manifest):
169
+ for source in manifest.sources.values():
170
+ if hasattr(source, "source_name"):
171
+ self.add_source(source)
172
+
173
+ def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> SourceDefinition:
174
+ if unique_id not in manifest.sources:
175
+ raise dbt_common.exceptions.DbtInternalError(
176
+ f"Source {unique_id} found in cache but not found in manifest"
177
+ )
178
+ return manifest.sources[unique_id]
179
+
180
+
181
+ class FunctionLookup(dbtClassMixin):
182
+ def __init__(self, manifest: "Manifest") -> None:
183
+ self.storage: Dict[str, Dict[PackageName, UniqueID]] = {}
184
+ self.populate(manifest)
185
+
186
+ def get_unique_id(self, search_name, package: Optional[PackageName]):
187
+ return find_unique_id_for_package(self.storage, search_name, package)
188
+
189
+ def find(self, search_name, package: Optional[PackageName], manifest: "Manifest"):
190
+ unique_id = self.get_unique_id(search_name, package)
191
+ if unique_id is not None:
192
+ return self.perform_lookup(unique_id, manifest)
193
+ return None
194
+
195
+ def add_function(self, function: FunctionNode):
196
+ if function.search_name not in self.storage:
197
+ self.storage[function.search_name] = {}
198
+
199
+ self.storage[function.search_name][function.package_name] = function.unique_id
200
+
201
+ def populate(self, manifest):
202
+ for function in manifest.functions.values():
203
+ if hasattr(function, "name"):
204
+ self.add_function(function)
205
+
206
+ def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> FunctionNode:
207
+ if unique_id not in manifest.functions:
208
+ raise dbt_common.exceptions.DbtInternalError(
209
+ f"Function {unique_id} found in cache but not found in manifest"
210
+ )
211
+ return manifest.functions[unique_id]
212
+
213
+
214
+ class RefableLookup(dbtClassMixin):
215
+ # model, seed, snapshot, function
216
+ _lookup_types: ClassVar[set] = set(REFABLE_NODE_TYPES)
217
+ _versioned_types: ClassVar[set] = set(VERSIONED_NODE_TYPES)
218
+
219
+ def __init__(self, manifest: "Manifest") -> None:
220
+ self.storage: Dict[str, Dict[PackageName, UniqueID]] = {}
221
+ self.populate(manifest)
222
+
223
+ def get_unique_id(
224
+ self,
225
+ key: str,
226
+ package: Optional[PackageName],
227
+ version: Optional[NodeVersion],
228
+ node: Optional[GraphMemberNode] = None,
229
+ ):
230
+ if version:
231
+ key = f"{key}.v{version}"
232
+
233
+ unique_ids = self._find_unique_ids_for_package(key, package)
234
+ if len(unique_ids) > 1:
235
+ raise AmbiguousResourceNameRefError(key, unique_ids, node)
236
+ else:
237
+ return unique_ids[0] if unique_ids else None
238
+
239
+ def find(
240
+ self,
241
+ key: str,
242
+ package: Optional[PackageName],
243
+ version: Optional[NodeVersion],
244
+ manifest: "Manifest",
245
+ source_node: Optional[GraphMemberNode] = None,
246
+ ):
247
+ unique_id = self.get_unique_id(key, package, version, source_node)
248
+ if unique_id is not None:
249
+ node = self.perform_lookup(unique_id, manifest)
250
+ # If this is an unpinned ref (no 'version' arg was passed),
251
+ # AND this is a versioned node,
252
+ # AND this ref is being resolved at runtime -- get_node_info != {}
253
+ # Only ModelNodes can be versioned.
254
+ if (
255
+ isinstance(node, ModelNode)
256
+ and version is None
257
+ and node.is_versioned
258
+ and get_node_info()
259
+ ):
260
+ # Check to see if newer versions are available, and log an "FYI" if so
261
+ max_version: UnparsedVersion = max(
262
+ [
263
+ UnparsedVersion(v.version)
264
+ for v in manifest.nodes.values()
265
+ if isinstance(v, ModelNode)
266
+ and v.name == node.name
267
+ and v.version is not None
268
+ ]
269
+ )
270
+ assert node.latest_version is not None # for mypy, whenever i may find it
271
+ if max_version > UnparsedVersion(node.latest_version):
272
+ fire_event(
273
+ UnpinnedRefNewVersionAvailable(
274
+ node_info=get_node_info(),
275
+ ref_node_name=node.name,
276
+ ref_node_package=node.package_name,
277
+ ref_node_version=str(node.version),
278
+ ref_max_version=str(max_version.v),
279
+ )
280
+ )
281
+
282
+ return node
283
+ return None
284
+
285
+ def add_node(self, node: ManifestNode):
286
+ if node.resource_type in self._lookup_types:
287
+ if node.name not in self.storage:
288
+ self.storage[node.name] = {}
289
+
290
+ if node.is_versioned:
291
+ if node.search_name not in self.storage:
292
+ self.storage[node.search_name] = {}
293
+ self.storage[node.search_name][node.package_name] = node.unique_id
294
+ if node.is_latest_version: # type: ignore
295
+ self.storage[node.name][node.package_name] = node.unique_id
296
+ else:
297
+ self.storage[node.name][node.package_name] = node.unique_id
298
+
299
+ def populate(self, manifest):
300
+ for node in manifest.nodes.values():
301
+ self.add_node(node)
302
+
303
+ def perform_lookup(self, unique_id: UniqueID, manifest) -> ManifestNode:
304
+ if unique_id in manifest.nodes:
305
+ node = manifest.nodes[unique_id]
306
+ else:
307
+ raise dbt_common.exceptions.DbtInternalError(
308
+ f"Node {unique_id} found in cache but not found in manifest"
309
+ )
310
+ return node
311
+
312
+ def _find_unique_ids_for_package(self, key, package: Optional[PackageName]) -> List[str]:
313
+ if key not in self.storage:
314
+ return []
315
+
316
+ pkg_dct: Mapping[PackageName, UniqueID] = self.storage[key]
317
+
318
+ if package is None:
319
+ if not pkg_dct:
320
+ return []
321
+ else:
322
+ return list(pkg_dct.values())
323
+ elif package in pkg_dct:
324
+ return [pkg_dct[package]]
325
+ else:
326
+ return []
327
+
328
+
329
+ class MetricLookup(dbtClassMixin):
330
+ def __init__(self, manifest: "Manifest") -> None:
331
+ self.storage: Dict[str, Dict[PackageName, UniqueID]] = {}
332
+ self.populate(manifest)
333
+
334
+ def get_unique_id(self, search_name, package: Optional[PackageName]):
335
+ return find_unique_id_for_package(self.storage, search_name, package)
336
+
337
+ def find(self, search_name, package: Optional[PackageName], manifest: "Manifest"):
338
+ unique_id = self.get_unique_id(search_name, package)
339
+ if unique_id is not None:
340
+ return self.perform_lookup(unique_id, manifest)
341
+ return None
342
+
343
+ def add_metric(self, metric: Metric):
344
+ if metric.search_name not in self.storage:
345
+ self.storage[metric.search_name] = {}
346
+
347
+ self.storage[metric.search_name][metric.package_name] = metric.unique_id
348
+
349
+ def populate(self, manifest):
350
+ for metric in manifest.metrics.values():
351
+ if hasattr(metric, "name"):
352
+ self.add_metric(metric)
353
+
354
+ def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> Metric:
355
+ if unique_id not in manifest.metrics:
356
+ raise dbt_common.exceptions.DbtInternalError(
357
+ f"Metric {unique_id} found in cache but not found in manifest"
358
+ )
359
+ return manifest.metrics[unique_id]
360
+
361
+
362
+ class SavedQueryLookup(dbtClassMixin):
363
+ """Lookup utility for finding SavedQuery nodes"""
364
+
365
+ def __init__(self, manifest: "Manifest") -> None:
366
+ self.storage: Dict[str, Dict[PackageName, UniqueID]] = {}
367
+ self.populate(manifest)
368
+
369
+ def get_unique_id(self, search_name, package: Optional[PackageName]):
370
+ return find_unique_id_for_package(self.storage, search_name, package)
371
+
372
+ def find(self, search_name, package: Optional[PackageName], manifest: "Manifest"):
373
+ unique_id = self.get_unique_id(search_name, package)
374
+ if unique_id is not None:
375
+ return self.perform_lookup(unique_id, manifest)
376
+ return None
377
+
378
+ def add_saved_query(self, saved_query: SavedQuery):
379
+ if saved_query.search_name not in self.storage:
380
+ self.storage[saved_query.search_name] = {}
381
+
382
+ self.storage[saved_query.search_name][saved_query.package_name] = saved_query.unique_id
383
+
384
+ def populate(self, manifest):
385
+ for saved_query in manifest.saved_queries.values():
386
+ if hasattr(saved_query, "name"):
387
+ self.add_saved_query(saved_query)
388
+
389
+ def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> SavedQuery:
390
+ if unique_id not in manifest.saved_queries:
391
+ raise dbt_common.exceptions.DbtInternalError(
392
+ f"SavedQUery {unique_id} found in cache but not found in manifest"
393
+ )
394
+ return manifest.saved_queries[unique_id]
395
+
396
+
397
+ class SemanticModelByMeasureLookup(dbtClassMixin):
398
+ """Lookup utility for finding SemanticModel by measure
399
+
400
+ This is possible because measure names are supposed to be unique across
401
+ the semantic models in a manifest.
402
+ """
403
+
404
+ def __init__(self, manifest: "Manifest") -> None:
405
+ self.storage: DefaultDict[str, Dict[PackageName, UniqueID]] = defaultdict(dict)
406
+ self.populate(manifest)
407
+
408
+ def get_unique_id(self, search_name: str, package: Optional[PackageName]):
409
+ return find_unique_id_for_package(self.storage, search_name, package)
410
+
411
+ def find(
412
+ self, search_name: str, package: Optional[PackageName], manifest: "Manifest"
413
+ ) -> Optional[SemanticModel]:
414
+ """Tries to find a SemanticModel based on a measure name"""
415
+ unique_id = self.get_unique_id(search_name, package)
416
+ if unique_id is not None:
417
+ return self.perform_lookup(unique_id, manifest)
418
+ return None
419
+
420
+ def add(self, semantic_model: SemanticModel):
421
+ """Sets all measures for a SemanticModel as paths to the SemanticModel's `unique_id`"""
422
+ for measure in semantic_model.measures:
423
+ self.storage[measure.name][semantic_model.package_name] = semantic_model.unique_id
424
+
425
+ def populate(self, manifest: "Manifest"):
426
+ """Populate storage with all the measure + package paths to the Manifest's SemanticModels"""
427
+ for semantic_model in manifest.semantic_models.values():
428
+ self.add(semantic_model=semantic_model)
429
+ for disabled in manifest.disabled.values():
430
+ for node in disabled:
431
+ if isinstance(node, SemanticModel):
432
+ self.add(semantic_model=node)
433
+
434
+ def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> SemanticModel:
435
+ """Tries to get a SemanticModel from the Manifest"""
436
+ enabled_semantic_model: Optional[SemanticModel] = manifest.semantic_models.get(unique_id)
437
+ disabled_semantic_model: Optional[List] = manifest.disabled.get(unique_id)
438
+
439
+ if isinstance(enabled_semantic_model, SemanticModel):
440
+ return enabled_semantic_model
441
+ elif disabled_semantic_model is not None and isinstance(
442
+ disabled_semantic_model[0], SemanticModel
443
+ ):
444
+ return disabled_semantic_model[0]
445
+ else:
446
+ raise dbt_common.exceptions.DbtInternalError(
447
+ f"Semantic model `{unique_id}` found in cache but not found in manifest"
448
+ )
449
+
450
+
451
+ # This handles both models/seeds/snapshots and sources/metrics/exposures/semantic_models
452
+ class DisabledLookup(dbtClassMixin):
453
+ def __init__(self, manifest: "Manifest") -> None:
454
+ self.storage: Dict[str, Dict[PackageName, List[Any]]] = {}
455
+ self.populate(manifest)
456
+
457
+ def populate(self, manifest: "Manifest"):
458
+ for node in list(chain.from_iterable(manifest.disabled.values())):
459
+ self.add_node(node)
460
+
461
+ def add_node(self, node: GraphMemberNode) -> None:
462
+ if node.search_name not in self.storage:
463
+ self.storage[node.search_name] = {}
464
+ if node.package_name not in self.storage[node.search_name]:
465
+ self.storage[node.search_name][node.package_name] = []
466
+ self.storage[node.search_name][node.package_name].append(node)
467
+
468
+ # This should return a list of disabled nodes. It's different from
469
+ # the other Lookup functions in that it returns full nodes, not just unique_ids
470
+ def find(
471
+ self,
472
+ search_name,
473
+ package: Optional[PackageName],
474
+ version: Optional[NodeVersion] = None,
475
+ resource_types: Optional[List[NodeType]] = None,
476
+ ) -> Optional[List[Any]]:
477
+ if version:
478
+ search_name = f"{search_name}.v{version}"
479
+
480
+ if search_name not in self.storage:
481
+ return None
482
+
483
+ pkg_dct: Mapping[PackageName, List[Any]] = self.storage[search_name]
484
+
485
+ nodes = []
486
+ if package is None:
487
+ if not pkg_dct:
488
+ return None
489
+ else:
490
+ nodes = next(iter(pkg_dct.values()))
491
+ elif package in pkg_dct:
492
+ nodes = pkg_dct[package]
493
+ else:
494
+ return None
495
+
496
+ if resource_types is None:
497
+ return nodes
498
+ else:
499
+ new_nodes = []
500
+ for node in nodes:
501
+ if node.resource_type in resource_types:
502
+ new_nodes.append(node)
503
+ if not new_nodes:
504
+ return None
505
+ else:
506
+ return new_nodes
507
+
508
+
509
+ class AnalysisLookup(RefableLookup):
510
+ _lookup_types: ClassVar[set] = set([NodeType.Analysis])
511
+ _versioned_types: ClassVar[set] = set()
512
+
513
+
514
+ class SingularTestLookup(dbtClassMixin):
515
+ def __init__(self, manifest: "Manifest") -> None:
516
+ self.storage: Dict[str, Dict[PackageName, UniqueID]] = {}
517
+ self.populate(manifest)
518
+
519
+ def get_unique_id(self, search_name, package: Optional[PackageName]) -> Optional[UniqueID]:
520
+ return find_unique_id_for_package(self.storage, search_name, package)
521
+
522
+ def find(
523
+ self, search_name, package: Optional[PackageName], manifest: "Manifest"
524
+ ) -> Optional[SingularTestNode]:
525
+ unique_id = self.get_unique_id(search_name, package)
526
+ if unique_id is not None:
527
+ return self.perform_lookup(unique_id, manifest)
528
+ return None
529
+
530
+ def add_singular_test(self, source: SingularTestNode) -> None:
531
+ if source.search_name not in self.storage:
532
+ self.storage[source.search_name] = {}
533
+
534
+ self.storage[source.search_name][source.package_name] = source.unique_id
535
+
536
+ def populate(self, manifest: "Manifest") -> None:
537
+ for node in manifest.nodes.values():
538
+ if isinstance(node, SingularTestNode):
539
+ self.add_singular_test(node)
540
+
541
+ def perform_lookup(self, unique_id: UniqueID, manifest: "Manifest") -> SingularTestNode:
542
+ if unique_id not in manifest.nodes:
543
+ raise dbt_common.exceptions.DbtInternalError(
544
+ f"Singular test {unique_id} found in cache but not found in manifest"
545
+ )
546
+ node = manifest.nodes[unique_id]
547
+ assert isinstance(node, SingularTestNode)
548
+ return node
549
+
550
+
551
+ def _packages_to_search(
552
+ current_project: str,
553
+ node_package: str,
554
+ target_package: Optional[str] = None,
555
+ ) -> List[Optional[str]]:
556
+ if target_package is not None:
557
+ return [target_package]
558
+ elif current_project == node_package:
559
+ return [current_project, None]
560
+ else:
561
+ return [current_project, node_package, None]
562
+
563
+
564
+ def _sort_values(dct):
565
+ """Given a dictionary, sort each value. This makes output deterministic,
566
+ which helps for tests.
567
+ """
568
+ return {k: sorted(v) for k, v in dct.items()}
569
+
570
+
571
+ def build_node_edges(nodes: List[ManifestNode]):
572
+ """Build the forward and backward edges on the given list of ManifestNodes
573
+ and return them as two separate dictionaries, each mapping unique IDs to
574
+ lists of edges.
575
+ """
576
+ backward_edges: Dict[str, List[str]] = {}
577
+ # pre-populate the forward edge dict for simplicity
578
+ forward_edges: Dict[str, List[str]] = {n.unique_id: [] for n in nodes}
579
+ for node in nodes:
580
+ backward_edges[node.unique_id] = node.depends_on_nodes[:]
581
+ for unique_id in backward_edges[node.unique_id]:
582
+ if unique_id in forward_edges.keys():
583
+ forward_edges[unique_id].append(node.unique_id)
584
+ return _sort_values(forward_edges), _sort_values(backward_edges)
585
+
586
+
587
+ # Build a map of children of macros and generic tests
588
+ def build_macro_edges(nodes: List[Any]):
589
+ forward_edges: Dict[str, List[str]] = {
590
+ n.unique_id: [] for n in nodes if n.unique_id.startswith("macro") or n.depends_on_macros
591
+ }
592
+ for node in nodes:
593
+ for unique_id in node.depends_on_macros:
594
+ if unique_id in forward_edges.keys():
595
+ forward_edges[unique_id].append(node.unique_id)
596
+ return _sort_values(forward_edges)
597
+
598
+
599
+ def _deepcopy(value):
600
+ return value.from_dict(value.to_dict(omit_none=True))
601
+
602
+
603
+ class Locality(enum.IntEnum):
604
+ Core = 1
605
+ Imported = 2
606
+ Root = 3
607
+
608
+
609
+ @dataclass
610
+ class MacroCandidate:
611
+ locality: Locality
612
+ macro: Macro
613
+
614
+ def __eq__(self, other: object) -> bool:
615
+ if not isinstance(other, MacroCandidate):
616
+ return NotImplemented
617
+ return self.locality == other.locality
618
+
619
+ def __lt__(self, other: object) -> bool:
620
+ if not isinstance(other, MacroCandidate):
621
+ return NotImplemented
622
+ if self.locality < other.locality:
623
+ return True
624
+ if self.locality > other.locality:
625
+ return False
626
+ return False
627
+
628
+
629
+ @dataclass
630
+ class MaterializationCandidate(MacroCandidate):
631
+ # specificity describes where in the inheritance chain this materialization candidate is
632
+ # a specificity of 0 means a materialization defined by the current adapter
633
+ # the highest the specificity describes a default materialization. the value itself depends on
634
+ # how many adapters there are in the inheritance chain
635
+ specificity: int
636
+
637
+ @classmethod
638
+ def from_macro(cls, candidate: MacroCandidate, specificity: int) -> "MaterializationCandidate":
639
+ return cls(
640
+ locality=candidate.locality,
641
+ macro=candidate.macro,
642
+ specificity=specificity,
643
+ )
644
+
645
+ def __eq__(self, other: object) -> bool:
646
+ if not isinstance(other, MaterializationCandidate):
647
+ return NotImplemented
648
+ equal = self.specificity == other.specificity and self.locality == other.locality
649
+ if equal:
650
+ raise DuplicateMaterializationNameError(self.macro, other)
651
+
652
+ return equal
653
+
654
+ def __lt__(self, other: object) -> bool:
655
+ if not isinstance(other, MaterializationCandidate):
656
+ return NotImplemented
657
+ if self.specificity > other.specificity:
658
+ return True
659
+ if self.specificity < other.specificity:
660
+ return False
661
+ if self.locality < other.locality:
662
+ return True
663
+ if self.locality > other.locality:
664
+ return False
665
+ return False
666
+
667
+
668
+ M = TypeVar("M", bound=MacroCandidate)
669
+
670
+
671
+ class CandidateList(List[M]):
672
+ def last_candidate(
673
+ self, valid_localities: Optional[List[Locality]] = None
674
+ ) -> Optional[MacroCandidate]:
675
+ """
676
+ Obtain the last (highest precedence) MacroCandidate from the CandidateList of any locality in valid_localities.
677
+ If valid_localities is not specified, return the last MacroCandidate of any locality.
678
+ """
679
+ if not self:
680
+ return None
681
+ self.sort()
682
+
683
+ if valid_localities is None:
684
+ return self[-1]
685
+
686
+ for candidate in reversed(self):
687
+ if candidate.locality in valid_localities:
688
+ return candidate
689
+
690
+ return None
691
+
692
+ def last(self) -> Optional[Macro]:
693
+ last_candidate = self.last_candidate()
694
+ return last_candidate.macro if last_candidate is not None else None
695
+
696
+
697
+ def _get_locality(macro: Macro, root_project_name: str, internal_packages: Set[str]) -> Locality:
698
+ if macro.package_name == root_project_name:
699
+ return Locality.Root
700
+ elif macro.package_name in internal_packages:
701
+ return Locality.Core
702
+ else:
703
+ return Locality.Imported
704
+
705
+
706
+ class Searchable(Protocol):
707
+ resource_type: NodeType
708
+ package_name: str
709
+
710
+ @property
711
+ def search_name(self) -> str:
712
+ raise NotImplementedError("search_name not implemented")
713
+
714
+
715
+ D = TypeVar("D")
716
+
717
+
718
+ @dataclass
719
+ class Disabled(Generic[D]):
720
+ target: D
721
+
722
+
723
+ MaybeFunctionNode = Optional[Union[FunctionNode, Disabled[FunctionNode]]]
724
+
725
+
726
+ MaybeMetricNode = Optional[Union[Metric, Disabled[Metric]]]
727
+
728
+
729
+ MaybeSavedQueryNode = Optional[Union[SavedQuery, Disabled[SavedQuery]]]
730
+
731
+
732
+ MaybeDocumentation = Optional[Documentation]
733
+
734
+
735
+ MaybeParsedSource = Optional[
736
+ Union[
737
+ SourceDefinition,
738
+ Disabled[SourceDefinition],
739
+ ]
740
+ ]
741
+
742
+
743
+ MaybeNonSource = Optional[Union[ManifestNode, Disabled[ManifestNode]]]
744
+
745
+
746
+ T = TypeVar("T", bound=GraphMemberNode)
747
+
748
+
749
+ # This contains macro methods that are in both the Manifest
750
+ # and the MacroManifest
751
+ class MacroMethods:
752
+ # Just to make mypy happy. There must be a better way.
753
+ def __init__(self):
754
+ self.macros = []
755
+ self.metadata = {}
756
+ self._macros_by_name = {}
757
+ self._macros_by_package = {}
758
+
759
+ def find_macro_candidate_by_name(
760
+ self, name: str, root_project_name: str, package: Optional[str]
761
+ ) -> Optional[MacroCandidate]:
762
+ """Find a MacroCandidate in the graph by its name and package name, or None for
763
+ any package. The root project name is used to determine priority:
764
+ - locally defined macros come first
765
+ - then imported macros
766
+ - then macros defined in the root project
767
+ """
768
+ filter: Optional[Callable[[MacroCandidate], bool]] = None
769
+ if package is not None:
770
+
771
+ def filter(candidate: MacroCandidate) -> bool:
772
+ return package == candidate.macro.package_name
773
+
774
+ candidates: CandidateList = self._find_macros_by_name(
775
+ name=name,
776
+ root_project_name=root_project_name,
777
+ filter=filter,
778
+ )
779
+
780
+ return candidates.last_candidate()
781
+
782
+ def find_macro_by_name(
783
+ self, name: str, root_project_name: str, package: Optional[str]
784
+ ) -> Optional[Macro]:
785
+ macro_candidate = self.find_macro_candidate_by_name(
786
+ name=name, root_project_name=root_project_name, package=package
787
+ )
788
+ return macro_candidate.macro if macro_candidate else None
789
+
790
+ def find_generate_macro_by_name(
791
+ self, component: str, root_project_name: str, imported_package: Optional[str] = None
792
+ ) -> Optional[Macro]:
793
+ """
794
+ The default `generate_X_name` macros are similar to regular ones, but only
795
+ includes imported packages when searching for a package.
796
+ - if package is not provided:
797
+ - if there is a `generate_{component}_name` macro in the root
798
+ project, return it
799
+ - return the `generate_{component}_name` macro from the 'dbt'
800
+ internal project
801
+ - if package is provided
802
+ - return the `generate_{component}_name` macro from the imported
803
+ package, if one exists
804
+ """
805
+
806
+ def filter(candidate: MacroCandidate) -> bool:
807
+ if imported_package:
808
+ return (
809
+ candidate.locality == Locality.Imported
810
+ and imported_package == candidate.macro.package_name
811
+ )
812
+ else:
813
+ return candidate.locality != Locality.Imported
814
+
815
+ candidates: CandidateList = self._find_macros_by_name(
816
+ name=f"generate_{component}_name",
817
+ root_project_name=root_project_name,
818
+ filter=filter,
819
+ )
820
+
821
+ return candidates.last()
822
+
823
+ def _find_macros_by_name(
824
+ self,
825
+ name: str,
826
+ root_project_name: str,
827
+ filter: Optional[Callable[[MacroCandidate], bool]] = None,
828
+ ) -> CandidateList:
829
+ """Find macros by their name."""
830
+ candidates: CandidateList = CandidateList()
831
+
832
+ macros_by_name = self.get_macros_by_name()
833
+ if name not in macros_by_name:
834
+ return candidates
835
+
836
+ packages = set(get_adapter_package_names(self.metadata.adapter_type))
837
+ for macro in macros_by_name[name]:
838
+ candidate = MacroCandidate(
839
+ locality=_get_locality(macro, root_project_name, packages),
840
+ macro=macro,
841
+ )
842
+ if filter is None or filter(candidate):
843
+ candidates.append(candidate)
844
+
845
+ return candidates
846
+
847
+ def get_macros_by_name(self) -> Dict[str, List[Macro]]:
848
+ if self._macros_by_name is None:
849
+ # The by-name mapping doesn't exist yet (perhaps because the manifest
850
+ # was deserialized), so we build it.
851
+ self._macros_by_name = self._build_macros_by_name(self.macros)
852
+
853
+ return self._macros_by_name
854
+
855
+ @staticmethod
856
+ def _build_macros_by_name(macros: Mapping[str, Macro]) -> Dict[str, List[Macro]]:
857
+ # Convert a macro dictionary keyed on unique id to a flattened version
858
+ # keyed on macro name for faster lookup by name. Since macro names are
859
+ # not necessarily unique, the dict value is a list.
860
+ macros_by_name: Dict[str, List[Macro]] = {}
861
+ for macro in macros.values():
862
+ if macro.name not in macros_by_name:
863
+ macros_by_name[macro.name] = []
864
+
865
+ macros_by_name[macro.name].append(macro)
866
+
867
+ return macros_by_name
868
+
869
+ def get_macros_by_package(self) -> Dict[str, Dict[str, Macro]]:
870
+ if self._macros_by_package is None:
871
+ # The by-package mapping doesn't exist yet (perhaps because the manifest
872
+ # was deserialized), so we build it.
873
+ self._macros_by_package = self._build_macros_by_package(self.macros)
874
+
875
+ return self._macros_by_package
876
+
877
+ @staticmethod
878
+ def _build_macros_by_package(macros: Mapping[str, Macro]) -> Dict[str, Dict[str, Macro]]:
879
+ # Convert a macro dictionary keyed on unique id to a flattened version
880
+ # keyed on package name for faster lookup by name.
881
+ macros_by_package: Dict[str, Dict[str, Macro]] = {}
882
+ for macro in macros.values():
883
+ if macro.package_name not in macros_by_package:
884
+ macros_by_package[macro.package_name] = {}
885
+ macros_by_name = macros_by_package[macro.package_name]
886
+ macros_by_name[macro.name] = macro
887
+
888
+ return macros_by_package
889
+
890
+
891
+ @dataclass
892
+ class ParsingInfo:
893
+ static_analysis_parsed_path_count: int = 0
894
+ static_analysis_path_count: int = 0
895
+
896
+
897
+ @dataclass
898
+ class ManifestStateCheck(dbtClassMixin):
899
+ vars_hash: FileHash = field(default_factory=FileHash.empty)
900
+ project_env_vars_hash: FileHash = field(default_factory=FileHash.empty)
901
+ profile_env_vars_hash: FileHash = field(default_factory=FileHash.empty)
902
+ profile_hash: FileHash = field(default_factory=FileHash.empty)
903
+ project_hashes: MutableMapping[str, FileHash] = field(default_factory=dict)
904
+
905
+
906
+ NodeClassT = TypeVar("NodeClassT", bound="BaseNode")
907
+ ResourceClassT = TypeVar("ResourceClassT", bound="BaseResource")
908
+
909
+
910
+ @dataclass
911
+ class Manifest(MacroMethods, dbtClassMixin):
912
+ """The manifest for the full graph, after parsing and during compilation."""
913
+
914
+ # These attributes are both positional and by keyword. If an attribute
915
+ # is added it must all be added in the __reduce_ex__ method in the
916
+ # args tuple in the right position.
917
+ nodes: MutableMapping[str, ManifestNode] = field(default_factory=dict)
918
+ sources: MutableMapping[str, SourceDefinition] = field(default_factory=dict)
919
+ macros: MutableMapping[str, Macro] = field(default_factory=dict)
920
+ docs: MutableMapping[str, Documentation] = field(default_factory=dict)
921
+ exposures: MutableMapping[str, Exposure] = field(default_factory=dict)
922
+ functions: MutableMapping[str, FunctionNode] = field(default_factory=dict)
923
+ metrics: MutableMapping[str, Metric] = field(default_factory=dict)
924
+ groups: MutableMapping[str, Group] = field(default_factory=dict)
925
+ selectors: MutableMapping[str, Any] = field(default_factory=dict)
926
+ files: MutableMapping[str, AnySourceFile] = field(default_factory=dict)
927
+ metadata: ManifestMetadata = field(default_factory=ManifestMetadata)
928
+ flat_graph: Dict[str, Any] = field(default_factory=dict)
929
+ state_check: ManifestStateCheck = field(default_factory=ManifestStateCheck)
930
+ source_patches: MutableMapping[SourceKey, SourcePatch] = field(default_factory=dict)
931
+ disabled: MutableMapping[str, List[GraphMemberNode]] = field(default_factory=dict)
932
+ env_vars: MutableMapping[str, str] = field(default_factory=dict)
933
+ semantic_models: MutableMapping[str, SemanticModel] = field(default_factory=dict)
934
+ unit_tests: MutableMapping[str, UnitTestDefinition] = field(default_factory=dict)
935
+ saved_queries: MutableMapping[str, SavedQuery] = field(default_factory=dict)
936
+ fixtures: MutableMapping[str, UnitTestFileFixture] = field(default_factory=dict)
937
+
938
+ _doc_lookup: Optional[DocLookup] = field(
939
+ default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
940
+ )
941
+ _source_lookup: Optional[SourceLookup] = field(
942
+ default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
943
+ )
944
+ _ref_lookup: Optional[RefableLookup] = field(
945
+ default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
946
+ )
947
+ _metric_lookup: Optional[MetricLookup] = field(
948
+ default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
949
+ )
950
+ _saved_query_lookup: Optional[SavedQueryLookup] = field(
951
+ default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
952
+ )
953
+ _semantic_model_by_measure_lookup: Optional[SemanticModelByMeasureLookup] = field(
954
+ default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
955
+ )
956
+ _disabled_lookup: Optional[DisabledLookup] = field(
957
+ default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
958
+ )
959
+ _analysis_lookup: Optional[AnalysisLookup] = field(
960
+ default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
961
+ )
962
+ _singular_test_lookup: Optional[SingularTestLookup] = field(
963
+ default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
964
+ )
965
+ _function_lookup: Optional[FunctionLookup] = field(
966
+ default=None, metadata={"serialize": lambda x: None, "deserialize": lambda x: None}
967
+ )
968
+ _parsing_info: ParsingInfo = field(
969
+ default_factory=ParsingInfo,
970
+ metadata={"serialize": lambda x: None, "deserialize": lambda x: None},
971
+ )
972
+ _lock: Lock = field(
973
+ default_factory=get_mp_context().Lock,
974
+ metadata={"serialize": lambda x: None, "deserialize": lambda x: None},
975
+ )
976
+ _macros_by_name: Optional[Dict[str, List[Macro]]] = field(
977
+ default=None,
978
+ metadata={"serialize": lambda x: None, "deserialize": lambda x: None},
979
+ )
980
+ _macros_by_package: Optional[Dict[str, Dict[str, Macro]]] = field(
981
+ default=None,
982
+ metadata={"serialize": lambda x: None, "deserialize": lambda x: None},
983
+ )
984
+
985
+ def __pre_serialize__(self, context: Optional[Dict] = None):
986
+ # serialization won't work with anything except an empty source_patches because
987
+ # tuple keys are not supported, so ensure it's empty
988
+ self.source_patches = {}
989
+ return self
990
+
991
+ @classmethod
992
+ def __post_deserialize__(cls, obj):
993
+ obj._lock = get_mp_context().Lock()
994
+ return obj
995
+
996
+ def build_flat_graph(self):
997
+ """This attribute is used in context.common by each node, so we want to
998
+ only build it once and avoid any concurrency issues around it.
999
+ Make sure you don't call this until you're done with building your
1000
+ manifest!
1001
+ """
1002
+ self.flat_graph = {
1003
+ "exposures": {k: v.to_dict(omit_none=False) for k, v in self.exposures.items()},
1004
+ "functions": {k: v.to_dict(omit_none=False) for k, v in self.functions.items()},
1005
+ "groups": {k: v.to_dict(omit_none=False) for k, v in self.groups.items()},
1006
+ "metrics": {k: v.to_dict(omit_none=False) for k, v in self.metrics.items()},
1007
+ "nodes": {k: v.to_dict(omit_none=False) for k, v in self.nodes.items()},
1008
+ "sources": {k: v.to_dict(omit_none=False) for k, v in self.sources.items()},
1009
+ "semantic_models": {
1010
+ k: v.to_dict(omit_none=False) for k, v in self.semantic_models.items()
1011
+ },
1012
+ "saved_queries": {
1013
+ k: v.to_dict(omit_none=False) for k, v in self.saved_queries.items()
1014
+ },
1015
+ }
1016
+
1017
+ def build_disabled_by_file_id(self):
1018
+ disabled_by_file_id = {}
1019
+ for node_list in self.disabled.values():
1020
+ for node in node_list:
1021
+ disabled_by_file_id[node.file_id] = node
1022
+ return disabled_by_file_id
1023
+
1024
+ def _get_parent_adapter_types(self, adapter_type: str) -> List[str]:
1025
+ # This is duplicated logic from core/dbt/context/providers.py
1026
+ # Ideally this would instead be incorporating actual dispatch logic
1027
+ from dbt.adapters.factory import get_adapter_type_names
1028
+
1029
+ # order matters for dispatch:
1030
+ # 1. current adapter
1031
+ # 2. any parent adapters (dependencies)
1032
+ # 3. 'default'
1033
+ return get_adapter_type_names(adapter_type) + ["default"]
1034
+
1035
+ def _materialization_candidates_for(
1036
+ self,
1037
+ project_name: str,
1038
+ materialization_name: str,
1039
+ adapter_type: str,
1040
+ specificity: int,
1041
+ ) -> CandidateList:
1042
+ full_name = dbt_common.utils.get_materialization_macro_name(
1043
+ materialization_name=materialization_name,
1044
+ adapter_type=adapter_type,
1045
+ with_prefix=False,
1046
+ )
1047
+ return CandidateList(
1048
+ MaterializationCandidate.from_macro(m, specificity)
1049
+ for m in self._find_macros_by_name(full_name, project_name)
1050
+ )
1051
+
1052
+ def find_materialization_macro_by_name(
1053
+ self, project_name: str, materialization_name: str, adapter_type: str
1054
+ ) -> Optional[Macro]:
1055
+ candidates: CandidateList = CandidateList(
1056
+ chain.from_iterable(
1057
+ self._materialization_candidates_for(
1058
+ project_name=project_name,
1059
+ materialization_name=materialization_name,
1060
+ adapter_type=atype,
1061
+ specificity=specificity, # where in the inheritance chain this candidate is
1062
+ )
1063
+ for specificity, atype in enumerate(self._get_parent_adapter_types(adapter_type))
1064
+ )
1065
+ )
1066
+ core_candidates = [
1067
+ candidate for candidate in candidates if candidate.locality == Locality.Core
1068
+ ]
1069
+
1070
+ materialization_candidate = candidates.last_candidate()
1071
+ # If an imported materialization macro was found that also had a core candidate, fire a deprecation
1072
+ if (
1073
+ materialization_candidate is not None
1074
+ and materialization_candidate.locality == Locality.Imported
1075
+ and core_candidates
1076
+ ):
1077
+ # preserve legacy behaviour - allow materialization override
1078
+ if (
1079
+ get_flags().require_explicit_package_overrides_for_builtin_materializations
1080
+ is False
1081
+ ):
1082
+ deprecations.warn(
1083
+ "package-materialization-override",
1084
+ package_name=materialization_candidate.macro.package_name,
1085
+ materialization_name=materialization_name,
1086
+ )
1087
+ else:
1088
+ materialization_candidate = candidates.last_candidate(
1089
+ valid_localities=[Locality.Core, Locality.Root]
1090
+ )
1091
+
1092
+ return materialization_candidate.macro if materialization_candidate else None
1093
+
1094
+ def get_resource_fqns(self) -> Mapping[str, PathSet]:
1095
+ resource_fqns: Dict[str, Set[Tuple[str, ...]]] = {}
1096
+ all_resources = chain(
1097
+ self.exposures.values(),
1098
+ self.functions.values(),
1099
+ self.nodes.values(),
1100
+ self.sources.values(),
1101
+ self.metrics.values(),
1102
+ self.semantic_models.values(),
1103
+ self.saved_queries.values(),
1104
+ self.unit_tests.values(),
1105
+ )
1106
+ for resource in all_resources:
1107
+ resource_type_plural = resource.resource_type.pluralize()
1108
+ if resource_type_plural not in resource_fqns:
1109
+ resource_fqns[resource_type_plural] = set()
1110
+ resource_fqns[resource_type_plural].add(tuple(resource.fqn))
1111
+ return resource_fqns
1112
+
1113
+ def get_used_schemas(self, resource_types=None):
1114
+ return frozenset(
1115
+ {
1116
+ (node.database, node.schema)
1117
+ for node in chain(self.nodes.values(), self.sources.values())
1118
+ if not resource_types or node.resource_type in resource_types
1119
+ }
1120
+ )
1121
+
1122
+ def get_used_databases(self):
1123
+ return frozenset(x.database for x in chain(self.nodes.values(), self.sources.values()))
1124
+
1125
+ def deepcopy(self):
1126
+ copy = Manifest(
1127
+ nodes={k: _deepcopy(v) for k, v in self.nodes.items()},
1128
+ sources={k: _deepcopy(v) for k, v in self.sources.items()},
1129
+ macros={k: _deepcopy(v) for k, v in self.macros.items()},
1130
+ docs={k: _deepcopy(v) for k, v in self.docs.items()},
1131
+ exposures={k: _deepcopy(v) for k, v in self.exposures.items()},
1132
+ functions={k: _deepcopy(v) for k, v in self.functions.items()},
1133
+ metrics={k: _deepcopy(v) for k, v in self.metrics.items()},
1134
+ groups={k: _deepcopy(v) for k, v in self.groups.items()},
1135
+ selectors={k: _deepcopy(v) for k, v in self.selectors.items()},
1136
+ metadata=self.metadata,
1137
+ disabled={k: _deepcopy(v) for k, v in self.disabled.items()},
1138
+ files={k: _deepcopy(v) for k, v in self.files.items()},
1139
+ state_check=_deepcopy(self.state_check),
1140
+ semantic_models={k: _deepcopy(v) for k, v in self.semantic_models.items()},
1141
+ unit_tests={k: _deepcopy(v) for k, v in self.unit_tests.items()},
1142
+ saved_queries={k: _deepcopy(v) for k, v in self.saved_queries.items()},
1143
+ )
1144
+ copy.build_flat_graph()
1145
+ return copy
1146
+
1147
+ def build_parent_and_child_maps(self):
1148
+ edge_members = list(
1149
+ chain(
1150
+ self.nodes.values(),
1151
+ self.sources.values(),
1152
+ self.exposures.values(),
1153
+ self.functions.values(),
1154
+ self.metrics.values(),
1155
+ self.semantic_models.values(),
1156
+ self.saved_queries.values(),
1157
+ self.unit_tests.values(),
1158
+ )
1159
+ )
1160
+ forward_edges, backward_edges = build_node_edges(edge_members)
1161
+ self.child_map = forward_edges
1162
+ self.parent_map = backward_edges
1163
+
1164
+ def build_macro_child_map(self):
1165
+ edge_members = list(
1166
+ chain(
1167
+ self.nodes.values(),
1168
+ self.macros.values(),
1169
+ )
1170
+ )
1171
+ forward_edges = build_macro_edges(edge_members)
1172
+ return forward_edges
1173
+
1174
+ def build_group_map(self):
1175
+ groupable_nodes = list(
1176
+ chain(
1177
+ self.nodes.values(),
1178
+ self.saved_queries.values(),
1179
+ self.semantic_models.values(),
1180
+ self.metrics.values(),
1181
+ )
1182
+ )
1183
+ group_map = {group.name: [] for group in self.groups.values()}
1184
+ for node in groupable_nodes:
1185
+ if node.group is not None:
1186
+ # group updates are not included with state:modified and
1187
+ # by ignoring the groups that aren't in the group map we
1188
+ # can avoid hitting errors for groups that are not getting
1189
+ # updated. This is a hack but any groups that are not
1190
+ # valid will be caught in
1191
+ # parser.manifest.ManifestLoader.check_valid_group_config_node
1192
+ if node.group in group_map:
1193
+ group_map[node.group].append(node.unique_id)
1194
+ self.group_map = group_map
1195
+
1196
+ def fill_tracking_metadata(self):
1197
+ self.metadata.user_id = tracking.active_user.id if tracking.active_user else None
1198
+ self.metadata.send_anonymous_usage_stats = get_flags().SEND_ANONYMOUS_USAGE_STATS
1199
+
1200
+ @classmethod
1201
+ def from_writable_manifest(cls, writable_manifest: WritableManifest) -> "Manifest":
1202
+ manifest = Manifest(
1203
+ nodes=cls._map_resources_to_map_nodes(writable_manifest.nodes),
1204
+ disabled=cls._map_list_resources_to_map_list_nodes(writable_manifest.disabled),
1205
+ unit_tests=cls._map_resources_to_map_nodes(writable_manifest.unit_tests),
1206
+ sources=cls._map_resources_to_map_nodes(writable_manifest.sources),
1207
+ macros=cls._map_resources_to_map_nodes(writable_manifest.macros),
1208
+ docs=cls._map_resources_to_map_nodes(writable_manifest.docs),
1209
+ exposures=cls._map_resources_to_map_nodes(writable_manifest.exposures),
1210
+ functions=cls._map_resources_to_map_nodes(writable_manifest.functions),
1211
+ metrics=cls._map_resources_to_map_nodes(writable_manifest.metrics),
1212
+ groups=cls._map_resources_to_map_nodes(writable_manifest.groups),
1213
+ semantic_models=cls._map_resources_to_map_nodes(writable_manifest.semantic_models),
1214
+ saved_queries=cls._map_resources_to_map_nodes(writable_manifest.saved_queries),
1215
+ selectors={
1216
+ selector_id: selector
1217
+ for selector_id, selector in writable_manifest.selectors.items()
1218
+ },
1219
+ metadata=writable_manifest.metadata,
1220
+ )
1221
+
1222
+ return manifest
1223
+
1224
+ def _map_nodes_to_map_resources(cls, nodes_map: MutableMapping[str, NodeClassT]):
1225
+ return {node_id: node.to_resource() for node_id, node in nodes_map.items()}
1226
+
1227
+ def _map_list_nodes_to_map_list_resources(
1228
+ cls, nodes_map: MutableMapping[str, List[NodeClassT]]
1229
+ ):
1230
+ return {
1231
+ node_id: [node.to_resource() for node in node_list]
1232
+ for node_id, node_list in nodes_map.items()
1233
+ }
1234
+
1235
+ @classmethod
1236
+ def _map_resources_to_map_nodes(cls, resources_map: Mapping[str, ResourceClassT]):
1237
+ return {
1238
+ node_id: RESOURCE_CLASS_TO_NODE_CLASS[type(resource)].from_resource(resource)
1239
+ for node_id, resource in resources_map.items()
1240
+ }
1241
+
1242
+ @classmethod
1243
+ def _map_list_resources_to_map_list_nodes(
1244
+ cls, resources_map: Optional[Mapping[str, List[ResourceClassT]]]
1245
+ ):
1246
+ if resources_map is None:
1247
+ return {}
1248
+
1249
+ return {
1250
+ node_id: [
1251
+ RESOURCE_CLASS_TO_NODE_CLASS[type(resource)].from_resource(resource)
1252
+ for resource in resource_list
1253
+ ]
1254
+ for node_id, resource_list in resources_map.items()
1255
+ }
1256
+
1257
+ def writable_manifest(self) -> "WritableManifest":
1258
+ self.build_parent_and_child_maps()
1259
+ self.build_group_map()
1260
+ self.fill_tracking_metadata()
1261
+
1262
+ return WritableManifest(
1263
+ nodes=self._map_nodes_to_map_resources(self.nodes),
1264
+ sources=self._map_nodes_to_map_resources(self.sources),
1265
+ macros=self._map_nodes_to_map_resources(self.macros),
1266
+ docs=self._map_nodes_to_map_resources(self.docs),
1267
+ exposures=self._map_nodes_to_map_resources(self.exposures),
1268
+ functions=self._map_nodes_to_map_resources(self.functions),
1269
+ metrics=self._map_nodes_to_map_resources(self.metrics),
1270
+ groups=self._map_nodes_to_map_resources(self.groups),
1271
+ selectors=self.selectors,
1272
+ metadata=self.metadata,
1273
+ disabled=self._map_list_nodes_to_map_list_resources(self.disabled),
1274
+ child_map=self.child_map,
1275
+ parent_map=self.parent_map,
1276
+ group_map=self.group_map,
1277
+ semantic_models=self._map_nodes_to_map_resources(self.semantic_models),
1278
+ unit_tests=self._map_nodes_to_map_resources(self.unit_tests),
1279
+ saved_queries=self._map_nodes_to_map_resources(self.saved_queries),
1280
+ )
1281
+
1282
+ def write(self, path):
1283
+ writable = self.writable_manifest()
1284
+ writable.write(path)
1285
+ fire_event(ArtifactWritten(artifact_type=writable.__class__.__name__, artifact_path=path))
1286
+
1287
+ # Called in dbt.compilation.Linker.write_graph and
1288
+ # dbt.graph.queue.get and ._include_in_cost
1289
+ def expect(self, unique_id: str) -> GraphMemberNode:
1290
+ if unique_id in self.nodes:
1291
+ return self.nodes[unique_id]
1292
+ elif unique_id in self.sources:
1293
+ return self.sources[unique_id]
1294
+ elif unique_id in self.exposures:
1295
+ return self.exposures[unique_id]
1296
+ elif unique_id in self.functions:
1297
+ return self.functions[unique_id]
1298
+ elif unique_id in self.metrics:
1299
+ return self.metrics[unique_id]
1300
+ elif unique_id in self.semantic_models:
1301
+ return self.semantic_models[unique_id]
1302
+ elif unique_id in self.unit_tests:
1303
+ return self.unit_tests[unique_id]
1304
+ elif unique_id in self.saved_queries:
1305
+ return self.saved_queries[unique_id]
1306
+ else:
1307
+ # something terrible has happened
1308
+ raise dbt_common.exceptions.DbtInternalError(
1309
+ "Expected node {} not found in manifest".format(unique_id)
1310
+ )
1311
+
1312
+ @property
1313
+ def doc_lookup(self) -> DocLookup:
1314
+ if self._doc_lookup is None:
1315
+ self._doc_lookup = DocLookup(self)
1316
+ return self._doc_lookup
1317
+
1318
+ def rebuild_doc_lookup(self):
1319
+ self._doc_lookup = DocLookup(self)
1320
+
1321
+ @property
1322
+ def source_lookup(self) -> SourceLookup:
1323
+ if self._source_lookup is None:
1324
+ self._source_lookup = SourceLookup(self)
1325
+ return self._source_lookup
1326
+
1327
+ def rebuild_source_lookup(self):
1328
+ self._source_lookup = SourceLookup(self)
1329
+
1330
+ @property
1331
+ def ref_lookup(self) -> RefableLookup:
1332
+ if self._ref_lookup is None:
1333
+ self._ref_lookup = RefableLookup(self)
1334
+ return self._ref_lookup
1335
+
1336
+ @property
1337
+ def metric_lookup(self) -> MetricLookup:
1338
+ if self._metric_lookup is None:
1339
+ self._metric_lookup = MetricLookup(self)
1340
+ return self._metric_lookup
1341
+
1342
+ @property
1343
+ def saved_query_lookup(self) -> SavedQueryLookup:
1344
+ """Retuns a SavedQueryLookup, instantiating it first if necessary."""
1345
+ if self._saved_query_lookup is None:
1346
+ self._saved_query_lookup = SavedQueryLookup(self)
1347
+ return self._saved_query_lookup
1348
+
1349
+ @property
1350
+ def semantic_model_by_measure_lookup(self) -> SemanticModelByMeasureLookup:
1351
+ """Gets (and creates if necessary) the lookup utility for getting SemanticModels by measures"""
1352
+ if self._semantic_model_by_measure_lookup is None:
1353
+ self._semantic_model_by_measure_lookup = SemanticModelByMeasureLookup(self)
1354
+ return self._semantic_model_by_measure_lookup
1355
+
1356
+ def rebuild_ref_lookup(self):
1357
+ self._ref_lookup = RefableLookup(self)
1358
+
1359
+ @property
1360
+ def disabled_lookup(self) -> DisabledLookup:
1361
+ if self._disabled_lookup is None:
1362
+ self._disabled_lookup = DisabledLookup(self)
1363
+ return self._disabled_lookup
1364
+
1365
+ def rebuild_disabled_lookup(self):
1366
+ self._disabled_lookup = DisabledLookup(self)
1367
+
1368
+ @property
1369
+ def analysis_lookup(self) -> AnalysisLookup:
1370
+ if self._analysis_lookup is None:
1371
+ self._analysis_lookup = AnalysisLookup(self)
1372
+ return self._analysis_lookup
1373
+
1374
+ @property
1375
+ def singular_test_lookup(self) -> SingularTestLookup:
1376
+ if self._singular_test_lookup is None:
1377
+ self._singular_test_lookup = SingularTestLookup(self)
1378
+ return self._singular_test_lookup
1379
+
1380
+ @property
1381
+ def function_lookup(self) -> FunctionLookup:
1382
+ if self._function_lookup is None:
1383
+ self._function_lookup = FunctionLookup(self)
1384
+ return self._function_lookup
1385
+
1386
+ @property
1387
+ def external_node_unique_ids(self):
1388
+ return [node.unique_id for node in self.nodes.values() if node.is_external_node]
1389
+
1390
+ # Called by dbt.parser.manifest._process_refs & ManifestLoader.check_for_model_deprecations
1391
+ def resolve_ref(
1392
+ self,
1393
+ source_node: GraphMemberNode,
1394
+ target_model_name: str,
1395
+ target_model_package: Optional[str],
1396
+ target_model_version: Optional[NodeVersion],
1397
+ current_project: str,
1398
+ node_package: str,
1399
+ ) -> MaybeNonSource:
1400
+
1401
+ node: Optional[ManifestNode] = None
1402
+ disabled: Optional[List[ManifestNode]] = None
1403
+
1404
+ candidates = _packages_to_search(current_project, node_package, target_model_package)
1405
+ for pkg in candidates:
1406
+ node = self.ref_lookup.find(
1407
+ target_model_name, pkg, target_model_version, self, source_node
1408
+ )
1409
+
1410
+ if node is not None and hasattr(node, "config") and node.config.enabled:
1411
+ return node
1412
+
1413
+ # it's possible that the node is disabled
1414
+ if disabled is None:
1415
+ disabled = self.disabled_lookup.find(
1416
+ target_model_name,
1417
+ pkg,
1418
+ version=target_model_version,
1419
+ resource_types=REFABLE_NODE_TYPES,
1420
+ )
1421
+
1422
+ if disabled:
1423
+ return Disabled(disabled[0])
1424
+ return None
1425
+
1426
+ # Called by dbt.parser.manifest._resolve_sources_for_exposure
1427
+ # and dbt.parser.manifest._process_source_for_node
1428
+ def resolve_source(
1429
+ self,
1430
+ target_source_name: str,
1431
+ target_table_name: str,
1432
+ current_project: str,
1433
+ node_package: str,
1434
+ ) -> MaybeParsedSource:
1435
+ search_name = f"{target_source_name}.{target_table_name}"
1436
+ candidates = _packages_to_search(current_project, node_package)
1437
+
1438
+ source: Optional[SourceDefinition] = None
1439
+ disabled: Optional[List[SourceDefinition]] = None
1440
+
1441
+ for pkg in candidates:
1442
+ source = self.source_lookup.find(search_name, pkg, self)
1443
+ if source is not None and source.config.enabled:
1444
+ return source
1445
+
1446
+ if disabled is None:
1447
+ disabled = self.disabled_lookup.find(
1448
+ f"{target_source_name}.{target_table_name}", pkg
1449
+ )
1450
+
1451
+ if disabled:
1452
+ return Disabled(disabled[0])
1453
+ return None
1454
+
1455
+ def resolve_function(
1456
+ self,
1457
+ target_function_name: str,
1458
+ target_function_package: Optional[str],
1459
+ current_project: str,
1460
+ node_package: str,
1461
+ ) -> MaybeFunctionNode:
1462
+ package_candidates = _packages_to_search(
1463
+ current_project, node_package, target_function_package
1464
+ )
1465
+ disabled: Optional[List[FunctionNode]] = None
1466
+ for package in package_candidates:
1467
+ function = self.function_lookup.find(target_function_name, package, self)
1468
+ if function is not None and function.config.enabled:
1469
+ return function
1470
+
1471
+ # it's possible that the function is disabled
1472
+ if disabled is None:
1473
+ disabled = self.disabled_lookup.find(target_function_name, package)
1474
+ if disabled:
1475
+ return Disabled(disabled[0])
1476
+ return None
1477
+
1478
+ def resolve_metric(
1479
+ self,
1480
+ target_metric_name: str,
1481
+ target_metric_package: Optional[str],
1482
+ current_project: str,
1483
+ node_package: str,
1484
+ ) -> MaybeMetricNode:
1485
+
1486
+ metric: Optional[Metric] = None
1487
+ disabled: Optional[List[Metric]] = None
1488
+
1489
+ candidates = _packages_to_search(current_project, node_package, target_metric_package)
1490
+ for pkg in candidates:
1491
+ metric = self.metric_lookup.find(target_metric_name, pkg, self)
1492
+
1493
+ if metric is not None and metric.config.enabled:
1494
+ return metric
1495
+
1496
+ # it's possible that the node is disabled
1497
+ if disabled is None:
1498
+ disabled = self.disabled_lookup.find(f"{target_metric_name}", pkg)
1499
+ if disabled:
1500
+ return Disabled(disabled[0])
1501
+ return None
1502
+
1503
+ def resolve_saved_query(
1504
+ self,
1505
+ target_saved_query_name: str,
1506
+ target_saved_query_package: Optional[str],
1507
+ current_project: str,
1508
+ node_package: str,
1509
+ ) -> MaybeSavedQueryNode:
1510
+ """Tries to find the SavedQuery by name within the available project and packages.
1511
+
1512
+ Will return the first enabled SavedQuery matching the name found while iterating over
1513
+ the scoped packages. If no enabled SavedQuery node match is found, returns the last
1514
+ disabled SavedQuery node. Otherwise it returns None.
1515
+ """
1516
+ disabled: Optional[List[SavedQuery]] = None
1517
+ candidates = _packages_to_search(current_project, node_package, target_saved_query_package)
1518
+ for pkg in candidates:
1519
+ saved_query = self.saved_query_lookup.find(target_saved_query_name, pkg, self)
1520
+
1521
+ if saved_query is not None and saved_query.config.enabled:
1522
+ return saved_query
1523
+
1524
+ # it's possible that the node is disabled
1525
+ if disabled is None:
1526
+ disabled = self.disabled_lookup.find(f"{target_saved_query_name}", pkg)
1527
+ if disabled:
1528
+ return Disabled(disabled[0])
1529
+
1530
+ return None
1531
+
1532
+ def resolve_semantic_model_for_measure(
1533
+ self,
1534
+ target_measure_name: str,
1535
+ current_project: str,
1536
+ node_package: str,
1537
+ target_package: Optional[str] = None,
1538
+ ) -> Optional[SemanticModel]:
1539
+ """Tries to find the SemanticModel that a measure belongs to"""
1540
+ candidates = _packages_to_search(current_project, node_package, target_package)
1541
+
1542
+ for pkg in candidates:
1543
+ semantic_model = self.semantic_model_by_measure_lookup.find(
1544
+ target_measure_name, pkg, self
1545
+ )
1546
+ # need to return it even if it's disabled so know it's not fully missing
1547
+ if semantic_model is not None:
1548
+ return semantic_model
1549
+
1550
+ return None
1551
+
1552
+ # Called by DocsRuntimeContext.doc
1553
+ def resolve_doc(
1554
+ self,
1555
+ name: str,
1556
+ package: Optional[str],
1557
+ current_project: str,
1558
+ node_package: str,
1559
+ ) -> Optional[Documentation]:
1560
+ """Resolve the given documentation. This follows the same algorithm as
1561
+ resolve_ref except the is_enabled checks are unnecessary as docs are
1562
+ always enabled.
1563
+ """
1564
+ candidates = _packages_to_search(current_project, node_package, package)
1565
+
1566
+ for pkg in candidates:
1567
+ result = self.doc_lookup.find(name, pkg, self)
1568
+ if result is not None:
1569
+ return result
1570
+ return None
1571
+
1572
+ def is_invalid_private_ref(
1573
+ self, node: GraphMemberNode, target_model: MaybeNonSource, dependencies: Optional[Mapping]
1574
+ ) -> bool:
1575
+ dependencies = dependencies or {}
1576
+ if not isinstance(target_model, ModelNode):
1577
+ return False
1578
+
1579
+ is_private_ref = (
1580
+ target_model.access == AccessType.Private
1581
+ # don't raise this reference error for ad hoc 'preview' queries
1582
+ and node.resource_type != NodeType.SqlOperation
1583
+ and node.resource_type != NodeType.RPCCall # TODO: rm
1584
+ )
1585
+ target_dependency = dependencies.get(target_model.package_name)
1586
+ restrict_package_access = target_dependency.restrict_access if target_dependency else False
1587
+
1588
+ # TODO: SemanticModel and SourceDefinition do not have group, and so should not be able to make _any_ private ref.
1589
+ return is_private_ref and (
1590
+ not hasattr(node, "group")
1591
+ or not node.group
1592
+ # Invalid reference because group does not match
1593
+ or node.group != target_model.group
1594
+ # Or, invalid because these are different namespaces (project/package) and restrict-access is enforced
1595
+ or (node.package_name != target_model.package_name and restrict_package_access)
1596
+ )
1597
+
1598
+ def is_invalid_protected_ref(
1599
+ self, node: GraphMemberNode, target_model: MaybeNonSource, dependencies: Optional[Mapping]
1600
+ ) -> bool:
1601
+ dependencies = dependencies or {}
1602
+ if not isinstance(target_model, ModelNode):
1603
+ return False
1604
+
1605
+ is_protected_ref = (
1606
+ target_model.access == AccessType.Protected
1607
+ # don't raise this reference error for ad hoc 'preview' queries
1608
+ and node.resource_type != NodeType.SqlOperation
1609
+ and node.resource_type != NodeType.RPCCall # TODO: rm
1610
+ )
1611
+ target_dependency = dependencies.get(target_model.package_name)
1612
+ restrict_package_access = target_dependency.restrict_access if target_dependency else False
1613
+
1614
+ return is_protected_ref and (
1615
+ node.package_name != target_model.package_name and restrict_package_access
1616
+ )
1617
+
1618
+ # Called in GraphRunnableTask.before_run, RunTask.before_run, CloneTask.before_run
1619
+ def merge_from_artifact(self, other: "Manifest") -> None:
1620
+ """Update this manifest by adding the 'defer_relation' attribute to all nodes
1621
+ with a counterpart in the stateful manifest used for deferral.
1622
+
1623
+ Only non-ephemeral refable nodes are examined.
1624
+ """
1625
+ refables = set(REFABLE_NODE_TYPES)
1626
+ for unique_id, node in other.nodes.items():
1627
+ current = self.nodes.get(unique_id)
1628
+ if current and node.resource_type in refables and not node.is_ephemeral:
1629
+ assert isinstance(node.config, NodeConfig) # this makes mypy happy
1630
+ defer_relation = DeferRelation(
1631
+ database=node.database,
1632
+ schema=node.schema,
1633
+ alias=node.alias,
1634
+ relation_name=node.relation_name,
1635
+ resource_type=node.resource_type,
1636
+ name=node.name,
1637
+ description=node.description,
1638
+ compiled_code=(node.compiled_code if not isinstance(node, SeedNode) else None),
1639
+ meta=node.meta,
1640
+ tags=node.tags,
1641
+ config=node.config,
1642
+ )
1643
+ self.nodes[unique_id] = replace(current, defer_relation=defer_relation)
1644
+
1645
+ # Rebuild the flat_graph, which powers the 'graph' context variable
1646
+ self.build_flat_graph()
1647
+
1648
+ # Methods that were formerly in ParseResult
1649
+ def add_macro(self, source_file: SourceFile, macro: Macro):
1650
+ if macro.unique_id in self.macros:
1651
+ # detect that the macro exists and emit an error
1652
+ raise DuplicateMacroInPackageError(macro=macro, macro_mapping=self.macros)
1653
+
1654
+ self.macros[macro.unique_id] = macro
1655
+
1656
+ if self._macros_by_name is None:
1657
+ self._macros_by_name = self._build_macros_by_name(self.macros)
1658
+
1659
+ if macro.name not in self._macros_by_name:
1660
+ self._macros_by_name[macro.name] = []
1661
+
1662
+ self._macros_by_name[macro.name].append(macro)
1663
+
1664
+ if self._macros_by_package is None:
1665
+ self._macros_by_package = self._build_macros_by_package(self.macros)
1666
+
1667
+ if macro.package_name not in self._macros_by_package:
1668
+ self._macros_by_package[macro.package_name] = {}
1669
+
1670
+ self._macros_by_package[macro.package_name][macro.name] = macro
1671
+
1672
+ source_file.macros.append(macro.unique_id)
1673
+
1674
+ def has_file(self, source_file: SourceFile) -> bool:
1675
+ key = source_file.file_id
1676
+ if key is None:
1677
+ return False
1678
+ if key not in self.files:
1679
+ return False
1680
+ my_checksum = self.files[key].checksum
1681
+ return my_checksum == source_file.checksum
1682
+
1683
+ def add_source(self, source_file: SchemaSourceFile, source: UnpatchedSourceDefinition):
1684
+ # sources can't be overwritten!
1685
+ _check_duplicates(source, self.sources)
1686
+ self.sources[source.unique_id] = source # type: ignore
1687
+ source_file.sources.append(source.unique_id)
1688
+
1689
+ def add_node_nofile(self, node: ManifestNode):
1690
+ # nodes can't be overwritten!
1691
+ _check_duplicates(node, self.nodes)
1692
+ self.nodes[node.unique_id] = node
1693
+
1694
+ def add_node(self, source_file: AnySourceFile, node: ManifestNode, test_from=None):
1695
+ self.add_node_nofile(node)
1696
+ if isinstance(source_file, SchemaSourceFile):
1697
+ if isinstance(node, GenericTestNode):
1698
+ assert test_from
1699
+ source_file.add_test(node.unique_id, test_from)
1700
+ elif isinstance(node, Metric):
1701
+ source_file.metrics.append(node.unique_id)
1702
+ elif isinstance(node, Exposure):
1703
+ source_file.exposures.append(node.unique_id)
1704
+ elif isinstance(node, Group):
1705
+ source_file.groups.append(node.unique_id)
1706
+ elif isinstance(node, SnapshotNode):
1707
+ source_file.snapshots.append(node.unique_id)
1708
+ elif isinstance(source_file, FixtureSourceFile):
1709
+ pass
1710
+ else:
1711
+ source_file.nodes.append(node.unique_id)
1712
+
1713
+ def add_exposure(self, source_file: SchemaSourceFile, exposure: Exposure):
1714
+ _check_duplicates(exposure, self.exposures)
1715
+ self.exposures[exposure.unique_id] = exposure
1716
+ source_file.exposures.append(exposure.unique_id)
1717
+
1718
+ def add_function(self, function: FunctionNode):
1719
+ _check_duplicates(function, self.functions)
1720
+ self.functions[function.unique_id] = function
1721
+
1722
+ def add_metric(
1723
+ self, source_file: SchemaSourceFile, metric: Metric, generated_from: Optional[str] = None
1724
+ ):
1725
+ _check_duplicates(metric, self.metrics)
1726
+ self.metrics[metric.unique_id] = metric
1727
+ if not generated_from:
1728
+ source_file.metrics.append(metric.unique_id)
1729
+ else:
1730
+ source_file.add_metrics_from_measures(generated_from, metric.unique_id)
1731
+
1732
+ def add_group(self, source_file: SchemaSourceFile, group: Group):
1733
+ _check_duplicates(group, self.groups)
1734
+ self.groups[group.unique_id] = group
1735
+ source_file.groups.append(group.unique_id)
1736
+
1737
+ def add_disabled_nofile(self, node: GraphMemberNode):
1738
+ # There can be multiple disabled nodes for the same unique_id
1739
+ if node.unique_id in self.disabled:
1740
+ self.disabled[node.unique_id].append(node)
1741
+ else:
1742
+ self.disabled[node.unique_id] = [node]
1743
+
1744
+ def add_disabled(self, source_file: AnySourceFile, node: GraphMemberNode, test_from=None):
1745
+ self.add_disabled_nofile(node)
1746
+ if isinstance(source_file, SchemaSourceFile):
1747
+ if isinstance(node, GenericTestNode):
1748
+ assert test_from
1749
+ source_file.add_test(node.unique_id, test_from)
1750
+ if isinstance(node, Metric):
1751
+ source_file.metrics.append(node.unique_id)
1752
+ if isinstance(node, SavedQuery):
1753
+ source_file.saved_queries.append(node.unique_id)
1754
+ if isinstance(node, SemanticModel):
1755
+ source_file.semantic_models.append(node.unique_id)
1756
+ if isinstance(node, Exposure):
1757
+ source_file.exposures.append(node.unique_id)
1758
+ if isinstance(node, FunctionNode):
1759
+ source_file.functions.append(node.unique_id)
1760
+ if isinstance(node, UnitTestDefinition):
1761
+ source_file.unit_tests.append(node.unique_id)
1762
+ elif isinstance(source_file, FixtureSourceFile):
1763
+ pass
1764
+ else:
1765
+ source_file.nodes.append(node.unique_id)
1766
+
1767
+ def add_doc(self, source_file: SourceFile, doc: Documentation):
1768
+ _check_duplicates(doc, self.docs)
1769
+ self.docs[doc.unique_id] = doc
1770
+ source_file.docs.append(doc.unique_id)
1771
+
1772
+ def add_semantic_model(self, source_file: SchemaSourceFile, semantic_model: SemanticModel):
1773
+ _check_duplicates(semantic_model, self.semantic_models)
1774
+ self.semantic_models[semantic_model.unique_id] = semantic_model
1775
+ source_file.semantic_models.append(semantic_model.unique_id)
1776
+
1777
+ def add_unit_test(self, source_file: SchemaSourceFile, unit_test: UnitTestDefinition):
1778
+ if unit_test.unique_id in self.unit_tests:
1779
+ raise DuplicateResourceNameError(unit_test, self.unit_tests[unit_test.unique_id])
1780
+ self.unit_tests[unit_test.unique_id] = unit_test
1781
+ source_file.unit_tests.append(unit_test.unique_id)
1782
+
1783
+ def add_fixture(self, source_file: FixtureSourceFile, fixture: UnitTestFileFixture):
1784
+ if fixture.unique_id in self.fixtures:
1785
+ raise DuplicateResourceNameError(fixture, self.fixtures[fixture.unique_id])
1786
+ self.fixtures[fixture.unique_id] = fixture
1787
+ source_file.fixture = fixture.unique_id
1788
+
1789
+ def add_saved_query(self, source_file: SchemaSourceFile, saved_query: SavedQuery) -> None:
1790
+ _check_duplicates(saved_query, self.saved_queries)
1791
+ self.saved_queries[saved_query.unique_id] = saved_query
1792
+ source_file.saved_queries.append(saved_query.unique_id)
1793
+
1794
+ # end of methods formerly in ParseResult
1795
+
1796
+ def find_node_from_ref_or_source(
1797
+ self, expression: str
1798
+ ) -> Optional[Union[ModelNode, SourceDefinition]]:
1799
+ ref_or_source = statically_parse_ref_or_source(expression)
1800
+
1801
+ node = None
1802
+ if isinstance(ref_or_source, RefArgs):
1803
+ node = self.ref_lookup.find(
1804
+ ref_or_source.name, ref_or_source.package, ref_or_source.version, self
1805
+ )
1806
+ else:
1807
+ source_name, source_table_name = ref_or_source[0], ref_or_source[1]
1808
+ node = self.source_lookup.find(f"{source_name}.{source_table_name}", None, self)
1809
+
1810
+ return node
1811
+
1812
+ # Provide support for copy.deepcopy() - we just need to avoid the lock!
1813
+ # pickle and deepcopy use this. It returns a callable object used to
1814
+ # create the initial version of the object and a tuple of arguments
1815
+ # for the object, i.e. the Manifest.
1816
+ # The order of the arguments must match the order of the attributes
1817
+ # in the Manifest class declaration, because they are used as
1818
+ # positional arguments to construct a Manifest.
1819
+ def __reduce_ex__(self, protocol):
1820
+ args = (
1821
+ self.nodes,
1822
+ self.sources,
1823
+ self.macros,
1824
+ self.docs,
1825
+ self.exposures,
1826
+ self.functions,
1827
+ self.metrics,
1828
+ self.groups,
1829
+ self.selectors,
1830
+ self.files,
1831
+ self.metadata,
1832
+ self.flat_graph,
1833
+ self.state_check,
1834
+ self.source_patches,
1835
+ self.disabled,
1836
+ self.env_vars,
1837
+ self.semantic_models,
1838
+ self.unit_tests,
1839
+ self.saved_queries,
1840
+ self._doc_lookup,
1841
+ self._source_lookup,
1842
+ self._ref_lookup,
1843
+ self._metric_lookup,
1844
+ self._semantic_model_by_measure_lookup,
1845
+ self._disabled_lookup,
1846
+ self._analysis_lookup,
1847
+ self._singular_test_lookup,
1848
+ )
1849
+ return self.__class__, args
1850
+
1851
+ def _microbatch_macro_is_core(self, project_name: str) -> bool:
1852
+ microbatch_is_core = False
1853
+ candidate = self.find_macro_candidate_by_name(
1854
+ name="get_incremental_microbatch_sql", root_project_name=project_name, package=None
1855
+ )
1856
+
1857
+ # We want to check for "Core", because "Core" basically means "builtin"
1858
+ if candidate is not None and candidate.locality == Locality.Core:
1859
+ microbatch_is_core = True
1860
+
1861
+ return microbatch_is_core
1862
+
1863
+ def use_microbatch_batches(self, project_name: str) -> bool:
1864
+ return (
1865
+ get_flags().require_batched_execution_for_custom_microbatch_strategy
1866
+ or self._microbatch_macro_is_core(project_name=project_name)
1867
+ )
1868
+
1869
+
1870
+ class MacroManifest(MacroMethods):
1871
+ def __init__(self, macros) -> None:
1872
+ self.macros = macros
1873
+ self.metadata = ManifestMetadata(
1874
+ user_id=tracking.active_user.id if tracking.active_user else None,
1875
+ send_anonymous_usage_stats=(
1876
+ get_flags().SEND_ANONYMOUS_USAGE_STATS if tracking.active_user else None
1877
+ ),
1878
+ )
1879
+ # This is returned by the 'graph' context property
1880
+ # in the ProviderContext class.
1881
+ self.flat_graph: Dict[str, Any] = {}
1882
+ self._macros_by_name: Optional[Dict[str, List[Macro]]] = None
1883
+ self._macros_by_package: Optional[Dict[str, Dict[str, Macro]]] = None
1884
+
1885
+
1886
+ AnyManifest = Union[Manifest, MacroManifest]
1887
+
1888
+
1889
+ def _check_duplicates(value: BaseNode, src: Mapping[str, BaseNode]):
1890
+ if value.unique_id in src:
1891
+ raise DuplicateResourceNameError(value, src[value.unique_id])
1892
+
1893
+
1894
+ K_T = TypeVar("K_T")
1895
+ V_T = TypeVar("V_T")
1896
+
1897
+
1898
+ def _expect_value(key: K_T, src: Mapping[K_T, V_T], old_file: SourceFile, name: str) -> V_T:
1899
+ if key not in src:
1900
+ raise CompilationError(
1901
+ 'Expected to find "{}" in cached "result.{}" based '
1902
+ "on cached file information: {}!".format(key, name, old_file)
1903
+ )
1904
+ return src[key]