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