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.
- dvt/__init__.py +7 -0
- dvt/_pydantic_shim.py +26 -0
- dvt/adapters/__init__.py +16 -0
- dvt/adapters/multi_adapter_manager.py +268 -0
- dvt/artifacts/__init__.py +0 -0
- dvt/artifacts/exceptions/__init__.py +1 -0
- dvt/artifacts/exceptions/schemas.py +31 -0
- dvt/artifacts/resources/__init__.py +116 -0
- dvt/artifacts/resources/base.py +68 -0
- dvt/artifacts/resources/types.py +93 -0
- dvt/artifacts/resources/v1/analysis.py +10 -0
- dvt/artifacts/resources/v1/catalog.py +23 -0
- dvt/artifacts/resources/v1/components.py +275 -0
- dvt/artifacts/resources/v1/config.py +282 -0
- dvt/artifacts/resources/v1/documentation.py +11 -0
- dvt/artifacts/resources/v1/exposure.py +52 -0
- dvt/artifacts/resources/v1/function.py +53 -0
- dvt/artifacts/resources/v1/generic_test.py +32 -0
- dvt/artifacts/resources/v1/group.py +22 -0
- dvt/artifacts/resources/v1/hook.py +11 -0
- dvt/artifacts/resources/v1/macro.py +30 -0
- dvt/artifacts/resources/v1/metric.py +173 -0
- dvt/artifacts/resources/v1/model.py +146 -0
- dvt/artifacts/resources/v1/owner.py +10 -0
- dvt/artifacts/resources/v1/saved_query.py +112 -0
- dvt/artifacts/resources/v1/seed.py +42 -0
- dvt/artifacts/resources/v1/semantic_layer_components.py +72 -0
- dvt/artifacts/resources/v1/semantic_model.py +315 -0
- dvt/artifacts/resources/v1/singular_test.py +14 -0
- dvt/artifacts/resources/v1/snapshot.py +92 -0
- dvt/artifacts/resources/v1/source_definition.py +85 -0
- dvt/artifacts/resources/v1/sql_operation.py +10 -0
- dvt/artifacts/resources/v1/unit_test_definition.py +78 -0
- dvt/artifacts/schemas/__init__.py +0 -0
- dvt/artifacts/schemas/base.py +191 -0
- dvt/artifacts/schemas/batch_results.py +24 -0
- dvt/artifacts/schemas/catalog/__init__.py +12 -0
- dvt/artifacts/schemas/catalog/v1/__init__.py +0 -0
- dvt/artifacts/schemas/catalog/v1/catalog.py +60 -0
- dvt/artifacts/schemas/freshness/__init__.py +1 -0
- dvt/artifacts/schemas/freshness/v3/__init__.py +0 -0
- dvt/artifacts/schemas/freshness/v3/freshness.py +159 -0
- dvt/artifacts/schemas/manifest/__init__.py +2 -0
- dvt/artifacts/schemas/manifest/v12/__init__.py +0 -0
- dvt/artifacts/schemas/manifest/v12/manifest.py +212 -0
- dvt/artifacts/schemas/results.py +148 -0
- dvt/artifacts/schemas/run/__init__.py +2 -0
- dvt/artifacts/schemas/run/v5/__init__.py +0 -0
- dvt/artifacts/schemas/run/v5/run.py +184 -0
- dvt/artifacts/schemas/upgrades/__init__.py +4 -0
- dvt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
- dvt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
- dvt/artifacts/utils/validation.py +153 -0
- dvt/cli/__init__.py +1 -0
- dvt/cli/context.py +16 -0
- dvt/cli/exceptions.py +56 -0
- dvt/cli/flags.py +558 -0
- dvt/cli/main.py +971 -0
- dvt/cli/option_types.py +121 -0
- dvt/cli/options.py +79 -0
- dvt/cli/params.py +803 -0
- dvt/cli/requires.py +478 -0
- dvt/cli/resolvers.py +32 -0
- dvt/cli/types.py +40 -0
- dvt/clients/__init__.py +0 -0
- dvt/clients/checked_load.py +82 -0
- dvt/clients/git.py +164 -0
- dvt/clients/jinja.py +206 -0
- dvt/clients/jinja_static.py +245 -0
- dvt/clients/registry.py +192 -0
- dvt/clients/yaml_helper.py +68 -0
- dvt/compilation.py +833 -0
- dvt/compute/__init__.py +26 -0
- dvt/compute/base.py +288 -0
- dvt/compute/engines/__init__.py +13 -0
- dvt/compute/engines/duckdb_engine.py +368 -0
- dvt/compute/engines/spark_engine.py +273 -0
- dvt/compute/query_analyzer.py +212 -0
- dvt/compute/router.py +483 -0
- dvt/config/__init__.py +4 -0
- dvt/config/catalogs.py +95 -0
- dvt/config/compute_config.py +406 -0
- dvt/config/profile.py +411 -0
- dvt/config/profiles_v2.py +464 -0
- dvt/config/project.py +893 -0
- dvt/config/renderer.py +232 -0
- dvt/config/runtime.py +491 -0
- dvt/config/selectors.py +209 -0
- dvt/config/utils.py +78 -0
- dvt/connectors/.gitignore +6 -0
- dvt/connectors/README.md +306 -0
- dvt/connectors/catalog.yml +217 -0
- dvt/connectors/download_connectors.py +300 -0
- dvt/constants.py +29 -0
- dvt/context/__init__.py +0 -0
- dvt/context/base.py +746 -0
- dvt/context/configured.py +136 -0
- dvt/context/context_config.py +350 -0
- dvt/context/docs.py +82 -0
- dvt/context/exceptions_jinja.py +179 -0
- dvt/context/macro_resolver.py +195 -0
- dvt/context/macros.py +171 -0
- dvt/context/manifest.py +73 -0
- dvt/context/providers.py +2198 -0
- dvt/context/query_header.py +14 -0
- dvt/context/secret.py +59 -0
- dvt/context/target.py +74 -0
- dvt/contracts/__init__.py +0 -0
- dvt/contracts/files.py +413 -0
- dvt/contracts/graph/__init__.py +0 -0
- dvt/contracts/graph/manifest.py +1904 -0
- dvt/contracts/graph/metrics.py +98 -0
- dvt/contracts/graph/model_config.py +71 -0
- dvt/contracts/graph/node_args.py +42 -0
- dvt/contracts/graph/nodes.py +1806 -0
- dvt/contracts/graph/semantic_manifest.py +233 -0
- dvt/contracts/graph/unparsed.py +812 -0
- dvt/contracts/project.py +417 -0
- dvt/contracts/results.py +53 -0
- dvt/contracts/selection.py +23 -0
- dvt/contracts/sql.py +86 -0
- dvt/contracts/state.py +69 -0
- dvt/contracts/util.py +46 -0
- dvt/deprecations.py +347 -0
- dvt/deps/__init__.py +0 -0
- dvt/deps/base.py +153 -0
- dvt/deps/git.py +196 -0
- dvt/deps/local.py +80 -0
- dvt/deps/registry.py +131 -0
- dvt/deps/resolver.py +149 -0
- dvt/deps/tarball.py +121 -0
- dvt/docs/source/_ext/dbt_click.py +118 -0
- dvt/docs/source/conf.py +32 -0
- dvt/env_vars.py +64 -0
- dvt/event_time/event_time.py +40 -0
- dvt/event_time/sample_window.py +60 -0
- dvt/events/__init__.py +16 -0
- dvt/events/base_types.py +37 -0
- dvt/events/core_types_pb2.py +2 -0
- dvt/events/logging.py +109 -0
- dvt/events/types.py +2534 -0
- dvt/exceptions.py +1487 -0
- dvt/flags.py +89 -0
- dvt/graph/__init__.py +11 -0
- dvt/graph/cli.py +248 -0
- dvt/graph/graph.py +172 -0
- dvt/graph/queue.py +213 -0
- dvt/graph/selector.py +375 -0
- dvt/graph/selector_methods.py +976 -0
- dvt/graph/selector_spec.py +223 -0
- dvt/graph/thread_pool.py +18 -0
- dvt/hooks.py +21 -0
- dvt/include/README.md +49 -0
- dvt/include/__init__.py +3 -0
- dvt/include/global_project.py +4 -0
- dvt/include/starter_project/.gitignore +4 -0
- dvt/include/starter_project/README.md +15 -0
- dvt/include/starter_project/__init__.py +3 -0
- dvt/include/starter_project/analyses/.gitkeep +0 -0
- dvt/include/starter_project/dvt_project.yml +36 -0
- dvt/include/starter_project/macros/.gitkeep +0 -0
- dvt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
- dvt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
- dvt/include/starter_project/models/example/schema.yml +21 -0
- dvt/include/starter_project/seeds/.gitkeep +0 -0
- dvt/include/starter_project/snapshots/.gitkeep +0 -0
- dvt/include/starter_project/tests/.gitkeep +0 -0
- dvt/internal_deprecations.py +27 -0
- dvt/jsonschemas/__init__.py +3 -0
- dvt/jsonschemas/jsonschemas.py +309 -0
- dvt/jsonschemas/project/0.0.110.json +4717 -0
- dvt/jsonschemas/project/0.0.85.json +2015 -0
- dvt/jsonschemas/resources/0.0.110.json +2636 -0
- dvt/jsonschemas/resources/0.0.85.json +2536 -0
- dvt/jsonschemas/resources/latest.json +6773 -0
- dvt/links.py +4 -0
- dvt/materializations/__init__.py +0 -0
- dvt/materializations/incremental/__init__.py +0 -0
- dvt/materializations/incremental/microbatch.py +235 -0
- dvt/mp_context.py +8 -0
- dvt/node_types.py +37 -0
- dvt/parser/__init__.py +23 -0
- dvt/parser/analysis.py +21 -0
- dvt/parser/base.py +549 -0
- dvt/parser/common.py +267 -0
- dvt/parser/docs.py +52 -0
- dvt/parser/fixtures.py +51 -0
- dvt/parser/functions.py +30 -0
- dvt/parser/generic_test.py +100 -0
- dvt/parser/generic_test_builders.py +334 -0
- dvt/parser/hooks.py +119 -0
- dvt/parser/macros.py +137 -0
- dvt/parser/manifest.py +2204 -0
- dvt/parser/models.py +574 -0
- dvt/parser/partial.py +1179 -0
- dvt/parser/read_files.py +445 -0
- dvt/parser/schema_generic_tests.py +423 -0
- dvt/parser/schema_renderer.py +111 -0
- dvt/parser/schema_yaml_readers.py +936 -0
- dvt/parser/schemas.py +1467 -0
- dvt/parser/search.py +149 -0
- dvt/parser/seeds.py +28 -0
- dvt/parser/singular_test.py +20 -0
- dvt/parser/snapshots.py +44 -0
- dvt/parser/sources.py +557 -0
- dvt/parser/sql.py +63 -0
- dvt/parser/unit_tests.py +622 -0
- dvt/plugins/__init__.py +20 -0
- dvt/plugins/contracts.py +10 -0
- dvt/plugins/exceptions.py +2 -0
- dvt/plugins/manager.py +164 -0
- dvt/plugins/manifest.py +21 -0
- dvt/profiler.py +20 -0
- dvt/py.typed +1 -0
- dvt/runners/__init__.py +2 -0
- dvt/runners/exposure_runner.py +7 -0
- dvt/runners/no_op_runner.py +46 -0
- dvt/runners/saved_query_runner.py +7 -0
- dvt/selected_resources.py +8 -0
- dvt/task/__init__.py +0 -0
- dvt/task/base.py +504 -0
- dvt/task/build.py +197 -0
- dvt/task/clean.py +57 -0
- dvt/task/clone.py +162 -0
- dvt/task/compile.py +151 -0
- dvt/task/compute.py +366 -0
- dvt/task/debug.py +650 -0
- dvt/task/deps.py +280 -0
- dvt/task/docs/__init__.py +3 -0
- dvt/task/docs/generate.py +408 -0
- dvt/task/docs/index.html +250 -0
- dvt/task/docs/serve.py +28 -0
- dvt/task/freshness.py +323 -0
- dvt/task/function.py +122 -0
- dvt/task/group_lookup.py +46 -0
- dvt/task/init.py +374 -0
- dvt/task/list.py +237 -0
- dvt/task/printer.py +176 -0
- dvt/task/profiles.py +256 -0
- dvt/task/retry.py +175 -0
- dvt/task/run.py +1146 -0
- dvt/task/run_operation.py +142 -0
- dvt/task/runnable.py +802 -0
- dvt/task/seed.py +104 -0
- dvt/task/show.py +150 -0
- dvt/task/snapshot.py +57 -0
- dvt/task/sql.py +111 -0
- dvt/task/test.py +464 -0
- dvt/tests/fixtures/__init__.py +1 -0
- dvt/tests/fixtures/project.py +620 -0
- dvt/tests/util.py +651 -0
- dvt/tracking.py +529 -0
- dvt/utils/__init__.py +3 -0
- dvt/utils/artifact_upload.py +151 -0
- dvt/utils/utils.py +408 -0
- dvt/version.py +249 -0
- dvt_core-1.11.0b4.dist-info/METADATA +252 -0
- dvt_core-1.11.0b4.dist-info/RECORD +261 -0
- dvt_core-1.11.0b4.dist-info/WHEEL +5 -0
- dvt_core-1.11.0b4.dist-info/entry_points.txt +2 -0
- 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]
|