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,423 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
from typing import Any, Dict, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
from dvt.artifacts.resources import NodeVersion, RefArgs
|
|
7
|
+
from dvt.clients.jinja import add_rendered_test_kwargs, get_rendered
|
|
8
|
+
from dvt.context.configured import SchemaYamlVars, generate_schema_yml_context
|
|
9
|
+
from dvt.context.context_config import ContextConfig
|
|
10
|
+
from dvt.context.macro_resolver import MacroResolver
|
|
11
|
+
from dvt.context.providers import generate_test_context
|
|
12
|
+
from dvt.contracts.files import FileHash
|
|
13
|
+
from dvt.contracts.graph.nodes import (
|
|
14
|
+
GenericTestNode,
|
|
15
|
+
GraphMemberNode,
|
|
16
|
+
ManifestNode,
|
|
17
|
+
UnpatchedSourceDefinition,
|
|
18
|
+
)
|
|
19
|
+
from dvt.contracts.graph.unparsed import UnparsedColumn, UnparsedNodeUpdate
|
|
20
|
+
from dvt.exceptions import (
|
|
21
|
+
CompilationError,
|
|
22
|
+
ParsingError,
|
|
23
|
+
SchemaConfigError,
|
|
24
|
+
TestConfigError,
|
|
25
|
+
)
|
|
26
|
+
from dvt.node_types import NodeType
|
|
27
|
+
from dvt.parser.base import SimpleParser
|
|
28
|
+
from dvt.parser.common import (
|
|
29
|
+
GenericTestBlock,
|
|
30
|
+
Testable,
|
|
31
|
+
TestBlock,
|
|
32
|
+
TestDef,
|
|
33
|
+
VersionedTestBlock,
|
|
34
|
+
trimmed,
|
|
35
|
+
)
|
|
36
|
+
from dvt.parser.generic_test_builders import TestBuilder
|
|
37
|
+
from dvt.parser.search import FileBlock
|
|
38
|
+
from dvt.utils import get_pseudo_test_path, md5
|
|
39
|
+
|
|
40
|
+
from dbt.adapters.factory import get_adapter, get_adapter_package_names
|
|
41
|
+
from dbt_common.dataclass_schema import ValidationError
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# This parser handles the tests that are defined in "schema" (yaml) files, on models,
|
|
45
|
+
# sources, etc. The base generic test is handled by the GenericTestParser
|
|
46
|
+
class SchemaGenericTestParser(SimpleParser):
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
project,
|
|
50
|
+
manifest,
|
|
51
|
+
root_project,
|
|
52
|
+
) -> None:
|
|
53
|
+
super().__init__(project, manifest, root_project)
|
|
54
|
+
self.schema_yaml_vars = SchemaYamlVars()
|
|
55
|
+
self.render_ctx = generate_schema_yml_context(
|
|
56
|
+
self.root_project, self.project.project_name, self.schema_yaml_vars
|
|
57
|
+
)
|
|
58
|
+
internal_package_names = get_adapter_package_names(self.root_project.credentials.type)
|
|
59
|
+
self.macro_resolver = MacroResolver(
|
|
60
|
+
self.manifest.macros, self.root_project.project_name, internal_package_names
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def resource_type(self) -> NodeType:
|
|
65
|
+
return NodeType.Test
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def get_compiled_path(cls, block: FileBlock) -> str:
|
|
69
|
+
return block.path.relative_path
|
|
70
|
+
|
|
71
|
+
def parse_file(self, block: FileBlock, dct: Optional[Dict] = None) -> None:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
def parse_from_dict(self, dct, validate=True) -> GenericTestNode:
|
|
75
|
+
if validate:
|
|
76
|
+
GenericTestNode.validate(dct)
|
|
77
|
+
return GenericTestNode.from_dict(dct)
|
|
78
|
+
|
|
79
|
+
def parse_column_tests(
|
|
80
|
+
self, block: TestBlock, column: UnparsedColumn, version: Optional[NodeVersion]
|
|
81
|
+
) -> None:
|
|
82
|
+
if not column.data_tests:
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
for data_test in column.data_tests:
|
|
86
|
+
self.parse_test(block, data_test, column, version)
|
|
87
|
+
|
|
88
|
+
def create_test_node(
|
|
89
|
+
self,
|
|
90
|
+
target: Union[UnpatchedSourceDefinition, UnparsedNodeUpdate],
|
|
91
|
+
path: str,
|
|
92
|
+
config: ContextConfig,
|
|
93
|
+
tags: List[str],
|
|
94
|
+
fqn: List[str],
|
|
95
|
+
name: str,
|
|
96
|
+
raw_code: str,
|
|
97
|
+
test_metadata: Dict[str, Any],
|
|
98
|
+
file_key_name: str,
|
|
99
|
+
column_name: Optional[str],
|
|
100
|
+
description: str,
|
|
101
|
+
) -> GenericTestNode:
|
|
102
|
+
|
|
103
|
+
HASH_LENGTH = 10
|
|
104
|
+
|
|
105
|
+
# N.B: This function builds a hashable string from any given test_metadata dict.
|
|
106
|
+
# it's a bit fragile for general use (only supports str, int, float, List, Dict)
|
|
107
|
+
# but it gets the job done here without the overhead of complete ser(de).
|
|
108
|
+
def get_hashable_md(data: Union[str, int, float, List, Dict]) -> Union[str, List, Dict]:
|
|
109
|
+
if type(data) == dict:
|
|
110
|
+
return {k: get_hashable_md(data[k]) for k in sorted(data.keys())} # type: ignore
|
|
111
|
+
elif type(data) == list:
|
|
112
|
+
return [get_hashable_md(val) for val in data] # type: ignore
|
|
113
|
+
else:
|
|
114
|
+
return str(data)
|
|
115
|
+
|
|
116
|
+
hashable_metadata = repr(get_hashable_md(test_metadata))
|
|
117
|
+
hash_string = "".join([name, hashable_metadata])
|
|
118
|
+
test_hash = md5(hash_string)[-HASH_LENGTH:]
|
|
119
|
+
|
|
120
|
+
dct = {
|
|
121
|
+
"alias": name,
|
|
122
|
+
"schema": self.default_schema,
|
|
123
|
+
"database": self.default_database,
|
|
124
|
+
"fqn": fqn,
|
|
125
|
+
"name": name,
|
|
126
|
+
"resource_type": self.resource_type,
|
|
127
|
+
"tags": tags,
|
|
128
|
+
"path": path,
|
|
129
|
+
"original_file_path": target.original_file_path,
|
|
130
|
+
"package_name": self.project.project_name,
|
|
131
|
+
"raw_code": raw_code,
|
|
132
|
+
"language": "sql",
|
|
133
|
+
"unique_id": self.generate_unique_id(name, test_hash),
|
|
134
|
+
"config": self.config_dict(config),
|
|
135
|
+
"test_metadata": test_metadata,
|
|
136
|
+
"column_name": column_name,
|
|
137
|
+
"checksum": FileHash.empty().to_dict(omit_none=True),
|
|
138
|
+
"file_key_name": file_key_name,
|
|
139
|
+
"description": description,
|
|
140
|
+
}
|
|
141
|
+
try:
|
|
142
|
+
GenericTestNode.validate(dct)
|
|
143
|
+
return GenericTestNode.from_dict(dct)
|
|
144
|
+
except ValidationError as exc:
|
|
145
|
+
# this is a bit silly, but build an UnparsedNode just for error
|
|
146
|
+
# message reasons
|
|
147
|
+
node = self._create_error_node(
|
|
148
|
+
name=target.name,
|
|
149
|
+
path=path,
|
|
150
|
+
original_file_path=target.original_file_path,
|
|
151
|
+
raw_code=raw_code,
|
|
152
|
+
)
|
|
153
|
+
raise TestConfigError(exc, node)
|
|
154
|
+
|
|
155
|
+
# This is called directly in the SourcePatcher and by the "parse_node"
|
|
156
|
+
# command which is called by the SchemaParser.
|
|
157
|
+
def parse_generic_test(
|
|
158
|
+
self,
|
|
159
|
+
target: Testable,
|
|
160
|
+
data_test: Dict[str, Any],
|
|
161
|
+
tags: List[str],
|
|
162
|
+
column_name: Optional[str],
|
|
163
|
+
schema_file_id: str,
|
|
164
|
+
version: Optional[NodeVersion],
|
|
165
|
+
) -> GenericTestNode:
|
|
166
|
+
try:
|
|
167
|
+
builder = TestBuilder(
|
|
168
|
+
data_test=data_test,
|
|
169
|
+
target=target,
|
|
170
|
+
column_name=column_name,
|
|
171
|
+
version=version,
|
|
172
|
+
package_name=target.package_name,
|
|
173
|
+
render_ctx=self.render_ctx,
|
|
174
|
+
)
|
|
175
|
+
if self.schema_yaml_vars.env_vars:
|
|
176
|
+
self.store_env_vars(target, schema_file_id, self.schema_yaml_vars.env_vars)
|
|
177
|
+
self.schema_yaml_vars.env_vars = {}
|
|
178
|
+
|
|
179
|
+
except ParsingError as exc:
|
|
180
|
+
context = trimmed(str(target))
|
|
181
|
+
msg = "Invalid test config given in {}:\n\t{}\n\t@: {}".format(
|
|
182
|
+
target.original_file_path, exc.msg, context
|
|
183
|
+
)
|
|
184
|
+
raise ParsingError(msg) from exc
|
|
185
|
+
|
|
186
|
+
except CompilationError as exc:
|
|
187
|
+
context = trimmed(str(target))
|
|
188
|
+
msg = (
|
|
189
|
+
"Invalid generic test configuration given in "
|
|
190
|
+
f"{target.original_file_path}: \n{exc.msg}\n\t@: {context}"
|
|
191
|
+
)
|
|
192
|
+
raise CompilationError(msg) from exc
|
|
193
|
+
|
|
194
|
+
original_name = os.path.basename(target.original_file_path)
|
|
195
|
+
compiled_path = get_pseudo_test_path(builder.compiled_name, original_name)
|
|
196
|
+
|
|
197
|
+
# fqn is the relative path of the yaml file where this generic test is defined,
|
|
198
|
+
# minus the project-level directory and the file name itself
|
|
199
|
+
# TODO pass a consistent path object from both UnparsedNode and UnpatchedSourceDefinition
|
|
200
|
+
path = pathlib.Path(target.original_file_path)
|
|
201
|
+
relative_path = str(path.relative_to(*path.parts[:1]))
|
|
202
|
+
fqn = self.get_fqn(relative_path, builder.fqn_name)
|
|
203
|
+
|
|
204
|
+
# this is the ContextConfig that is used in render_update
|
|
205
|
+
config: ContextConfig = self.initial_config(fqn)
|
|
206
|
+
# Adding the builder's config to the ContextConfig
|
|
207
|
+
# is needed to ensure the config makes it to the pre_model hook which dbt-snowflake needs
|
|
208
|
+
config.add_config_call(builder.config)
|
|
209
|
+
# builder.args contains keyword args for the test macro,
|
|
210
|
+
# not configs which have been separated out in the builder.
|
|
211
|
+
# The keyword args are not completely rendered until compilation.
|
|
212
|
+
metadata = {
|
|
213
|
+
"namespace": builder.namespace,
|
|
214
|
+
"name": builder.name,
|
|
215
|
+
"kwargs": builder.args,
|
|
216
|
+
}
|
|
217
|
+
tags = sorted(set(itertools.chain(tags, builder.tags())))
|
|
218
|
+
|
|
219
|
+
if isinstance(target, UnpatchedSourceDefinition):
|
|
220
|
+
file_key_name = f"{target.source.yaml_key}.{target.source.name}"
|
|
221
|
+
else:
|
|
222
|
+
file_key_name = f"{target.yaml_key}.{target.name}"
|
|
223
|
+
|
|
224
|
+
node = self.create_test_node(
|
|
225
|
+
target=target,
|
|
226
|
+
path=compiled_path,
|
|
227
|
+
config=config,
|
|
228
|
+
fqn=fqn,
|
|
229
|
+
tags=tags,
|
|
230
|
+
name=builder.fqn_name,
|
|
231
|
+
raw_code=builder.build_raw_code(),
|
|
232
|
+
column_name=column_name,
|
|
233
|
+
test_metadata=metadata,
|
|
234
|
+
file_key_name=file_key_name,
|
|
235
|
+
description=builder.description,
|
|
236
|
+
)
|
|
237
|
+
self.render_test_update(node, config, builder, schema_file_id)
|
|
238
|
+
|
|
239
|
+
return node
|
|
240
|
+
|
|
241
|
+
def _lookup_attached_node(
|
|
242
|
+
self, target: Testable, version: Optional[NodeVersion]
|
|
243
|
+
) -> Optional[Union[ManifestNode, GraphMemberNode]]:
|
|
244
|
+
"""Look up attached node for Testable target nodes other than sources. Can be None if generic test attached to SQL node with no corresponding .sql file."""
|
|
245
|
+
attached_node = None # type: Optional[Union[ManifestNode, GraphMemberNode]]
|
|
246
|
+
if not isinstance(target, UnpatchedSourceDefinition):
|
|
247
|
+
attached_node_unique_id = self.manifest.ref_lookup.get_unique_id(
|
|
248
|
+
target.name, target.package_name, version
|
|
249
|
+
)
|
|
250
|
+
if attached_node_unique_id:
|
|
251
|
+
attached_node = self.manifest.nodes[attached_node_unique_id]
|
|
252
|
+
else:
|
|
253
|
+
disabled_node = self.manifest.disabled_lookup.find(
|
|
254
|
+
target.name, None
|
|
255
|
+
) or self.manifest.disabled_lookup.find(target.name.upper(), None)
|
|
256
|
+
if disabled_node:
|
|
257
|
+
attached_node = self.manifest.disabled[disabled_node[0].unique_id][0]
|
|
258
|
+
return attached_node
|
|
259
|
+
|
|
260
|
+
def store_env_vars(self, target, schema_file_id, env_vars):
|
|
261
|
+
self.manifest.env_vars.update(env_vars)
|
|
262
|
+
if schema_file_id in self.manifest.files:
|
|
263
|
+
schema_file = self.manifest.files[schema_file_id]
|
|
264
|
+
if isinstance(target, UnpatchedSourceDefinition):
|
|
265
|
+
search_name = target.source.name
|
|
266
|
+
yaml_key = target.source.yaml_key
|
|
267
|
+
if "." in search_name: # source file definitions
|
|
268
|
+
(search_name, _) = search_name.split(".")
|
|
269
|
+
else:
|
|
270
|
+
search_name = target.name
|
|
271
|
+
yaml_key = target.yaml_key
|
|
272
|
+
for var in env_vars.keys():
|
|
273
|
+
schema_file.add_env_var(var, yaml_key, search_name)
|
|
274
|
+
|
|
275
|
+
# This does special shortcut processing for the two
|
|
276
|
+
# most common internal macros, not_null and unique,
|
|
277
|
+
# which avoids the jinja rendering to resolve config
|
|
278
|
+
# and variables, etc, which might be in the macro.
|
|
279
|
+
# In the future we will look at generalizing this
|
|
280
|
+
# more to handle additional macros or to use static
|
|
281
|
+
# parsing to avoid jinja overhead.
|
|
282
|
+
def render_test_update(self, node, config, builder, schema_file_id):
|
|
283
|
+
macro_unique_id = self.macro_resolver.get_macro_id(
|
|
284
|
+
node.package_name, "test_" + builder.name
|
|
285
|
+
)
|
|
286
|
+
# Add the depends_on here so we can limit the macros added
|
|
287
|
+
# to the context in rendering processing
|
|
288
|
+
node.depends_on.add_macro(macro_unique_id)
|
|
289
|
+
if macro_unique_id in ["macro.dbt.test_not_null", "macro.dbt.test_unique"]:
|
|
290
|
+
config_call_dict = builder.config
|
|
291
|
+
config._config_call_dict = config_call_dict
|
|
292
|
+
# This sets the config from dbt_project
|
|
293
|
+
self.update_parsed_node_config(node, config)
|
|
294
|
+
# source node tests are processed at patch_source time
|
|
295
|
+
if isinstance(builder.target, UnpatchedSourceDefinition):
|
|
296
|
+
sources = [builder.target.fqn[-2], builder.target.fqn[-1]]
|
|
297
|
+
node.sources.append(sources)
|
|
298
|
+
else: # all other nodes
|
|
299
|
+
node.refs.append(RefArgs(name=builder.target.name, version=builder.version))
|
|
300
|
+
else:
|
|
301
|
+
try:
|
|
302
|
+
# make a base context that doesn't have the magic kwargs field
|
|
303
|
+
context = generate_test_context(
|
|
304
|
+
node,
|
|
305
|
+
self.root_project,
|
|
306
|
+
self.manifest,
|
|
307
|
+
config,
|
|
308
|
+
self.macro_resolver,
|
|
309
|
+
)
|
|
310
|
+
# update with rendered test kwargs (which collects any refs)
|
|
311
|
+
# Note: This does not actually update the kwargs with the rendered
|
|
312
|
+
# values. That happens in compilation.
|
|
313
|
+
add_rendered_test_kwargs(context, node, capture_macros=True)
|
|
314
|
+
# the parsed node is not rendered in the native context.
|
|
315
|
+
get_rendered(node.raw_code, context, node, capture_macros=True)
|
|
316
|
+
self.update_parsed_node_config(node, config)
|
|
317
|
+
# env_vars should have been updated in the context env_var method
|
|
318
|
+
except ValidationError as exc:
|
|
319
|
+
# we got a ValidationError - probably bad types in config()
|
|
320
|
+
raise SchemaConfigError(exc, node=node) from exc
|
|
321
|
+
|
|
322
|
+
# Set attached_node for generic test nodes, if available.
|
|
323
|
+
# Generic test node inherits attached node's group config value.
|
|
324
|
+
attached_node = self._lookup_attached_node(builder.target, builder.version)
|
|
325
|
+
if attached_node:
|
|
326
|
+
node.attached_node = attached_node.unique_id
|
|
327
|
+
node.group, node.group = attached_node.group, attached_node.group
|
|
328
|
+
|
|
329
|
+
def parse_node(self, block: GenericTestBlock) -> GenericTestNode:
|
|
330
|
+
"""In schema parsing, we rewrite most of the part of parse_node that
|
|
331
|
+
builds the initial node to be parsed, but rendering is basically the
|
|
332
|
+
same
|
|
333
|
+
"""
|
|
334
|
+
node = self.parse_generic_test(
|
|
335
|
+
target=block.target,
|
|
336
|
+
data_test=block.data_test,
|
|
337
|
+
tags=block.tags,
|
|
338
|
+
column_name=block.column_name,
|
|
339
|
+
schema_file_id=block.file.file_id,
|
|
340
|
+
version=block.version,
|
|
341
|
+
)
|
|
342
|
+
self.add_test_node(block, node)
|
|
343
|
+
return node
|
|
344
|
+
|
|
345
|
+
def add_test_node(self, block: GenericTestBlock, node: GenericTestNode):
|
|
346
|
+
test_from = {"key": block.target.yaml_key, "name": block.target.name}
|
|
347
|
+
if node.config.enabled:
|
|
348
|
+
self.manifest.add_node(block.file, node, test_from)
|
|
349
|
+
else:
|
|
350
|
+
self.manifest.add_disabled(block.file, node, test_from)
|
|
351
|
+
|
|
352
|
+
def render_with_context(
|
|
353
|
+
self,
|
|
354
|
+
node: GenericTestNode,
|
|
355
|
+
config: ContextConfig,
|
|
356
|
+
) -> None:
|
|
357
|
+
"""Given the parsed node and a ContextConfig to use during
|
|
358
|
+
parsing, collect all the refs that might be squirreled away in the test
|
|
359
|
+
arguments. This includes the implicit "model" argument.
|
|
360
|
+
"""
|
|
361
|
+
# make a base context that doesn't have the magic kwargs field
|
|
362
|
+
context = self._context_for(node, config)
|
|
363
|
+
# update it with the rendered test kwargs (which collects any refs)
|
|
364
|
+
add_rendered_test_kwargs(context, node, capture_macros=True)
|
|
365
|
+
|
|
366
|
+
# the parsed node is not rendered in the native context.
|
|
367
|
+
get_rendered(node.raw_code, context, node, capture_macros=True)
|
|
368
|
+
|
|
369
|
+
def parse_test(
|
|
370
|
+
self,
|
|
371
|
+
target_block: TestBlock,
|
|
372
|
+
data_test: TestDef,
|
|
373
|
+
column: Optional[UnparsedColumn],
|
|
374
|
+
version: Optional[NodeVersion],
|
|
375
|
+
) -> None:
|
|
376
|
+
if isinstance(data_test, str):
|
|
377
|
+
data_test = {data_test: {}}
|
|
378
|
+
|
|
379
|
+
if column is None:
|
|
380
|
+
column_name: Optional[str] = None
|
|
381
|
+
column_tags: List[str] = []
|
|
382
|
+
else:
|
|
383
|
+
column_name = column.name
|
|
384
|
+
should_quote = column.quote or (column.quote is None and target_block.quote_columns)
|
|
385
|
+
if should_quote:
|
|
386
|
+
column_name = get_adapter(self.root_project).quote(column_name)
|
|
387
|
+
|
|
388
|
+
column_config_tags = column.config.get("tags", [])
|
|
389
|
+
if isinstance(column_config_tags, str):
|
|
390
|
+
column_config_tags = [column_config_tags]
|
|
391
|
+
column_tags = list(set(column.tags + column_config_tags))
|
|
392
|
+
|
|
393
|
+
block = GenericTestBlock.from_test_block(
|
|
394
|
+
src=target_block,
|
|
395
|
+
data_test=data_test,
|
|
396
|
+
column_name=column_name,
|
|
397
|
+
tags=column_tags,
|
|
398
|
+
version=version,
|
|
399
|
+
)
|
|
400
|
+
self.parse_node(block)
|
|
401
|
+
|
|
402
|
+
def parse_tests(self, block: TestBlock) -> None:
|
|
403
|
+
for column in block.columns:
|
|
404
|
+
self.parse_column_tests(block, column, None)
|
|
405
|
+
|
|
406
|
+
for data_test in block.data_tests:
|
|
407
|
+
self.parse_test(block, data_test, None, None)
|
|
408
|
+
|
|
409
|
+
def parse_versioned_tests(self, block: VersionedTestBlock) -> None:
|
|
410
|
+
if not block.target.versions:
|
|
411
|
+
self.parse_tests(block)
|
|
412
|
+
else:
|
|
413
|
+
for version in block.target.versions:
|
|
414
|
+
for column in block.target.get_columns_for_version(version.v):
|
|
415
|
+
self.parse_column_tests(block, column, version.v)
|
|
416
|
+
|
|
417
|
+
for test in block.target.get_tests_for_version(version.v):
|
|
418
|
+
self.parse_test(block, test, None, version.v)
|
|
419
|
+
|
|
420
|
+
def generate_unique_id(self, resource_name: str, hash: Optional[str] = None) -> str:
|
|
421
|
+
return ".".join(
|
|
422
|
+
filter(None, [self.resource_type, self.project.project_name, resource_name, hash])
|
|
423
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from dvt.config.renderer import BaseRenderer, Keypath
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# This class renders dictionaries derived from "schema" yaml files.
|
|
7
|
+
# It calls Jinja on strings (in deep_map_render), except for certain
|
|
8
|
+
# keys which are skipped because they need to be rendered later
|
|
9
|
+
# (tests and description). Test configs are rendered in the
|
|
10
|
+
# generic test builder code, but skips the keyword args. The test
|
|
11
|
+
# keyword args are rendered to capture refs in render_test_update.
|
|
12
|
+
# Keyword args are finally rendered at compilation time.
|
|
13
|
+
# Descriptions are not rendered until 'process_docs'.
|
|
14
|
+
# Pre- and post-hooks in configs are late-rendered.
|
|
15
|
+
class SchemaYamlRenderer(BaseRenderer):
|
|
16
|
+
def __init__(self, context: Dict[str, Any], key: str) -> None:
|
|
17
|
+
super().__init__(context)
|
|
18
|
+
self.key = key
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def name(self):
|
|
22
|
+
return "Rendering yaml"
|
|
23
|
+
|
|
24
|
+
def _is_norender_key(self, keypath: Keypath) -> bool:
|
|
25
|
+
"""
|
|
26
|
+
models:
|
|
27
|
+
- name: blah
|
|
28
|
+
description: blah
|
|
29
|
+
data_tests: ...
|
|
30
|
+
columns:
|
|
31
|
+
- name:
|
|
32
|
+
description: blah
|
|
33
|
+
data_tests: ...
|
|
34
|
+
|
|
35
|
+
Return True if it's tests, data_tests or description - those aren't rendered now
|
|
36
|
+
because they're rendered later in parse_generic_tests or process_docs.
|
|
37
|
+
"tests" and "data_tests" are both currently supported but "tests" has been deprecated
|
|
38
|
+
"""
|
|
39
|
+
# top level descriptions and data_tests
|
|
40
|
+
if len(keypath) >= 1 and keypath[0] in (
|
|
41
|
+
"tests",
|
|
42
|
+
"data_tests",
|
|
43
|
+
"description",
|
|
44
|
+
"loaded_at_query",
|
|
45
|
+
):
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
# columns descriptions and data_tests
|
|
49
|
+
if len(keypath) == 2 and keypath[1] in (
|
|
50
|
+
"tests",
|
|
51
|
+
"data_tests",
|
|
52
|
+
"description",
|
|
53
|
+
"loaded_at_query",
|
|
54
|
+
):
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
# config: pre- and post-hooks, and loaded_at_query
|
|
58
|
+
if (
|
|
59
|
+
len(keypath) >= 2
|
|
60
|
+
and keypath[0] == "config"
|
|
61
|
+
and keypath[1] in ("pre_hook", "post_hook", "loaded_at_query")
|
|
62
|
+
):
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
# versions
|
|
66
|
+
if len(keypath) == 5 and keypath[4] == "description":
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
len(keypath) >= 3
|
|
71
|
+
and keypath[0] in ("columns", "dimensions", "measures", "entities")
|
|
72
|
+
and keypath[2] in ("tests", "data_tests", "description")
|
|
73
|
+
):
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
# don't render descriptions or test keyword arguments
|
|
79
|
+
def should_render_keypath(self, keypath: Keypath) -> bool:
|
|
80
|
+
if len(keypath) < 1:
|
|
81
|
+
return True
|
|
82
|
+
if self.key == "sources":
|
|
83
|
+
if keypath[0] in ("description", "loaded_at_query"):
|
|
84
|
+
return False
|
|
85
|
+
if len(keypath) >= 2 and keypath[0] == "config" and keypath[1] == "loaded_at_query":
|
|
86
|
+
return False
|
|
87
|
+
if keypath[0] == "tables":
|
|
88
|
+
if self._is_norender_key(keypath[2:]):
|
|
89
|
+
return False
|
|
90
|
+
elif self.key == "macros":
|
|
91
|
+
if keypath[0] == "arguments":
|
|
92
|
+
if self._is_norender_key(keypath[1:]):
|
|
93
|
+
return False
|
|
94
|
+
elif self._is_norender_key(keypath[0:]):
|
|
95
|
+
return False
|
|
96
|
+
elif self.key == "metrics":
|
|
97
|
+
# This ensures that metric filters are skipped
|
|
98
|
+
if keypath[-1] == "filter" or len(keypath) > 1 and keypath[-2] == "filter":
|
|
99
|
+
return False
|
|
100
|
+
elif self._is_norender_key(keypath[0:]):
|
|
101
|
+
return False
|
|
102
|
+
elif self.key == "saved_queries":
|
|
103
|
+
# This ensures that saved query filters are skipped
|
|
104
|
+
if keypath[0] == "query_params" and len(keypath) > 1 and keypath[1] == "where":
|
|
105
|
+
return False
|
|
106
|
+
elif self._is_norender_key(keypath[0:]):
|
|
107
|
+
return False
|
|
108
|
+
else: # models, seeds, snapshots, analyses
|
|
109
|
+
if self._is_norender_key(keypath[0:]):
|
|
110
|
+
return False
|
|
111
|
+
return True
|