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/context/base.py
ADDED
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import itertools
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import threading
|
|
9
|
+
from typing import Any, Callable, Dict, Iterable, List, Mapping, NoReturn, Optional, Set
|
|
10
|
+
|
|
11
|
+
import dvt.deprecations as deprecations
|
|
12
|
+
import dvt.flags as flags_module
|
|
13
|
+
|
|
14
|
+
# These modules are added to the context. Consider alternative
|
|
15
|
+
# approaches which will extend well to potentially many modules
|
|
16
|
+
import pytz
|
|
17
|
+
from dvt import tracking, utils
|
|
18
|
+
from dvt.clients.jinja import get_rendered
|
|
19
|
+
from dvt.clients.yaml_helper import ( # noqa: F401
|
|
20
|
+
Dumper,
|
|
21
|
+
Loader,
|
|
22
|
+
SafeLoader,
|
|
23
|
+
safe_load,
|
|
24
|
+
yaml,
|
|
25
|
+
)
|
|
26
|
+
from dvt.constants import DEFAULT_ENV_PLACEHOLDER, SECRET_PLACEHOLDER
|
|
27
|
+
from dvt.contracts.graph.nodes import Resource
|
|
28
|
+
from dvt.events.types import JinjaLogDebug, JinjaLogInfo
|
|
29
|
+
from dvt.exceptions import (
|
|
30
|
+
EnvVarMissingError,
|
|
31
|
+
RequiredVarNotFoundError,
|
|
32
|
+
SecretEnvVarLocationError,
|
|
33
|
+
SetStrictWrongTypeError,
|
|
34
|
+
ZipStrictWrongTypeError,
|
|
35
|
+
)
|
|
36
|
+
from dvt.flags import get_flags
|
|
37
|
+
from dvt.version import __version__ as dbt_version
|
|
38
|
+
|
|
39
|
+
from dbt_common.constants import SECRET_ENV_PREFIX
|
|
40
|
+
from dbt_common.context import get_invocation_context
|
|
41
|
+
from dbt_common.events.contextvars import get_node_info
|
|
42
|
+
from dbt_common.events.functions import fire_event, get_invocation_id
|
|
43
|
+
from dbt_common.events.types import PrintEvent
|
|
44
|
+
from dbt_common.exceptions.macros import MacroReturn
|
|
45
|
+
|
|
46
|
+
# See the `contexts` module README for more information on how contexts work
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_pytz_module_context() -> Dict[str, Any]:
|
|
50
|
+
context_exports = pytz.__all__ # type: ignore
|
|
51
|
+
|
|
52
|
+
return {name: getattr(pytz, name) for name in context_exports}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_datetime_module_context() -> Dict[str, Any]:
|
|
56
|
+
context_exports = ["date", "datetime", "time", "timedelta", "tzinfo"]
|
|
57
|
+
|
|
58
|
+
return {name: getattr(datetime, name) for name in context_exports}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_re_module_context() -> Dict[str, Any]:
|
|
62
|
+
# TODO CT-211
|
|
63
|
+
context_exports = re.__all__ # type: ignore[attr-defined]
|
|
64
|
+
|
|
65
|
+
return {name: getattr(re, name) for name in context_exports}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_itertools_module_context() -> Dict[str, Any]:
|
|
69
|
+
# Excluded dropwhile, filterfalse, takewhile and groupby;
|
|
70
|
+
# first 3 illogical for Jinja and last redundant.
|
|
71
|
+
context_exports = [
|
|
72
|
+
"count",
|
|
73
|
+
"cycle",
|
|
74
|
+
"repeat",
|
|
75
|
+
"accumulate",
|
|
76
|
+
"chain",
|
|
77
|
+
"compress",
|
|
78
|
+
"islice",
|
|
79
|
+
"starmap",
|
|
80
|
+
"tee",
|
|
81
|
+
"zip_longest",
|
|
82
|
+
"product",
|
|
83
|
+
"permutations",
|
|
84
|
+
"combinations",
|
|
85
|
+
"combinations_with_replacement",
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
def deprecation_wrapper(fn):
|
|
89
|
+
def deprecation_wrapper_inner(*args, **kwargs):
|
|
90
|
+
deprecations.warn("modules-itertools-usage-deprecation")
|
|
91
|
+
return fn(*args, **kwargs)
|
|
92
|
+
|
|
93
|
+
return deprecation_wrapper_inner
|
|
94
|
+
|
|
95
|
+
return {name: deprecation_wrapper(getattr(itertools, name)) for name in context_exports}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_context_modules() -> Dict[str, Dict[str, Any]]:
|
|
99
|
+
return {
|
|
100
|
+
"pytz": get_pytz_module_context(),
|
|
101
|
+
"datetime": get_datetime_module_context(),
|
|
102
|
+
"re": get_re_module_context(),
|
|
103
|
+
"itertools": get_itertools_module_context(),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ContextMember:
|
|
108
|
+
def __init__(self, value: Any, name: Optional[str] = None) -> None:
|
|
109
|
+
self.name = name
|
|
110
|
+
self.inner = value
|
|
111
|
+
|
|
112
|
+
def key(self, default: str) -> str:
|
|
113
|
+
if self.name is None:
|
|
114
|
+
return default
|
|
115
|
+
return self.name
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def contextmember(value: Optional[str] = None) -> Callable:
|
|
119
|
+
return lambda v: ContextMember(v, name=value)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def contextproperty(value: Optional[str] = None) -> Callable:
|
|
123
|
+
return lambda v: ContextMember(property(v), name=value)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ContextMeta(type):
|
|
127
|
+
def __new__(mcls, name, bases, dct: Dict[str, Any]) -> ContextMeta:
|
|
128
|
+
context_members: Dict[str, Any] = {}
|
|
129
|
+
context_attrs: Dict[str, Any] = {}
|
|
130
|
+
new_dct: Dict[str, Any] = {}
|
|
131
|
+
|
|
132
|
+
for base in bases:
|
|
133
|
+
context_members.update(getattr(base, "_context_members_", {}))
|
|
134
|
+
context_attrs.update(getattr(base, "_context_attrs_", {}))
|
|
135
|
+
|
|
136
|
+
for key, value in dct.items():
|
|
137
|
+
if isinstance(value, ContextMember):
|
|
138
|
+
context_key = value.key(key)
|
|
139
|
+
context_members[context_key] = value.inner
|
|
140
|
+
context_attrs[context_key] = key
|
|
141
|
+
value = value.inner
|
|
142
|
+
new_dct[key] = value
|
|
143
|
+
new_dct["_context_members_"] = context_members
|
|
144
|
+
new_dct["_context_attrs_"] = context_attrs
|
|
145
|
+
return type.__new__(mcls, name, bases, new_dct)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class Var:
|
|
149
|
+
_VAR_NOTSET = object()
|
|
150
|
+
|
|
151
|
+
def __init__(
|
|
152
|
+
self,
|
|
153
|
+
context: Mapping[str, Any],
|
|
154
|
+
cli_vars: Mapping[str, Any],
|
|
155
|
+
node: Optional[Resource] = None,
|
|
156
|
+
) -> None:
|
|
157
|
+
self._context: Mapping[str, Any] = context
|
|
158
|
+
self._cli_vars: Mapping[str, Any] = cli_vars
|
|
159
|
+
self._node: Optional[Resource] = node
|
|
160
|
+
self._merged: Mapping[str, Any] = self._generate_merged()
|
|
161
|
+
|
|
162
|
+
def _generate_merged(self) -> Mapping[str, Any]:
|
|
163
|
+
return self._cli_vars
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def node_name(self) -> str:
|
|
167
|
+
if self._node is not None:
|
|
168
|
+
return self._node.name
|
|
169
|
+
else:
|
|
170
|
+
return "<Configuration>"
|
|
171
|
+
|
|
172
|
+
def get_missing_var(self, var_name: str) -> NoReturn:
|
|
173
|
+
# TODO function name implies a non exception resolution
|
|
174
|
+
raise RequiredVarNotFoundError(var_name, dict(self._merged), self._node)
|
|
175
|
+
|
|
176
|
+
def has_var(self, var_name: str) -> bool:
|
|
177
|
+
return var_name in self._merged
|
|
178
|
+
|
|
179
|
+
def get_rendered_var(self, var_name: str) -> Any:
|
|
180
|
+
raw = self._merged[var_name]
|
|
181
|
+
# if bool/int/float/etc are passed in, don't compile anything
|
|
182
|
+
if not isinstance(raw, str):
|
|
183
|
+
return raw
|
|
184
|
+
|
|
185
|
+
return get_rendered(raw, dict(self._context))
|
|
186
|
+
|
|
187
|
+
def __call__(self, var_name: str, default: Any = _VAR_NOTSET) -> Any:
|
|
188
|
+
if self.has_var(var_name):
|
|
189
|
+
return self.get_rendered_var(var_name)
|
|
190
|
+
elif default is not self._VAR_NOTSET:
|
|
191
|
+
return default
|
|
192
|
+
else:
|
|
193
|
+
return self.get_missing_var(var_name)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class BaseContext(metaclass=ContextMeta):
|
|
197
|
+
# Set by ContextMeta
|
|
198
|
+
_context_members_: Dict[str, Any]
|
|
199
|
+
_context_attrs_: Dict[str, Any]
|
|
200
|
+
|
|
201
|
+
# subclass is TargetContext
|
|
202
|
+
def __init__(self, cli_vars: Dict[str, Any]) -> None:
|
|
203
|
+
self._ctx: Dict[str, Any] = {}
|
|
204
|
+
self.cli_vars: Dict[str, Any] = cli_vars
|
|
205
|
+
self.env_vars: Dict[str, Any] = {}
|
|
206
|
+
|
|
207
|
+
def generate_builtins(self) -> Dict[str, Any]:
|
|
208
|
+
builtins: Dict[str, Any] = {}
|
|
209
|
+
for key, value in self._context_members_.items():
|
|
210
|
+
if hasattr(value, "__get__"):
|
|
211
|
+
# handle properties, bound methods, etc
|
|
212
|
+
value = value.__get__(self)
|
|
213
|
+
builtins[key] = value
|
|
214
|
+
return builtins
|
|
215
|
+
|
|
216
|
+
# no dbtClassMixin so this is not an actual override
|
|
217
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
218
|
+
self._ctx["context"] = self._ctx
|
|
219
|
+
builtins = self.generate_builtins()
|
|
220
|
+
self._ctx["builtins"] = builtins
|
|
221
|
+
self._ctx.update(builtins)
|
|
222
|
+
return self._ctx
|
|
223
|
+
|
|
224
|
+
@contextproperty()
|
|
225
|
+
def dbt_version(self) -> str:
|
|
226
|
+
"""The `dbt_version` variable returns the installed version of dbt that
|
|
227
|
+
is currently running. It can be used for debugging or auditing
|
|
228
|
+
purposes.
|
|
229
|
+
|
|
230
|
+
> macros/get_version.sql
|
|
231
|
+
|
|
232
|
+
{% macro get_version() %}
|
|
233
|
+
{% set msg = "The installed version of dbt is: " ~ dbt_version %}
|
|
234
|
+
{% do log(msg, info=true) %}
|
|
235
|
+
{% endmacro %}
|
|
236
|
+
|
|
237
|
+
Example output:
|
|
238
|
+
|
|
239
|
+
$ dbt run-operation get_version
|
|
240
|
+
The installed version of dbt is 0.16.0
|
|
241
|
+
"""
|
|
242
|
+
return dbt_version
|
|
243
|
+
|
|
244
|
+
@contextproperty()
|
|
245
|
+
def var(self) -> Var:
|
|
246
|
+
"""Variables can be passed from your `dbt_project.yml` file into models
|
|
247
|
+
during compilation. These variables are useful for configuring packages
|
|
248
|
+
for deployment in multiple environments, or defining values that should
|
|
249
|
+
be used across multiple models within a package.
|
|
250
|
+
|
|
251
|
+
To add a variable to a model, use the `var()` function:
|
|
252
|
+
|
|
253
|
+
> my_model.sql:
|
|
254
|
+
|
|
255
|
+
select * from events where event_type = '{{ var("event_type") }}'
|
|
256
|
+
|
|
257
|
+
If you try to run this model without supplying an `event_type`
|
|
258
|
+
variable, you'll receive a compilation error that looks like this:
|
|
259
|
+
|
|
260
|
+
Encountered an error:
|
|
261
|
+
! Compilation error while compiling model package_name.my_model:
|
|
262
|
+
! Required var 'event_type' not found in config:
|
|
263
|
+
Vars supplied to package_name.my_model = {
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
To supply a variable to a given model, add one or more `vars`
|
|
267
|
+
dictionaries to the `models` config in your `dbt_project.yml` file.
|
|
268
|
+
These `vars` are in-scope for all models at or below where they are
|
|
269
|
+
defined, so place them where they make the most sense. Below are three
|
|
270
|
+
different placements of the `vars` dict, all of which will make the
|
|
271
|
+
`my_model` model compile.
|
|
272
|
+
|
|
273
|
+
> dbt_project.yml:
|
|
274
|
+
|
|
275
|
+
# 1) scoped at the model level
|
|
276
|
+
models:
|
|
277
|
+
package_name:
|
|
278
|
+
my_model:
|
|
279
|
+
materialized: view
|
|
280
|
+
vars:
|
|
281
|
+
event_type: activation
|
|
282
|
+
# 2) scoped at the package level
|
|
283
|
+
models:
|
|
284
|
+
package_name:
|
|
285
|
+
vars:
|
|
286
|
+
event_type: activation
|
|
287
|
+
my_model:
|
|
288
|
+
materialized: view
|
|
289
|
+
# 3) scoped globally
|
|
290
|
+
models:
|
|
291
|
+
vars:
|
|
292
|
+
event_type: activation
|
|
293
|
+
package_name:
|
|
294
|
+
my_model:
|
|
295
|
+
materialized: view
|
|
296
|
+
|
|
297
|
+
## Variable default values
|
|
298
|
+
|
|
299
|
+
The `var()` function takes an optional second argument, `default`. If
|
|
300
|
+
this argument is provided, then it will be the default value for the
|
|
301
|
+
variable if one is not explicitly defined.
|
|
302
|
+
|
|
303
|
+
> my_model.sql:
|
|
304
|
+
|
|
305
|
+
-- Use 'activation' as the event_type if the variable is not
|
|
306
|
+
-- defined.
|
|
307
|
+
select *
|
|
308
|
+
from events
|
|
309
|
+
where event_type = '{{ var("event_type", "activation") }}'
|
|
310
|
+
"""
|
|
311
|
+
return Var(self._ctx, self.cli_vars)
|
|
312
|
+
|
|
313
|
+
@contextmember()
|
|
314
|
+
def env_var(self, var: str, default: Optional[str] = None) -> str:
|
|
315
|
+
"""The env_var() function. Return the environment variable named 'var'.
|
|
316
|
+
If there is no such environment variable set, return the default.
|
|
317
|
+
|
|
318
|
+
If the default is None, raise an exception for an undefined variable.
|
|
319
|
+
"""
|
|
320
|
+
return_value = None
|
|
321
|
+
if var.startswith(SECRET_ENV_PREFIX):
|
|
322
|
+
raise SecretEnvVarLocationError(var)
|
|
323
|
+
env = get_invocation_context().env
|
|
324
|
+
if var in env:
|
|
325
|
+
return_value = env[var]
|
|
326
|
+
elif default is not None:
|
|
327
|
+
return_value = default
|
|
328
|
+
|
|
329
|
+
if return_value is not None:
|
|
330
|
+
# If the environment variable is set from a default, store a string indicating
|
|
331
|
+
# that so we can skip partial parsing. Otherwise the file will be scheduled for
|
|
332
|
+
# reparsing. If the default changes, the file will have been updated and therefore
|
|
333
|
+
# will be scheduled for reparsing anyways.
|
|
334
|
+
self.env_vars[var] = return_value if var in env else DEFAULT_ENV_PLACEHOLDER
|
|
335
|
+
|
|
336
|
+
return return_value
|
|
337
|
+
else:
|
|
338
|
+
raise EnvVarMissingError(var)
|
|
339
|
+
|
|
340
|
+
if os.environ.get("DBT_MACRO_DEBUGGING"):
|
|
341
|
+
|
|
342
|
+
@contextmember()
|
|
343
|
+
@staticmethod
|
|
344
|
+
def debug():
|
|
345
|
+
"""Enter a debugger at this line in the compiled jinja code."""
|
|
346
|
+
import sys
|
|
347
|
+
|
|
348
|
+
import ipdb # type: ignore
|
|
349
|
+
|
|
350
|
+
frame = sys._getframe(3)
|
|
351
|
+
ipdb.set_trace(frame)
|
|
352
|
+
return ""
|
|
353
|
+
|
|
354
|
+
@contextmember("return")
|
|
355
|
+
@staticmethod
|
|
356
|
+
def _return(data: Any) -> NoReturn:
|
|
357
|
+
"""The `return` function can be used in macros to return data to the
|
|
358
|
+
caller. The type of the data (`dict`, `list`, `int`, etc) will be
|
|
359
|
+
preserved through the return call.
|
|
360
|
+
|
|
361
|
+
:param data: The data to return to the caller
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
> macros/example.sql:
|
|
365
|
+
|
|
366
|
+
{% macro get_data() %}
|
|
367
|
+
{{ return([1,2,3]) }}
|
|
368
|
+
{% endmacro %}
|
|
369
|
+
|
|
370
|
+
> models/my_model.sql:
|
|
371
|
+
|
|
372
|
+
select
|
|
373
|
+
-- getdata() returns a list!
|
|
374
|
+
{% for i in getdata() %}
|
|
375
|
+
{{ i }}
|
|
376
|
+
{% if not loop.last %},{% endif %}
|
|
377
|
+
{% endfor %}
|
|
378
|
+
|
|
379
|
+
"""
|
|
380
|
+
raise MacroReturn(data)
|
|
381
|
+
|
|
382
|
+
@contextmember()
|
|
383
|
+
@staticmethod
|
|
384
|
+
def fromjson(string: str, default: Any = None) -> Any:
|
|
385
|
+
"""The `fromjson` context method can be used to deserialize a json
|
|
386
|
+
string into a Python object primitive, eg. a `dict` or `list`.
|
|
387
|
+
|
|
388
|
+
:param value: The json string to deserialize
|
|
389
|
+
:param default: A default value to return if the `string` argument
|
|
390
|
+
cannot be deserialized (optional)
|
|
391
|
+
|
|
392
|
+
Usage:
|
|
393
|
+
|
|
394
|
+
{% set my_json_str = '{"abc": 123}' %}
|
|
395
|
+
{% set my_dict = fromjson(my_json_str) %}
|
|
396
|
+
{% do log(my_dict['abc']) %}
|
|
397
|
+
"""
|
|
398
|
+
try:
|
|
399
|
+
return json.loads(string)
|
|
400
|
+
except ValueError:
|
|
401
|
+
return default
|
|
402
|
+
|
|
403
|
+
@contextmember()
|
|
404
|
+
@staticmethod
|
|
405
|
+
def tojson(value: Any, default: Any = None, sort_keys: bool = False) -> Any:
|
|
406
|
+
"""The `tojson` context method can be used to serialize a Python
|
|
407
|
+
object primitive, eg. a `dict` or `list` to a json string.
|
|
408
|
+
|
|
409
|
+
:param value: The value serialize to json
|
|
410
|
+
:param default: A default value to return if the `value` argument
|
|
411
|
+
cannot be serialized
|
|
412
|
+
:param sort_keys: If True, sort the keys.
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
Usage:
|
|
416
|
+
|
|
417
|
+
{% set my_dict = {"abc": 123} %}
|
|
418
|
+
{% set my_json_string = tojson(my_dict) %}
|
|
419
|
+
{% do log(my_json_string) %}
|
|
420
|
+
"""
|
|
421
|
+
try:
|
|
422
|
+
return json.dumps(value, sort_keys=sort_keys)
|
|
423
|
+
except ValueError:
|
|
424
|
+
return default
|
|
425
|
+
|
|
426
|
+
@contextmember()
|
|
427
|
+
@staticmethod
|
|
428
|
+
def fromyaml(value: str, default: Any = None) -> Any:
|
|
429
|
+
"""The fromyaml context method can be used to deserialize a yaml string
|
|
430
|
+
into a Python object primitive, eg. a `dict` or `list`.
|
|
431
|
+
|
|
432
|
+
:param value: The yaml string to deserialize
|
|
433
|
+
:param default: A default value to return if the `string` argument
|
|
434
|
+
cannot be deserialized (optional)
|
|
435
|
+
|
|
436
|
+
Usage:
|
|
437
|
+
|
|
438
|
+
{% set my_yml_str -%}
|
|
439
|
+
dogs:
|
|
440
|
+
- good
|
|
441
|
+
- bad
|
|
442
|
+
{%- endset %}
|
|
443
|
+
{% set my_dict = fromyaml(my_yml_str) %}
|
|
444
|
+
{% do log(my_dict['dogs'], info=true) %}
|
|
445
|
+
-- ["good", "bad"]
|
|
446
|
+
{% do my_dict['dogs'].pop() }
|
|
447
|
+
{% do log(my_dict['dogs'], info=true) %}
|
|
448
|
+
-- ["good"]
|
|
449
|
+
"""
|
|
450
|
+
try:
|
|
451
|
+
return safe_load(value)
|
|
452
|
+
except (AttributeError, ValueError, yaml.YAMLError):
|
|
453
|
+
return default
|
|
454
|
+
|
|
455
|
+
# safe_dump defaults to sort_keys=True, but we act like json.dumps (the
|
|
456
|
+
# opposite)
|
|
457
|
+
@contextmember()
|
|
458
|
+
@staticmethod
|
|
459
|
+
def toyaml(
|
|
460
|
+
value: Any, default: Optional[str] = None, sort_keys: bool = False
|
|
461
|
+
) -> Optional[str]:
|
|
462
|
+
"""The `tojson` context method can be used to serialize a Python
|
|
463
|
+
object primitive, eg. a `dict` or `list` to a yaml string.
|
|
464
|
+
|
|
465
|
+
:param value: The value serialize to yaml
|
|
466
|
+
:param default: A default value to return if the `value` argument
|
|
467
|
+
cannot be serialized
|
|
468
|
+
:param sort_keys: If True, sort the keys.
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
Usage:
|
|
472
|
+
|
|
473
|
+
{% set my_dict = {"abc": 123} %}
|
|
474
|
+
{% set my_yaml_string = toyaml(my_dict) %}
|
|
475
|
+
{% do log(my_yaml_string) %}
|
|
476
|
+
"""
|
|
477
|
+
try:
|
|
478
|
+
return yaml.safe_dump(data=value, sort_keys=sort_keys)
|
|
479
|
+
except (ValueError, yaml.YAMLError):
|
|
480
|
+
return default
|
|
481
|
+
|
|
482
|
+
@contextmember("set")
|
|
483
|
+
@staticmethod
|
|
484
|
+
def _set(value: Iterable[Any], default: Any = None) -> Optional[Set[Any]]:
|
|
485
|
+
"""The `set` context method can be used to convert any iterable
|
|
486
|
+
to a sequence of iterable elements that are unique (a set).
|
|
487
|
+
|
|
488
|
+
:param value: The iterable
|
|
489
|
+
:param default: A default value to return if the `value` argument
|
|
490
|
+
is not an iterable
|
|
491
|
+
|
|
492
|
+
Usage:
|
|
493
|
+
{% set my_list = [1, 2, 2, 3] %}
|
|
494
|
+
{% set my_set = set(my_list) %}
|
|
495
|
+
{% do log(my_set) %} {# {1, 2, 3} #}
|
|
496
|
+
"""
|
|
497
|
+
try:
|
|
498
|
+
return set(value)
|
|
499
|
+
except TypeError:
|
|
500
|
+
return default
|
|
501
|
+
|
|
502
|
+
@contextmember()
|
|
503
|
+
@staticmethod
|
|
504
|
+
def set_strict(value: Iterable[Any]) -> Set[Any]:
|
|
505
|
+
"""The `set_strict` context method can be used to convert any iterable
|
|
506
|
+
to a sequence of iterable elements that are unique (a set). The
|
|
507
|
+
difference to the `set` context method is that the `set_strict` method
|
|
508
|
+
will raise an exception on a TypeError.
|
|
509
|
+
|
|
510
|
+
:param value: The iterable
|
|
511
|
+
|
|
512
|
+
Usage:
|
|
513
|
+
{% set my_list = [1, 2, 2, 3] %}
|
|
514
|
+
{% set my_set = set_strict(my_list) %}
|
|
515
|
+
{% do log(my_set) %} {# {1, 2, 3} #}
|
|
516
|
+
"""
|
|
517
|
+
try:
|
|
518
|
+
return set(value)
|
|
519
|
+
except TypeError as e:
|
|
520
|
+
raise SetStrictWrongTypeError(e)
|
|
521
|
+
|
|
522
|
+
@contextmember("zip")
|
|
523
|
+
@staticmethod
|
|
524
|
+
def _zip(*args: Iterable[Any], default: Any = None) -> Optional[Iterable[Any]]:
|
|
525
|
+
"""The `zip` context method can be used to used to return
|
|
526
|
+
an iterator of tuples, where the i-th tuple contains the i-th
|
|
527
|
+
element from each of the argument iterables.
|
|
528
|
+
|
|
529
|
+
:param *args: Any number of iterables
|
|
530
|
+
:param default: A default value to return if `*args` is not
|
|
531
|
+
iterable
|
|
532
|
+
|
|
533
|
+
Usage:
|
|
534
|
+
{% set my_list_a = [1, 2] %}
|
|
535
|
+
{% set my_list_b = ['alice', 'bob'] %}
|
|
536
|
+
{% set my_zip = zip(my_list_a, my_list_b) | list %}
|
|
537
|
+
{% do log(my_set) %} {# [(1, 'alice'), (2, 'bob')] #}
|
|
538
|
+
"""
|
|
539
|
+
try:
|
|
540
|
+
return zip(*args)
|
|
541
|
+
except TypeError:
|
|
542
|
+
return default
|
|
543
|
+
|
|
544
|
+
@contextmember()
|
|
545
|
+
@staticmethod
|
|
546
|
+
def zip_strict(*args: Iterable[Any]) -> Iterable[Any]:
|
|
547
|
+
"""The `zip_strict` context method can be used to used to return
|
|
548
|
+
an iterator of tuples, where the i-th tuple contains the i-th
|
|
549
|
+
element from each of the argument iterables. The difference to the
|
|
550
|
+
`zip` context method is that the `zip_strict` method will raise an
|
|
551
|
+
exception on a TypeError.
|
|
552
|
+
|
|
553
|
+
:param *args: Any number of iterables
|
|
554
|
+
|
|
555
|
+
Usage:
|
|
556
|
+
{% set my_list_a = [1, 2] %}
|
|
557
|
+
{% set my_list_b = ['alice', 'bob'] %}
|
|
558
|
+
{% set my_zip = zip_strict(my_list_a, my_list_b) | list %}
|
|
559
|
+
{% do log(my_set) %} {# [(1, 'alice'), (2, 'bob')] #}
|
|
560
|
+
"""
|
|
561
|
+
try:
|
|
562
|
+
return zip(*args)
|
|
563
|
+
except TypeError as e:
|
|
564
|
+
raise ZipStrictWrongTypeError(e)
|
|
565
|
+
|
|
566
|
+
@contextmember()
|
|
567
|
+
@staticmethod
|
|
568
|
+
def log(msg: str, info: bool = False) -> str:
|
|
569
|
+
"""Logs a line to either the log file or stdout.
|
|
570
|
+
|
|
571
|
+
:param msg: The message to log
|
|
572
|
+
:param info: If `False`, write to the log file. If `True`, write to
|
|
573
|
+
both the log file and stdout.
|
|
574
|
+
|
|
575
|
+
> macros/my_log_macro.sql
|
|
576
|
+
|
|
577
|
+
{% macro some_macro(arg1, arg2) %}
|
|
578
|
+
{{ log("Running some_macro: " ~ arg1 ~ ", " ~ arg2) }}
|
|
579
|
+
{% endmacro %}"
|
|
580
|
+
"""
|
|
581
|
+
# Detect instances of the placeholder value ($$$DBT_SECRET_START...DBT_SECRET_END$$$)
|
|
582
|
+
# and replace it with the standard mask '*****'
|
|
583
|
+
if "DBT_SECRET_START" in str(msg):
|
|
584
|
+
search_group = f"({SECRET_ENV_PREFIX}(.*))"
|
|
585
|
+
pattern = SECRET_PLACEHOLDER.format(search_group).replace("$", r"\$")
|
|
586
|
+
m = re.search(
|
|
587
|
+
pattern,
|
|
588
|
+
msg,
|
|
589
|
+
)
|
|
590
|
+
if m:
|
|
591
|
+
msg = re.sub(pattern, "*****", msg)
|
|
592
|
+
|
|
593
|
+
if info:
|
|
594
|
+
fire_event(JinjaLogInfo(msg=msg, node_info=get_node_info()))
|
|
595
|
+
else:
|
|
596
|
+
fire_event(JinjaLogDebug(msg=msg, node_info=get_node_info()))
|
|
597
|
+
return ""
|
|
598
|
+
|
|
599
|
+
@contextproperty()
|
|
600
|
+
def run_started_at(self) -> Optional[datetime.datetime]:
|
|
601
|
+
"""`run_started_at` outputs the timestamp that this run started, e.g.
|
|
602
|
+
`2017-04-21 01:23:45.678`. The `run_started_at` variable is a Python
|
|
603
|
+
`datetime` object. As of 0.9.1, the timezone of this variable defaults
|
|
604
|
+
to UTC.
|
|
605
|
+
|
|
606
|
+
> run_started_at_example.sql
|
|
607
|
+
|
|
608
|
+
select
|
|
609
|
+
'{{ run_started_at.strftime("%Y-%m-%d") }}' as date_day
|
|
610
|
+
from ...
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
To modify the timezone of this variable, use the the `pytz` module:
|
|
614
|
+
|
|
615
|
+
> run_started_at_utc.sql
|
|
616
|
+
|
|
617
|
+
{% set est = modules.pytz.timezone("America/New_York") %}
|
|
618
|
+
select
|
|
619
|
+
'{{ run_started_at.astimezone(est) }}' as run_started_est
|
|
620
|
+
from ...
|
|
621
|
+
"""
|
|
622
|
+
if tracking.active_user is not None:
|
|
623
|
+
return tracking.active_user.run_started_at
|
|
624
|
+
else:
|
|
625
|
+
return None
|
|
626
|
+
|
|
627
|
+
@contextproperty()
|
|
628
|
+
def invocation_id(self) -> Optional[str]:
|
|
629
|
+
"""invocation_id outputs a UUID generated for this dbt run (useful for
|
|
630
|
+
auditing)
|
|
631
|
+
"""
|
|
632
|
+
return get_invocation_id()
|
|
633
|
+
|
|
634
|
+
@contextproperty()
|
|
635
|
+
def thread_id(self) -> str:
|
|
636
|
+
"""thread_id outputs an ID for the current thread (useful for auditing)"""
|
|
637
|
+
return threading.current_thread().name
|
|
638
|
+
|
|
639
|
+
@contextproperty()
|
|
640
|
+
def modules(self) -> Dict[str, Any]:
|
|
641
|
+
"""The `modules` variable in the Jinja context contains useful Python
|
|
642
|
+
modules for operating on data.
|
|
643
|
+
|
|
644
|
+
# datetime
|
|
645
|
+
|
|
646
|
+
This variable is a pointer to the Python datetime module.
|
|
647
|
+
|
|
648
|
+
Usage:
|
|
649
|
+
|
|
650
|
+
{% set dt = modules.datetime.datetime.now() %}
|
|
651
|
+
|
|
652
|
+
# pytz
|
|
653
|
+
|
|
654
|
+
This variable is a pointer to the Python pytz module.
|
|
655
|
+
|
|
656
|
+
Usage:
|
|
657
|
+
|
|
658
|
+
{% set dt = modules.datetime.datetime(2002, 10, 27, 6, 0, 0) %}
|
|
659
|
+
{% set dt_local = modules.pytz.timezone('US/Eastern').localize(dt) %}
|
|
660
|
+
{{ dt_local }}
|
|
661
|
+
""" # noqa
|
|
662
|
+
return get_context_modules()
|
|
663
|
+
|
|
664
|
+
@contextproperty()
|
|
665
|
+
def flags(self) -> Any:
|
|
666
|
+
"""The `flags` variable contains true/false values for flags provided
|
|
667
|
+
on the command line.
|
|
668
|
+
|
|
669
|
+
> flags.sql:
|
|
670
|
+
|
|
671
|
+
{% if flags.FULL_REFRESH %}
|
|
672
|
+
drop table ...
|
|
673
|
+
{% else %}
|
|
674
|
+
-- no-op
|
|
675
|
+
{% endif %}
|
|
676
|
+
|
|
677
|
+
This supports all flags defined in flags submodule (core/dbt/flags.py)
|
|
678
|
+
"""
|
|
679
|
+
return flags_module.get_flag_obj()
|
|
680
|
+
|
|
681
|
+
@contextmember()
|
|
682
|
+
@staticmethod
|
|
683
|
+
def print(msg: str) -> str:
|
|
684
|
+
"""Prints a line to stdout.
|
|
685
|
+
|
|
686
|
+
:param msg: The message to print
|
|
687
|
+
|
|
688
|
+
> macros/my_log_macro.sql
|
|
689
|
+
|
|
690
|
+
{% macro some_macro(arg1, arg2) %}
|
|
691
|
+
{{ print("Running some_macro: " ~ arg1 ~ ", " ~ arg2) }}
|
|
692
|
+
{% endmacro %}"
|
|
693
|
+
"""
|
|
694
|
+
|
|
695
|
+
if get_flags().PRINT:
|
|
696
|
+
# No formatting, still get to stdout when --quiet is used
|
|
697
|
+
fire_event(PrintEvent(msg=msg))
|
|
698
|
+
return ""
|
|
699
|
+
|
|
700
|
+
@contextmember()
|
|
701
|
+
@staticmethod
|
|
702
|
+
def diff_of_two_dicts(
|
|
703
|
+
dict_a: Dict[str, List[str]], dict_b: Dict[str, List[str]]
|
|
704
|
+
) -> Dict[str, List[str]]:
|
|
705
|
+
"""
|
|
706
|
+
Given two dictionaries of type Dict[str, List[str]]:
|
|
707
|
+
dict_a = {'key_x': ['value_1', 'VALUE_2'], 'KEY_Y': ['value_3']}
|
|
708
|
+
dict_b = {'key_x': ['value_1'], 'key_z': ['value_4']}
|
|
709
|
+
Return the same dictionary representation of dict_a MINUS dict_b,
|
|
710
|
+
performing a case-insensitive comparison between the strings in each.
|
|
711
|
+
All keys returned will be in the original case of dict_a.
|
|
712
|
+
returns {'key_x': ['VALUE_2'], 'KEY_Y': ['value_3']}
|
|
713
|
+
"""
|
|
714
|
+
|
|
715
|
+
dict_diff = {}
|
|
716
|
+
dict_b_lowered = {k.casefold(): [x.casefold() for x in v] for k, v in dict_b.items()}
|
|
717
|
+
for k in dict_a:
|
|
718
|
+
if k.casefold() in dict_b_lowered.keys():
|
|
719
|
+
diff = []
|
|
720
|
+
for v in dict_a[k]:
|
|
721
|
+
if v.casefold() not in dict_b_lowered[k.casefold()]:
|
|
722
|
+
diff.append(v)
|
|
723
|
+
if diff:
|
|
724
|
+
dict_diff.update({k: diff})
|
|
725
|
+
else:
|
|
726
|
+
dict_diff.update({k: dict_a[k]})
|
|
727
|
+
return dict_diff
|
|
728
|
+
|
|
729
|
+
@contextmember()
|
|
730
|
+
@staticmethod
|
|
731
|
+
def local_md5(value: str) -> str:
|
|
732
|
+
"""Calculates an MD5 hash of the given string.
|
|
733
|
+
It's called "local_md5" to emphasize that it runs locally in dbt (in jinja context) and not an MD5 SQL command.
|
|
734
|
+
|
|
735
|
+
:param value: The value to hash
|
|
736
|
+
|
|
737
|
+
Usage:
|
|
738
|
+
{% set value_hash = local_md5("hello world") %}
|
|
739
|
+
"""
|
|
740
|
+
return utils.md5(value)
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def generate_base_context(cli_vars: Dict[str, Any]) -> Dict[str, Any]:
|
|
744
|
+
ctx = BaseContext(cli_vars)
|
|
745
|
+
# This is not a Mashumaro to_dict call
|
|
746
|
+
return ctx.to_dict()
|