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/exceptions.py
ADDED
|
@@ -0,0 +1,1487 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Union
|
|
5
|
+
|
|
6
|
+
from dvt.node_types import REFABLE_NODE_TYPES, AccessType, NodeType
|
|
7
|
+
|
|
8
|
+
from dbt_common.constants import SECRET_ENV_PREFIX
|
|
9
|
+
from dbt_common.dataclass_schema import ValidationError
|
|
10
|
+
from dbt_common.exceptions import (
|
|
11
|
+
CommandResultError,
|
|
12
|
+
CompilationError,
|
|
13
|
+
DbtConfigError,
|
|
14
|
+
DbtInternalError,
|
|
15
|
+
DbtRuntimeError,
|
|
16
|
+
DbtValidationError,
|
|
17
|
+
env_secrets,
|
|
18
|
+
scrub_secrets,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
import agate
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ContractBreakingChangeError(DbtRuntimeError):
|
|
26
|
+
CODE = 10016
|
|
27
|
+
MESSAGE = "Breaking Change to Contract"
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
breaking_changes: List[str],
|
|
32
|
+
node=None,
|
|
33
|
+
) -> None:
|
|
34
|
+
self.breaking_changes = breaking_changes
|
|
35
|
+
super().__init__(self.message(), node)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def type(self):
|
|
39
|
+
return "Breaking change to contract"
|
|
40
|
+
|
|
41
|
+
def message(self):
|
|
42
|
+
reasons = "\n - ".join(self.breaking_changes)
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
"While comparing to previous project state, dbt detected a breaking change to an enforced contract."
|
|
46
|
+
f"\n - {reasons}\n"
|
|
47
|
+
"Consider making an additive (non-breaking) change instead, if possible.\n"
|
|
48
|
+
"Otherwise, create a new model version: https://docs.getdbt.com/docs/collaborate/govern/model-versions"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ParsingError(DbtRuntimeError):
|
|
53
|
+
CODE = 10015
|
|
54
|
+
MESSAGE = "Parsing Error"
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def type(self):
|
|
58
|
+
return "Parsing"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class dbtPluginError(DbtRuntimeError):
|
|
62
|
+
CODE = 10020
|
|
63
|
+
MESSAGE = "Plugin Error"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# TODO: this isn't raised in the core codebase. Is it raised elsewhere?
|
|
67
|
+
class JSONValidationError(DbtValidationError):
|
|
68
|
+
def __init__(self, typename, errors) -> None:
|
|
69
|
+
self.typename = typename
|
|
70
|
+
self.errors = errors
|
|
71
|
+
self.errors_message = ", ".join(errors)
|
|
72
|
+
msg = f'Invalid arguments passed to "{self.typename}" instance: {self.errors_message}'
|
|
73
|
+
super().__init__(msg)
|
|
74
|
+
|
|
75
|
+
def __reduce__(self):
|
|
76
|
+
# see https://stackoverflow.com/a/36342588 for why this is necessary
|
|
77
|
+
return (JSONValidationError, (self.typename, self.errors))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AliasError(DbtValidationError):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class DependencyError(Exception):
|
|
85
|
+
CODE = 10006
|
|
86
|
+
MESSAGE = "Dependency Error"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class FailFastError(DbtRuntimeError):
|
|
90
|
+
CODE = 10013
|
|
91
|
+
MESSAGE = "FailFast Error"
|
|
92
|
+
|
|
93
|
+
def __init__(self, msg: str, result=None, node=None) -> None:
|
|
94
|
+
super().__init__(msg=msg, node=node)
|
|
95
|
+
self.result = result
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def type(self):
|
|
99
|
+
return "FailFast"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class DbtProjectError(DbtConfigError):
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class DbtSelectorsError(DbtConfigError):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class DbtProfileError(DbtConfigError):
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class DbtExclusivePropertyUseError(DbtConfigError):
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class InvalidSelectorError(DbtRuntimeError):
|
|
119
|
+
def __init__(self, name: str) -> None:
|
|
120
|
+
self.name = name
|
|
121
|
+
super().__init__(name)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class DuplicateYamlKeyError(CompilationError):
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# compilation level exceptions
|
|
129
|
+
class GraphDependencyNotFoundError(CompilationError):
|
|
130
|
+
def __init__(self, node, dependency: str) -> None:
|
|
131
|
+
self.node = node
|
|
132
|
+
self.dependency = dependency
|
|
133
|
+
super().__init__(msg=self.get_message())
|
|
134
|
+
|
|
135
|
+
def get_message(self) -> str:
|
|
136
|
+
msg = f"'{self.node.unique_id}' depends on '{self.dependency}' which is not in the graph!"
|
|
137
|
+
return msg
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class ForeignKeyConstraintToSyntaxError(CompilationError):
|
|
141
|
+
def __init__(self, node, expression: str) -> None:
|
|
142
|
+
self.expression = expression
|
|
143
|
+
self.node = node
|
|
144
|
+
super().__init__(msg=self.get_message())
|
|
145
|
+
|
|
146
|
+
def get_message(self) -> str:
|
|
147
|
+
msg = f"'{self.node.unique_id}' defines a foreign key constraint 'to' expression which is not valid 'ref' or 'source' syntax: {self.expression}."
|
|
148
|
+
|
|
149
|
+
return msg
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# client level exceptions
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class NoSupportedLanguagesFoundError(CompilationError):
|
|
156
|
+
def __init__(self, node) -> None:
|
|
157
|
+
self.node = node
|
|
158
|
+
self.msg = f"No supported_languages found in materialization macro {self.node.name}"
|
|
159
|
+
super().__init__(msg=self.msg)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class MaterializtionMacroNotUsedError(CompilationError):
|
|
163
|
+
def __init__(self, node) -> None:
|
|
164
|
+
self.node = node
|
|
165
|
+
self.msg = "Only materialization macros can be used with this function"
|
|
166
|
+
super().__init__(msg=self.msg)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class MacroNamespaceNotStringError(CompilationError):
|
|
170
|
+
def __init__(self, kwarg_type: Any) -> None:
|
|
171
|
+
self.kwarg_type = kwarg_type
|
|
172
|
+
super().__init__(msg=self.get_message())
|
|
173
|
+
|
|
174
|
+
def get_message(self) -> str:
|
|
175
|
+
msg = (
|
|
176
|
+
"The macro_namespace parameter to adapter.dispatch "
|
|
177
|
+
f"is a {self.kwarg_type}, not a string"
|
|
178
|
+
)
|
|
179
|
+
return msg
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class UnknownGitCloningProblemError(DbtRuntimeError):
|
|
183
|
+
def __init__(self, repo: str) -> None:
|
|
184
|
+
self.repo = scrub_secrets(repo, env_secrets())
|
|
185
|
+
super().__init__(msg=self.get_message())
|
|
186
|
+
|
|
187
|
+
def get_message(self) -> str:
|
|
188
|
+
msg = f"""\
|
|
189
|
+
Something went wrong while cloning {self.repo}
|
|
190
|
+
Check the debug logs for more information
|
|
191
|
+
"""
|
|
192
|
+
return msg
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class NoAdaptersAvailableError(DbtRuntimeError):
|
|
196
|
+
def __init__(self) -> None:
|
|
197
|
+
super().__init__(msg=self.get_message())
|
|
198
|
+
|
|
199
|
+
def get_message(self) -> str:
|
|
200
|
+
msg = "No adapters available. Learn how to install an adapter by going to https://docs.getdbt.com/docs/connect-adapters#install-using-the-cli"
|
|
201
|
+
return msg
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class BadSpecError(DbtInternalError):
|
|
205
|
+
def __init__(self, repo, revision, error) -> None:
|
|
206
|
+
self.repo = repo
|
|
207
|
+
self.revision = revision
|
|
208
|
+
self.stderr = scrub_secrets(error.stderr.strip(), env_secrets())
|
|
209
|
+
super().__init__(msg=self.get_message())
|
|
210
|
+
|
|
211
|
+
def get_message(self) -> str:
|
|
212
|
+
msg = f"Error checking out spec='{self.revision}' for repo {self.repo}\n{self.stderr}"
|
|
213
|
+
return msg
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class GitCloningError(DbtInternalError):
|
|
217
|
+
def __init__(self, repo: str, revision: str, error: CommandResultError) -> None:
|
|
218
|
+
self.repo = repo
|
|
219
|
+
self.revision = revision
|
|
220
|
+
self.error = error
|
|
221
|
+
super().__init__(msg=self.get_message())
|
|
222
|
+
|
|
223
|
+
def get_message(self) -> str:
|
|
224
|
+
stderr = self.error.stderr.strip()
|
|
225
|
+
if "usage: git" in stderr:
|
|
226
|
+
stderr = stderr.split("\nusage: git")[0]
|
|
227
|
+
if re.match("fatal: destination path '(.+)' already exists", stderr):
|
|
228
|
+
self.error.cmd = list(scrub_secrets(str(self.error.cmd), env_secrets()))
|
|
229
|
+
raise self.error
|
|
230
|
+
|
|
231
|
+
msg = f"Error checking out spec='{self.revision}' for repo {self.repo}\n{stderr}"
|
|
232
|
+
return scrub_secrets(msg, env_secrets())
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class GitCheckoutError(BadSpecError):
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class OperationError(CompilationError):
|
|
240
|
+
def __init__(self, operation_name) -> None:
|
|
241
|
+
self.operation_name = operation_name
|
|
242
|
+
super().__init__(msg=self.get_message())
|
|
243
|
+
|
|
244
|
+
def get_message(self) -> str:
|
|
245
|
+
msg = (
|
|
246
|
+
f"dbt encountered an error when attempting to create a {self.operation_name}. "
|
|
247
|
+
"If this error persists, please create an issue at: \n\n"
|
|
248
|
+
"https://github.com/dbt-labs/dbt-core"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return msg
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# context level exceptions
|
|
255
|
+
class ZipStrictWrongTypeError(CompilationError):
|
|
256
|
+
def __init__(self, exc) -> None:
|
|
257
|
+
self.exc = exc
|
|
258
|
+
msg = str(self.exc)
|
|
259
|
+
super().__init__(msg=msg)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class SetStrictWrongTypeError(CompilationError):
|
|
263
|
+
def __init__(self, exc) -> None:
|
|
264
|
+
self.exc = exc
|
|
265
|
+
msg = str(self.exc)
|
|
266
|
+
super().__init__(msg=msg)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class LoadAgateTableValueError(CompilationError):
|
|
270
|
+
def __init__(self, exc: ValueError, node) -> None:
|
|
271
|
+
self.exc = exc
|
|
272
|
+
self.node = node
|
|
273
|
+
msg = str(self.exc)
|
|
274
|
+
super().__init__(msg=msg)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class LoadAgateTableNotSeedError(CompilationError):
|
|
278
|
+
def __init__(self, resource_type, node) -> None:
|
|
279
|
+
self.resource_type = resource_type
|
|
280
|
+
self.node = node
|
|
281
|
+
msg = f"can only load_agate_table for seeds (got a {self.resource_type})"
|
|
282
|
+
super().__init__(msg=msg)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class PackageNotInDepsError(CompilationError):
|
|
286
|
+
def __init__(self, package_name: str, node) -> None:
|
|
287
|
+
self.package_name = package_name
|
|
288
|
+
self.node = node
|
|
289
|
+
msg = f"Node package named {self.package_name} not found!"
|
|
290
|
+
super().__init__(msg=msg)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class OperationsCannotRefEphemeralNodesError(CompilationError):
|
|
294
|
+
def __init__(self, target_name: str, node) -> None:
|
|
295
|
+
self.target_name = target_name
|
|
296
|
+
self.node = node
|
|
297
|
+
msg = f"Operations can not ref() ephemeral nodes, but {target_name} is ephemeral"
|
|
298
|
+
super().__init__(msg=msg)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class PersistDocsValueTypeError(CompilationError):
|
|
302
|
+
def __init__(self, persist_docs: Any) -> None:
|
|
303
|
+
self.persist_docs = persist_docs
|
|
304
|
+
msg = (
|
|
305
|
+
"Invalid value provided for 'persist_docs'. Expected dict "
|
|
306
|
+
f"but received {type(self.persist_docs)}"
|
|
307
|
+
)
|
|
308
|
+
super().__init__(msg=msg)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class InlineModelConfigError(CompilationError):
|
|
312
|
+
def __init__(self, node) -> None:
|
|
313
|
+
self.node = node
|
|
314
|
+
msg = "Invalid inline model config"
|
|
315
|
+
super().__init__(msg=msg)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class ConflictingConfigKeysError(CompilationError):
|
|
319
|
+
def __init__(self, oldkey: str, newkey: str, node) -> None:
|
|
320
|
+
self.oldkey = oldkey
|
|
321
|
+
self.newkey = newkey
|
|
322
|
+
self.node = node
|
|
323
|
+
msg = f'Invalid config, has conflicting keys "{self.oldkey}" and "{self.newkey}"'
|
|
324
|
+
super().__init__(msg=msg)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class NumberSourceArgsError(CompilationError):
|
|
328
|
+
def __init__(self, args, node) -> None:
|
|
329
|
+
self.args = args
|
|
330
|
+
self.node = node
|
|
331
|
+
msg = f"source() takes exactly two arguments ({len(self.args)} given)"
|
|
332
|
+
super().__init__(msg=msg)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class RequiredVarNotFoundError(CompilationError):
|
|
336
|
+
def __init__(self, var_name: str, merged: Dict, node) -> None:
|
|
337
|
+
self.var_name = var_name
|
|
338
|
+
self.merged = merged
|
|
339
|
+
self.node = node
|
|
340
|
+
super().__init__(msg=self.get_message())
|
|
341
|
+
|
|
342
|
+
def get_message(self) -> str:
|
|
343
|
+
if self.node is not None:
|
|
344
|
+
node_name = self.node.name
|
|
345
|
+
else:
|
|
346
|
+
node_name = "<Configuration>"
|
|
347
|
+
|
|
348
|
+
dct = {k: self.merged[k] for k in self.merged}
|
|
349
|
+
pretty_vars = json.dumps(dct, sort_keys=True, indent=4)
|
|
350
|
+
|
|
351
|
+
msg = f"Required var '{self.var_name}' not found in config:\nVars supplied to {node_name} = {pretty_vars}"
|
|
352
|
+
return scrub_secrets(msg, self.var_secrets())
|
|
353
|
+
|
|
354
|
+
def var_secrets(self) -> List[str]:
|
|
355
|
+
return [v for k, v in self.merged.items() if k.startswith(SECRET_ENV_PREFIX) and v.strip()]
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class PackageNotFoundForMacroError(CompilationError):
|
|
359
|
+
def __init__(self, package_name: str) -> None:
|
|
360
|
+
self.package_name = package_name
|
|
361
|
+
msg = f"Could not find package '{self.package_name}'"
|
|
362
|
+
super().__init__(msg=msg)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class SecretEnvVarLocationError(ParsingError):
|
|
366
|
+
def __init__(self, env_var_name: str) -> None:
|
|
367
|
+
self.env_var_name = env_var_name
|
|
368
|
+
super().__init__(msg=self.get_message())
|
|
369
|
+
|
|
370
|
+
def get_message(self) -> str:
|
|
371
|
+
msg = (
|
|
372
|
+
"Secret env vars are allowed only in profiles.yml or packages.yml. "
|
|
373
|
+
f"Found '{self.env_var_name}' referenced elsewhere."
|
|
374
|
+
)
|
|
375
|
+
return msg
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class BooleanError(CompilationError):
|
|
379
|
+
def __init__(self, return_value: Any, macro_name: str) -> None:
|
|
380
|
+
self.return_value = return_value
|
|
381
|
+
self.macro_name = macro_name
|
|
382
|
+
super().__init__(msg=self.get_message())
|
|
383
|
+
|
|
384
|
+
def get_message(self) -> str:
|
|
385
|
+
msg = (
|
|
386
|
+
f"Macro '{self.macro_name}' returns '{self.return_value}'. It is not type 'bool' "
|
|
387
|
+
"and cannot not be converted reliably to a bool."
|
|
388
|
+
)
|
|
389
|
+
return msg
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class RefArgsError(CompilationError):
|
|
393
|
+
def __init__(self, node, args) -> None:
|
|
394
|
+
self.node = node
|
|
395
|
+
self.args = args
|
|
396
|
+
super().__init__(msg=self.get_message())
|
|
397
|
+
|
|
398
|
+
def get_message(self) -> str:
|
|
399
|
+
msg = f"ref() takes at most two arguments ({len(self.args)} given)"
|
|
400
|
+
return msg
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
class MetricArgsError(CompilationError):
|
|
404
|
+
def __init__(self, node, args) -> None:
|
|
405
|
+
self.node = node
|
|
406
|
+
self.args = args
|
|
407
|
+
super().__init__(msg=self.get_message())
|
|
408
|
+
|
|
409
|
+
def get_message(self) -> str:
|
|
410
|
+
msg = f"metric() takes at most two arguments ({len(self.args)} given)"
|
|
411
|
+
return msg
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class RefBadContextError(CompilationError):
|
|
415
|
+
def __init__(self, node, args) -> None:
|
|
416
|
+
self.node = node
|
|
417
|
+
self.args = args.positional_args # type: ignore
|
|
418
|
+
self.kwargs = args.keyword_args # type: ignore
|
|
419
|
+
super().__init__(msg=self.get_message())
|
|
420
|
+
|
|
421
|
+
def get_message(self) -> str:
|
|
422
|
+
# This explicitly references model['name'], instead of model['alias'], for
|
|
423
|
+
# better error messages. Ex. If models foo_users and bar_users are aliased
|
|
424
|
+
# to 'users', in their respective schemas, then you would want to see
|
|
425
|
+
# 'bar_users' in your error messge instead of just 'users'.
|
|
426
|
+
if isinstance(self.node, dict):
|
|
427
|
+
model_name = self.node["name"]
|
|
428
|
+
else:
|
|
429
|
+
model_name = self.node.name
|
|
430
|
+
|
|
431
|
+
ref_args = ", ".join("'{}'".format(a) for a in self.args)
|
|
432
|
+
|
|
433
|
+
keyword_args = ""
|
|
434
|
+
if self.kwargs:
|
|
435
|
+
keyword_args = ", ".join(
|
|
436
|
+
"{}='{}'".format(k, v) for k, v in self.kwargs.items() # type: ignore
|
|
437
|
+
)
|
|
438
|
+
keyword_args = "," + keyword_args
|
|
439
|
+
|
|
440
|
+
ref_string = f"{{{{ ref({ref_args}{keyword_args}) }}}}"
|
|
441
|
+
|
|
442
|
+
msg = f"""dbt was unable to infer all dependencies for the model "{model_name}".
|
|
443
|
+
This typically happens when ref() is placed within a conditional block.
|
|
444
|
+
|
|
445
|
+
To fix this, add the following hint to the top of the model "{model_name}":
|
|
446
|
+
|
|
447
|
+
-- depends_on: {ref_string}"""
|
|
448
|
+
|
|
449
|
+
return msg
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
class DocArgsError(CompilationError):
|
|
453
|
+
def __init__(self, node, args) -> None:
|
|
454
|
+
self.node = node
|
|
455
|
+
self.args = args
|
|
456
|
+
super().__init__(msg=self.get_message())
|
|
457
|
+
|
|
458
|
+
def get_message(self) -> str:
|
|
459
|
+
msg = f"doc() takes at most two arguments ({len(self.args)} given)"
|
|
460
|
+
return msg
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class DocTargetNotFoundError(CompilationError):
|
|
464
|
+
def __init__(
|
|
465
|
+
self, node, target_doc_name: str, target_doc_package: Optional[str] = None
|
|
466
|
+
) -> None:
|
|
467
|
+
self.node = node
|
|
468
|
+
self.target_doc_name = target_doc_name
|
|
469
|
+
self.target_doc_package = target_doc_package
|
|
470
|
+
super().__init__(msg=self.get_message())
|
|
471
|
+
|
|
472
|
+
def get_message(self) -> str:
|
|
473
|
+
target_package_string = ""
|
|
474
|
+
if self.target_doc_package is not None:
|
|
475
|
+
target_package_string = f"in package '{self.target_doc_package}' "
|
|
476
|
+
msg = f"Documentation for '{self.node.unique_id}' depends on doc '{self.target_doc_name}' {target_package_string} which was not found"
|
|
477
|
+
return msg
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
class MacroDispatchArgError(CompilationError):
|
|
481
|
+
def __init__(self, macro_name: str) -> None:
|
|
482
|
+
self.macro_name = macro_name
|
|
483
|
+
super().__init__(msg=self.get_message())
|
|
484
|
+
|
|
485
|
+
def get_message(self) -> str:
|
|
486
|
+
msg = f"""\
|
|
487
|
+
The "packages" argument of adapter.dispatch() has been deprecated.
|
|
488
|
+
Use the "macro_namespace" argument instead.
|
|
489
|
+
|
|
490
|
+
Raised during dispatch for: {self.macro_name}
|
|
491
|
+
|
|
492
|
+
For more information, see:
|
|
493
|
+
|
|
494
|
+
https://docs.getdbt.com/reference/dbt-jinja-functions/dispatch
|
|
495
|
+
"""
|
|
496
|
+
return msg
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
class DuplicateMacroNameError(CompilationError):
|
|
500
|
+
def __init__(self, node_1, node_2, namespace: str) -> None:
|
|
501
|
+
self.node_1 = node_1
|
|
502
|
+
self.node_2 = node_2
|
|
503
|
+
self.namespace = namespace
|
|
504
|
+
super().__init__(msg=self.get_message())
|
|
505
|
+
|
|
506
|
+
def get_message(self) -> str:
|
|
507
|
+
duped_name = self.node_1.name
|
|
508
|
+
if self.node_1.package_name != self.node_2.package_name:
|
|
509
|
+
extra = f' ("{self.node_1.package_name}" and "{self.node_2.package_name}" are both in the "{self.namespace}" namespace)'
|
|
510
|
+
else:
|
|
511
|
+
extra = ""
|
|
512
|
+
|
|
513
|
+
msg = (
|
|
514
|
+
f'dbt found two macros with the name "{duped_name}" in the namespace "{self.namespace}"{extra}. '
|
|
515
|
+
"Since these macros have the same name and exist in the same "
|
|
516
|
+
"namespace, dbt will be unable to decide which to call. To fix this, "
|
|
517
|
+
f"change the name of one of these macros:\n- {self.node_1.unique_id} "
|
|
518
|
+
f"({self.node_1.original_file_path})\n- {self.node_2.unique_id} ({self.node_2.original_file_path})"
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
return msg
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
class MacroResultAlreadyLoadedError(CompilationError):
|
|
525
|
+
def __init__(self, result_name) -> None:
|
|
526
|
+
self.result_name = result_name
|
|
527
|
+
super().__init__(msg=self.get_message())
|
|
528
|
+
|
|
529
|
+
def get_message(self) -> str:
|
|
530
|
+
msg = f"The 'statement' result named '{self.result_name}' has already been loaded into a variable"
|
|
531
|
+
|
|
532
|
+
return msg
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
# parser level exceptions
|
|
536
|
+
class DictParseError(ParsingError):
|
|
537
|
+
def __init__(self, exc: ValidationError, node) -> None:
|
|
538
|
+
self.exc = exc
|
|
539
|
+
self.node = node
|
|
540
|
+
msg = self.validator_error_message(exc)
|
|
541
|
+
super().__init__(msg=msg)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
class ConfigUpdateError(ParsingError):
|
|
545
|
+
def __init__(self, exc: ValidationError, node) -> None:
|
|
546
|
+
self.exc = exc
|
|
547
|
+
self.node = node
|
|
548
|
+
msg = self.validator_error_message(exc)
|
|
549
|
+
super().__init__(msg=msg)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class PythonParsingError(ParsingError):
|
|
553
|
+
def __init__(self, exc: SyntaxError, node) -> None:
|
|
554
|
+
self.exc = exc
|
|
555
|
+
self.node = node
|
|
556
|
+
super().__init__(msg=self.get_message())
|
|
557
|
+
|
|
558
|
+
def get_message(self) -> str:
|
|
559
|
+
validated_exc = self.validator_error_message(self.exc)
|
|
560
|
+
msg = f"{validated_exc}\n{self.exc.text}"
|
|
561
|
+
return msg
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
class PythonLiteralEvalError(ParsingError):
|
|
565
|
+
def __init__(self, exc: Exception, node) -> None:
|
|
566
|
+
self.exc = exc
|
|
567
|
+
self.node = node
|
|
568
|
+
super().__init__(msg=self.get_message())
|
|
569
|
+
|
|
570
|
+
def get_message(self) -> str:
|
|
571
|
+
msg = (
|
|
572
|
+
f"Error when trying to literal_eval an arg to dbt.ref(), dbt.source(), dbt.config() or dbt.config.get() \n{self.exc}\n"
|
|
573
|
+
"https://docs.python.org/3/library/ast.html#ast.literal_eval\n"
|
|
574
|
+
"In dbt python model, `dbt.ref`, `dbt.source`, `dbt.config`, `dbt.config.get` function args only support Python literal structures"
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
return msg
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
class ModelConfigError(ParsingError):
|
|
581
|
+
def __init__(self, exc: ValidationError, node) -> None:
|
|
582
|
+
self.msg = self.validator_error_message(exc)
|
|
583
|
+
self.node = node
|
|
584
|
+
super().__init__(msg=self.msg)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
class YamlParseListError(ParsingError):
|
|
588
|
+
def __init__(
|
|
589
|
+
self,
|
|
590
|
+
path: str,
|
|
591
|
+
key: str,
|
|
592
|
+
yaml_data: List,
|
|
593
|
+
cause,
|
|
594
|
+
) -> None:
|
|
595
|
+
self.path = path
|
|
596
|
+
self.key = key
|
|
597
|
+
self.yaml_data = yaml_data
|
|
598
|
+
self.cause = cause
|
|
599
|
+
super().__init__(msg=self.get_message())
|
|
600
|
+
|
|
601
|
+
def get_message(self) -> str:
|
|
602
|
+
if isinstance(self.cause, str):
|
|
603
|
+
reason = self.cause
|
|
604
|
+
elif isinstance(self.cause, ValidationError):
|
|
605
|
+
reason = self.validator_error_message(self.cause)
|
|
606
|
+
else:
|
|
607
|
+
reason = self.cause.msg
|
|
608
|
+
msg = f"Invalid {self.key} config given in {self.path} @ {self.key}: {self.yaml_data} - {reason}"
|
|
609
|
+
return msg
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
class YamlParseDictError(ParsingError):
|
|
613
|
+
def __init__(
|
|
614
|
+
self,
|
|
615
|
+
path: str,
|
|
616
|
+
key: str,
|
|
617
|
+
yaml_data: Dict[str, Any],
|
|
618
|
+
cause,
|
|
619
|
+
) -> None:
|
|
620
|
+
self.path = path
|
|
621
|
+
self.key = key
|
|
622
|
+
self.yaml_data = yaml_data
|
|
623
|
+
self.cause = cause
|
|
624
|
+
super().__init__(msg=self.get_message())
|
|
625
|
+
|
|
626
|
+
def get_message(self) -> str:
|
|
627
|
+
if isinstance(self.cause, str):
|
|
628
|
+
reason = self.cause
|
|
629
|
+
elif isinstance(self.cause, ValidationError):
|
|
630
|
+
reason = self.validator_error_message(self.cause)
|
|
631
|
+
else:
|
|
632
|
+
reason = self.cause.msg
|
|
633
|
+
msg = f"Invalid {self.key} config given in {self.path} @ {self.key}: {self.yaml_data} - {reason}"
|
|
634
|
+
return msg
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
class YamlLoadError(ParsingError):
|
|
638
|
+
def __init__(
|
|
639
|
+
self,
|
|
640
|
+
path: str,
|
|
641
|
+
exc: DbtValidationError,
|
|
642
|
+
project_name: Optional[str] = None,
|
|
643
|
+
) -> None:
|
|
644
|
+
self.project_name = project_name
|
|
645
|
+
self.path = path
|
|
646
|
+
self.exc = exc
|
|
647
|
+
super().__init__(msg=self.get_message())
|
|
648
|
+
|
|
649
|
+
def get_message(self) -> str:
|
|
650
|
+
reason = self.validator_error_message(self.exc)
|
|
651
|
+
|
|
652
|
+
msg = f"Error reading {self.project_name}: {self.path} - {reason}"
|
|
653
|
+
|
|
654
|
+
return msg
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
class TestConfigError(ParsingError):
|
|
658
|
+
def __init__(self, exc: ValidationError, node) -> None:
|
|
659
|
+
self.msg = self.validator_error_message(exc)
|
|
660
|
+
self.node = node
|
|
661
|
+
super().__init__(msg=self.msg)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
class SchemaConfigError(ParsingError):
|
|
665
|
+
def __init__(self, exc: ValidationError, node) -> None:
|
|
666
|
+
self.msg = self.validator_error_message(exc)
|
|
667
|
+
self.node = node
|
|
668
|
+
super().__init__(msg=self.msg)
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
class SnapshopConfigError(ParsingError):
|
|
672
|
+
def __init__(self, exc: ValidationError, node) -> None:
|
|
673
|
+
self.msg = self.validator_error_message(exc)
|
|
674
|
+
self.node = node
|
|
675
|
+
super().__init__(msg=self.msg)
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
class DbtReferenceError(ParsingError):
|
|
679
|
+
def __init__(self, unique_id: str, ref_unique_id: str, access: AccessType, scope: str) -> None:
|
|
680
|
+
self.unique_id = unique_id
|
|
681
|
+
self.ref_unique_id = ref_unique_id
|
|
682
|
+
self.access = access
|
|
683
|
+
self.scope = scope
|
|
684
|
+
self.scope_type = "group" if self.access == AccessType.Private else "package"
|
|
685
|
+
super().__init__(msg=self.get_message())
|
|
686
|
+
|
|
687
|
+
def get_message(self) -> str:
|
|
688
|
+
return (
|
|
689
|
+
f"Node {self.unique_id} attempted to reference node {self.ref_unique_id}, "
|
|
690
|
+
f"which is not allowed because the referenced node is {self.access} to the '{self.scope}' {self.scope_type}."
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
class InvalidAccessTypeError(ParsingError):
|
|
695
|
+
def __init__(
|
|
696
|
+
self, unique_id: str, field_value: str, materialization: Optional[str] = None
|
|
697
|
+
) -> None:
|
|
698
|
+
self.unique_id = unique_id
|
|
699
|
+
self.field_value = field_value
|
|
700
|
+
self.materialization = materialization
|
|
701
|
+
|
|
702
|
+
with_materialization = (
|
|
703
|
+
f"with '{self.materialization}' materialization " if self.materialization else ""
|
|
704
|
+
)
|
|
705
|
+
msg = f"Node {self.unique_id} {with_materialization}has an invalid value ({self.field_value}) for the access field"
|
|
706
|
+
super().__init__(msg=msg)
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
class InvalidUnitTestGivenInput(ParsingError):
|
|
710
|
+
def __init__(self, input: str) -> None:
|
|
711
|
+
msg = f"Unit test given inputs must be either a 'ref', 'source' or 'this' call. Got: '{input}'."
|
|
712
|
+
super().__init__(msg=msg)
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
class SameKeyNestedError(CompilationError):
|
|
716
|
+
def __init__(self) -> None:
|
|
717
|
+
msg = "Test cannot have the same key at the top-level and in config"
|
|
718
|
+
super().__init__(msg=msg)
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
class TestArgIncludesModelError(CompilationError):
|
|
722
|
+
def __init__(self) -> None:
|
|
723
|
+
msg = 'Test arguments include "model", which is a reserved argument'
|
|
724
|
+
super().__init__(msg=msg)
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
class UnexpectedTestNamePatternError(CompilationError):
|
|
728
|
+
def __init__(self, test_name: str) -> None:
|
|
729
|
+
self.test_name = test_name
|
|
730
|
+
msg = f"Test name string did not match expected pattern: {self.test_name}"
|
|
731
|
+
super().__init__(msg=msg)
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
class CustomMacroPopulatingConfigValueError(CompilationError):
|
|
735
|
+
def __init__(
|
|
736
|
+
self,
|
|
737
|
+
target_name: str,
|
|
738
|
+
name: str,
|
|
739
|
+
key: str,
|
|
740
|
+
err_msg: str,
|
|
741
|
+
column_name: Optional[str] = None,
|
|
742
|
+
) -> None:
|
|
743
|
+
self.target_name = target_name
|
|
744
|
+
self.column_name = column_name
|
|
745
|
+
self.name = name
|
|
746
|
+
self.key = key
|
|
747
|
+
self.err_msg = err_msg
|
|
748
|
+
super().__init__(msg=self.get_message())
|
|
749
|
+
|
|
750
|
+
def get_message(self) -> str:
|
|
751
|
+
# Generic tests do not include custom macros in the Jinja
|
|
752
|
+
# rendering context, so this will almost always fail. As it
|
|
753
|
+
# currently stands, the error message is inscrutable, which
|
|
754
|
+
# has caused issues for some projects migrating from
|
|
755
|
+
# pre-0.20.0 to post-0.20.0.
|
|
756
|
+
# See https://github.com/dbt-labs/dbt-core/issues/4103
|
|
757
|
+
# and https://github.com/dbt-labs/dbt-core/issues/5294
|
|
758
|
+
|
|
759
|
+
msg = (
|
|
760
|
+
f"The {self.target_name}.{self.column_name} column's "
|
|
761
|
+
f'"{self.name}" test references an undefined '
|
|
762
|
+
f"macro in its {self.key} configuration argument. "
|
|
763
|
+
f"The macro {self.err_msg}.\n"
|
|
764
|
+
"Please note that the generic test configuration parser "
|
|
765
|
+
"currently does not support using custom macros to "
|
|
766
|
+
"populate configuration values"
|
|
767
|
+
)
|
|
768
|
+
return msg
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
class TagsNotListOfStringsError(CompilationError):
|
|
772
|
+
def __init__(self, tags: Any) -> None:
|
|
773
|
+
self.tags = tags
|
|
774
|
+
msg = f"got {self.tags} ({type(self.tags)}) for tags, expected a list of strings"
|
|
775
|
+
super().__init__(msg=msg)
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
class TagNotStringError(CompilationError):
|
|
779
|
+
def __init__(self, tag: Any) -> None:
|
|
780
|
+
self.tag = tag
|
|
781
|
+
msg = f"got {self.tag} ({type(self.tag)}) for tag, expected a str"
|
|
782
|
+
super().__init__(msg=msg)
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
class TestNameNotStringError(ParsingError):
|
|
786
|
+
def __init__(self, test_name: Any) -> None:
|
|
787
|
+
self.test_name = test_name
|
|
788
|
+
super().__init__(msg=self.get_message())
|
|
789
|
+
|
|
790
|
+
def get_message(self) -> str:
|
|
791
|
+
msg = f"test name must be a str, got {type(self.test_name)} (value {self.test_name})"
|
|
792
|
+
return msg
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
class TestArgsNotDictError(ParsingError):
|
|
796
|
+
def __init__(self, test_args: Any) -> None:
|
|
797
|
+
self.test_args = test_args
|
|
798
|
+
super().__init__(msg=self.get_message())
|
|
799
|
+
|
|
800
|
+
def get_message(self) -> str:
|
|
801
|
+
msg = f"test arguments must be a dict, got {type(self.test_args)} (value {self.test_args})"
|
|
802
|
+
return msg
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
class TestDefinitionDictLengthError(ParsingError):
|
|
806
|
+
def __init__(self, test):
|
|
807
|
+
self.test = test
|
|
808
|
+
super().__init__(msg=self.get_message())
|
|
809
|
+
|
|
810
|
+
def get_message(self) -> str:
|
|
811
|
+
msg = (
|
|
812
|
+
"test definition dictionary must have exactly one key, got"
|
|
813
|
+
f" {self.test} instead ({len(self.test)} keys)"
|
|
814
|
+
)
|
|
815
|
+
return msg
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
class TestTypeError(ParsingError):
|
|
819
|
+
def __init__(self, test: Any):
|
|
820
|
+
self.test = test
|
|
821
|
+
super().__init__(msg=self.get_message())
|
|
822
|
+
|
|
823
|
+
def get_message(self) -> str:
|
|
824
|
+
msg = f"test must be dict or str, got {type(self.test)} (value {self.test})"
|
|
825
|
+
return msg
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
# This is triggered across multiple files
|
|
829
|
+
class EnvVarMissingError(ParsingError):
|
|
830
|
+
def __init__(self, var: str):
|
|
831
|
+
self.var = var
|
|
832
|
+
super().__init__(msg=self.get_message())
|
|
833
|
+
|
|
834
|
+
def get_message(self) -> str:
|
|
835
|
+
msg = f"Env var required but not provided: '{self.var}'"
|
|
836
|
+
return msg
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
class TargetNotFoundError(CompilationError):
|
|
840
|
+
def __init__(
|
|
841
|
+
self,
|
|
842
|
+
node,
|
|
843
|
+
target_name: str,
|
|
844
|
+
target_kind: str,
|
|
845
|
+
target_package: Optional[str] = None,
|
|
846
|
+
target_version: Optional[Union[str, float]] = None,
|
|
847
|
+
disabled: Optional[bool] = None,
|
|
848
|
+
):
|
|
849
|
+
self.node = node
|
|
850
|
+
self.target_name = target_name
|
|
851
|
+
self.target_kind = target_kind
|
|
852
|
+
self.target_package = target_package
|
|
853
|
+
self.target_version = target_version
|
|
854
|
+
self.disabled = disabled
|
|
855
|
+
super().__init__(msg=self.get_message())
|
|
856
|
+
|
|
857
|
+
def get_message(self) -> str:
|
|
858
|
+
original_file_path = self.node.original_file_path
|
|
859
|
+
unique_id = self.node.unique_id
|
|
860
|
+
resource_type_title = self.node.resource_type.title()
|
|
861
|
+
|
|
862
|
+
if self.disabled is None:
|
|
863
|
+
reason = "was not found or is disabled"
|
|
864
|
+
elif self.disabled is True:
|
|
865
|
+
reason = "is disabled"
|
|
866
|
+
else:
|
|
867
|
+
reason = "was not found"
|
|
868
|
+
|
|
869
|
+
target_version_string = ""
|
|
870
|
+
if self.target_version is not None:
|
|
871
|
+
target_version_string = f"with version '{self.target_version}' "
|
|
872
|
+
|
|
873
|
+
target_package_string = ""
|
|
874
|
+
if self.target_package is not None:
|
|
875
|
+
target_package_string = f"in package or project '{self.target_package}' "
|
|
876
|
+
|
|
877
|
+
msg = (
|
|
878
|
+
f"{resource_type_title} '{unique_id}' ({original_file_path}) depends on a "
|
|
879
|
+
f"{self.target_kind} named '{self.target_name}' {target_version_string}{target_package_string}which {reason}"
|
|
880
|
+
)
|
|
881
|
+
return msg
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
class DuplicateSourcePatchNameError(CompilationError):
|
|
885
|
+
def __init__(self, patch_1, patch_2):
|
|
886
|
+
self.patch_1 = patch_1
|
|
887
|
+
self.patch_2 = patch_2
|
|
888
|
+
super().__init__(msg=self.get_message())
|
|
889
|
+
|
|
890
|
+
def get_message(self) -> str:
|
|
891
|
+
name = f"{self.patch_1.overrides}.{self.patch_1.name}"
|
|
892
|
+
fix = self._fix_dupe_msg(
|
|
893
|
+
self.patch_1.path,
|
|
894
|
+
self.patch_2.path,
|
|
895
|
+
name,
|
|
896
|
+
"sources",
|
|
897
|
+
)
|
|
898
|
+
msg = (
|
|
899
|
+
f"dbt found two schema.yml entries for the same source named "
|
|
900
|
+
f"{self.patch_1.name} in package {self.patch_1.overrides}. Sources may only be "
|
|
901
|
+
f"overridden a single time. To fix this, {fix}"
|
|
902
|
+
)
|
|
903
|
+
return msg
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
class DuplicateMacroPatchNameError(CompilationError):
|
|
907
|
+
def __init__(self, patch_1, existing_patch_path):
|
|
908
|
+
self.patch_1 = patch_1
|
|
909
|
+
self.existing_patch_path = existing_patch_path
|
|
910
|
+
super().__init__(msg=self.get_message())
|
|
911
|
+
|
|
912
|
+
def get_message(self) -> str:
|
|
913
|
+
package_name = self.patch_1.package_name
|
|
914
|
+
name = self.patch_1.name
|
|
915
|
+
fix = self._fix_dupe_msg(
|
|
916
|
+
self.patch_1.original_file_path, self.existing_patch_path, name, "macros"
|
|
917
|
+
)
|
|
918
|
+
msg = (
|
|
919
|
+
f"dbt found two schema.yml entries for the same macro in package "
|
|
920
|
+
f"{package_name} named {name}. Macros may only be described a single "
|
|
921
|
+
f"time. To fix this, {fix}"
|
|
922
|
+
)
|
|
923
|
+
return msg
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
# core level exceptions
|
|
927
|
+
class DuplicateAliasError(AliasError):
|
|
928
|
+
def __init__(self, kwargs: Mapping[str, Any], aliases: Mapping[str, str], canonical_key: str):
|
|
929
|
+
self.kwargs = kwargs
|
|
930
|
+
self.aliases = aliases
|
|
931
|
+
self.canonical_key = canonical_key
|
|
932
|
+
super().__init__(msg=self.get_message())
|
|
933
|
+
|
|
934
|
+
def get_message(self) -> str:
|
|
935
|
+
# dupe found: go through the dict so we can have a nice-ish error
|
|
936
|
+
key_names = ", ".join(
|
|
937
|
+
"{}".format(k) for k in self.kwargs if self.aliases.get(k) == self.canonical_key
|
|
938
|
+
)
|
|
939
|
+
msg = f'Got duplicate keys: ({key_names}) all map to "{self.canonical_key}"'
|
|
940
|
+
return msg
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
# deps exceptions
|
|
944
|
+
class MultipleVersionGitDepsError(DependencyError):
|
|
945
|
+
def __init__(self, git: str, requested):
|
|
946
|
+
self.git = git
|
|
947
|
+
self.requested = requested
|
|
948
|
+
msg = (
|
|
949
|
+
"git dependencies should contain exactly one version. "
|
|
950
|
+
f"{self.git} contains: {self.requested}"
|
|
951
|
+
)
|
|
952
|
+
super().__init__(msg)
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
class DuplicateProjectDependencyError(DependencyError):
|
|
956
|
+
def __init__(self, project_name: str):
|
|
957
|
+
self.project_name = project_name
|
|
958
|
+
msg = (
|
|
959
|
+
f'Found duplicate project "{self.project_name}". This occurs when '
|
|
960
|
+
"a dependency has the same project name as some other dependency."
|
|
961
|
+
)
|
|
962
|
+
super().__init__(msg)
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
class DuplicateDependencyToRootError(DependencyError):
|
|
966
|
+
def __init__(self, project_name: str):
|
|
967
|
+
self.project_name = project_name
|
|
968
|
+
msg = (
|
|
969
|
+
"Found a dependency with the same name as the root project "
|
|
970
|
+
f'"{self.project_name}". Package names must be unique in a project.'
|
|
971
|
+
" Please rename one of these packages."
|
|
972
|
+
)
|
|
973
|
+
super().__init__(msg)
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
class MismatchedDependencyTypeError(DependencyError):
|
|
977
|
+
def __init__(self, new, old):
|
|
978
|
+
self.new = new
|
|
979
|
+
self.old = old
|
|
980
|
+
msg = (
|
|
981
|
+
f"Cannot incorporate {self.new} ({self.new.__class__.__name__}) in {self.old} "
|
|
982
|
+
f"({self.old.__class__.__name__}): mismatched types"
|
|
983
|
+
)
|
|
984
|
+
super().__init__(msg)
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
class PackageVersionNotFoundError(DependencyError):
|
|
988
|
+
def __init__(
|
|
989
|
+
self,
|
|
990
|
+
package_name: str,
|
|
991
|
+
version_range,
|
|
992
|
+
available_versions: List[str],
|
|
993
|
+
should_version_check: bool,
|
|
994
|
+
):
|
|
995
|
+
self.package_name = package_name
|
|
996
|
+
self.version_range = version_range
|
|
997
|
+
self.available_versions = available_versions
|
|
998
|
+
self.should_version_check = should_version_check
|
|
999
|
+
super().__init__(self.get_message())
|
|
1000
|
+
|
|
1001
|
+
def get_message(self) -> str:
|
|
1002
|
+
base_msg = (
|
|
1003
|
+
"Could not find a matching compatible version for package {}\n"
|
|
1004
|
+
" Requested range: {}\n"
|
|
1005
|
+
" Compatible versions: {}\n"
|
|
1006
|
+
)
|
|
1007
|
+
addendum = (
|
|
1008
|
+
(
|
|
1009
|
+
"\n"
|
|
1010
|
+
" Not shown: package versions incompatible with installed version of dbt-core\n"
|
|
1011
|
+
" To include them, run 'dbt --no-version-check deps'"
|
|
1012
|
+
)
|
|
1013
|
+
if self.should_version_check
|
|
1014
|
+
else ""
|
|
1015
|
+
)
|
|
1016
|
+
msg = (
|
|
1017
|
+
base_msg.format(self.package_name, self.version_range, self.available_versions)
|
|
1018
|
+
+ addendum
|
|
1019
|
+
)
|
|
1020
|
+
return msg
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
class PackageNotFoundError(DependencyError):
|
|
1024
|
+
def __init__(self, package_name: str):
|
|
1025
|
+
self.package_name = package_name
|
|
1026
|
+
msg = f"Package {self.package_name} was not found in the package index"
|
|
1027
|
+
super().__init__(msg)
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
# config level exceptions
|
|
1031
|
+
class ProfileConfigError(DbtProfileError):
|
|
1032
|
+
def __init__(self, exc: ValidationError):
|
|
1033
|
+
self.exc = exc
|
|
1034
|
+
msg = self.validator_error_message(self.exc)
|
|
1035
|
+
super().__init__(msg=msg)
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
class ProjectContractError(DbtProjectError):
|
|
1039
|
+
def __init__(self, exc: ValidationError):
|
|
1040
|
+
self.exc = exc
|
|
1041
|
+
msg = self.validator_error_message(self.exc)
|
|
1042
|
+
super().__init__(msg=msg)
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
class ProjectContractBrokenError(DbtProjectError):
|
|
1046
|
+
def __init__(self, exc: ValidationError):
|
|
1047
|
+
self.exc = exc
|
|
1048
|
+
msg = self.validator_error_message(self.exc)
|
|
1049
|
+
super().__init__(msg=msg)
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
class ConfigContractBrokenError(DbtProjectError):
|
|
1053
|
+
def __init__(self, exc: ValidationError):
|
|
1054
|
+
self.exc = exc
|
|
1055
|
+
msg = self.validator_error_message(self.exc)
|
|
1056
|
+
super().__init__(msg=msg)
|
|
1057
|
+
|
|
1058
|
+
|
|
1059
|
+
class NonUniquePackageNameError(CompilationError):
|
|
1060
|
+
def __init__(self, project_name: str):
|
|
1061
|
+
self.project_name = project_name
|
|
1062
|
+
super().__init__(msg=self.get_message())
|
|
1063
|
+
|
|
1064
|
+
def get_message(self) -> str:
|
|
1065
|
+
msg = (
|
|
1066
|
+
"dbt found more than one package with the name "
|
|
1067
|
+
f'"{self.project_name}" included in this project. Package '
|
|
1068
|
+
"names must be unique in a project. Please rename "
|
|
1069
|
+
"one of these packages."
|
|
1070
|
+
)
|
|
1071
|
+
return msg
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
class UninstalledPackagesFoundError(CompilationError):
|
|
1075
|
+
def __init__(
|
|
1076
|
+
self,
|
|
1077
|
+
count_packages_specified: int,
|
|
1078
|
+
count_packages_installed: int,
|
|
1079
|
+
packages_specified_path: str,
|
|
1080
|
+
packages_install_path: str,
|
|
1081
|
+
):
|
|
1082
|
+
self.count_packages_specified = count_packages_specified
|
|
1083
|
+
self.count_packages_installed = count_packages_installed
|
|
1084
|
+
self.packages_specified_path = packages_specified_path
|
|
1085
|
+
self.packages_install_path = packages_install_path
|
|
1086
|
+
super().__init__(msg=self.get_message())
|
|
1087
|
+
|
|
1088
|
+
def get_message(self) -> str:
|
|
1089
|
+
msg = (
|
|
1090
|
+
f"dbt found {self.count_packages_specified} package(s) "
|
|
1091
|
+
f"specified in {self.packages_specified_path}, but only "
|
|
1092
|
+
f"{self.count_packages_installed} package(s) installed "
|
|
1093
|
+
f'in {self.packages_install_path}. Run "dbt deps" to '
|
|
1094
|
+
"install package dependencies."
|
|
1095
|
+
)
|
|
1096
|
+
return msg
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
class OptionNotYamlDictError(CompilationError):
|
|
1100
|
+
def __init__(self, var_type, option_name):
|
|
1101
|
+
self.var_type = var_type
|
|
1102
|
+
self.option_name = option_name
|
|
1103
|
+
super().__init__(msg=self.get_message())
|
|
1104
|
+
|
|
1105
|
+
def get_message(self) -> str:
|
|
1106
|
+
type_name = self.var_type.__name__
|
|
1107
|
+
|
|
1108
|
+
msg = f"The --{self.option_name} argument must be a YAML dictionary, but was of type '{type_name}'"
|
|
1109
|
+
return msg
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
# contracts level
|
|
1113
|
+
class UnrecognizedCredentialTypeError(CompilationError):
|
|
1114
|
+
def __init__(self, typename: str, supported_types: List):
|
|
1115
|
+
self.typename = typename
|
|
1116
|
+
self.supported_types = supported_types
|
|
1117
|
+
super().__init__(msg=self.get_message())
|
|
1118
|
+
|
|
1119
|
+
def get_message(self) -> str:
|
|
1120
|
+
msg = 'Unrecognized credentials type "{}" - supported types are ({})'.format(
|
|
1121
|
+
self.typename, ", ".join('"{}"'.format(t) for t in self.supported_types)
|
|
1122
|
+
)
|
|
1123
|
+
return msg
|
|
1124
|
+
|
|
1125
|
+
|
|
1126
|
+
# jinja exceptions
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
class PatchTargetNotFoundError(CompilationError):
|
|
1130
|
+
def __init__(self, patches: Dict):
|
|
1131
|
+
self.patches = patches
|
|
1132
|
+
super().__init__(msg=self.get_message())
|
|
1133
|
+
|
|
1134
|
+
def get_message(self) -> str:
|
|
1135
|
+
patch_list = "\n\t".join(
|
|
1136
|
+
f"model {p.name} (referenced in path {p.original_file_path})"
|
|
1137
|
+
for p in self.patches.values()
|
|
1138
|
+
)
|
|
1139
|
+
msg = f"dbt could not find models for the following patches:\n\t{patch_list}"
|
|
1140
|
+
return msg
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
class MissingRelationError(CompilationError):
|
|
1144
|
+
def __init__(self, relation, model=None):
|
|
1145
|
+
self.relation = relation
|
|
1146
|
+
self.model = model
|
|
1147
|
+
msg = f"Relation {self.relation} not found!"
|
|
1148
|
+
super().__init__(msg=msg)
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
class AmbiguousAliasError(CompilationError):
|
|
1152
|
+
def __init__(self, node_1, node_2, duped_name=None):
|
|
1153
|
+
self.node_1 = node_1
|
|
1154
|
+
self.node_2 = node_2
|
|
1155
|
+
if duped_name is None:
|
|
1156
|
+
self.duped_name = f"{self.node_1.database}.{self.node_1.schema}.{self.node_1.alias}"
|
|
1157
|
+
else:
|
|
1158
|
+
self.duped_name = duped_name
|
|
1159
|
+
super().__init__(msg=self.get_message())
|
|
1160
|
+
|
|
1161
|
+
def get_message(self) -> str:
|
|
1162
|
+
|
|
1163
|
+
msg = (
|
|
1164
|
+
f'dbt found two resources with the database representation "{self.duped_name}".\ndbt '
|
|
1165
|
+
"cannot create two resources with identical database representations. "
|
|
1166
|
+
"To fix this,\nchange the configuration of one of these resources:"
|
|
1167
|
+
f"\n- {self.node_1.unique_id} ({self.node_1.original_file_path})\n- {self.node_2.unique_id} ({self.node_2.original_file_path})"
|
|
1168
|
+
)
|
|
1169
|
+
return msg
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
class AmbiguousResourceNameRefError(CompilationError):
|
|
1173
|
+
def __init__(self, duped_name, unique_ids, node=None):
|
|
1174
|
+
self.duped_name = duped_name
|
|
1175
|
+
self.unique_ids = unique_ids
|
|
1176
|
+
self.packages = [unique_id.split(".")[1] for unique_id in unique_ids]
|
|
1177
|
+
super().__init__(msg=self.get_message(), node=node)
|
|
1178
|
+
|
|
1179
|
+
def get_message(self) -> str:
|
|
1180
|
+
formatted_unique_ids = "'{0}'".format("', '".join(self.unique_ids))
|
|
1181
|
+
formatted_packages = "'{0}'".format("' or '".join(self.packages))
|
|
1182
|
+
msg = (
|
|
1183
|
+
f"When referencing '{self.duped_name}', dbt found nodes in multiple packages: {formatted_unique_ids}"
|
|
1184
|
+
f"\nTo fix this, use two-argument 'ref', with the package name first: {formatted_packages}"
|
|
1185
|
+
)
|
|
1186
|
+
return msg
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
class AmbiguousCatalogMatchError(CompilationError):
|
|
1190
|
+
def __init__(self, unique_id: str, match_1, match_2):
|
|
1191
|
+
self.unique_id = unique_id
|
|
1192
|
+
self.match_1 = match_1
|
|
1193
|
+
self.match_2 = match_2
|
|
1194
|
+
super().__init__(msg=self.get_message())
|
|
1195
|
+
|
|
1196
|
+
def get_match_string(self, match):
|
|
1197
|
+
match_schema = match.get("metadata", {}).get("schema")
|
|
1198
|
+
match_name = match.get("metadata", {}).get("name")
|
|
1199
|
+
return f"{match_schema}.{match_name}"
|
|
1200
|
+
|
|
1201
|
+
def get_message(self) -> str:
|
|
1202
|
+
msg = (
|
|
1203
|
+
"dbt found two relations in your warehouse with similar database identifiers. "
|
|
1204
|
+
"dbt\nis unable to determine which of these relations was created by the model "
|
|
1205
|
+
f'"{self.unique_id}".\nIn order for dbt to correctly generate the catalog, one '
|
|
1206
|
+
"of the following relations must be deleted or renamed:\n\n - "
|
|
1207
|
+
f"{self.get_match_string(self.match_1)}\n - {self.get_match_string(self.match_2)}"
|
|
1208
|
+
)
|
|
1209
|
+
|
|
1210
|
+
return msg
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
class DependencyNotFoundError(CompilationError):
|
|
1214
|
+
def __init__(self, node, node_description, required_pkg):
|
|
1215
|
+
self.node = node
|
|
1216
|
+
self.node_description = node_description
|
|
1217
|
+
self.required_pkg = required_pkg
|
|
1218
|
+
super().__init__(msg=self.get_message())
|
|
1219
|
+
|
|
1220
|
+
def get_message(self) -> str:
|
|
1221
|
+
msg = (
|
|
1222
|
+
f"Error while parsing {self.node_description}.\nThe required package "
|
|
1223
|
+
f'"{self.required_pkg}" was not found. Is the package installed?\n'
|
|
1224
|
+
"Hint: You may need to run `dbt deps`."
|
|
1225
|
+
)
|
|
1226
|
+
|
|
1227
|
+
return msg
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
class DuplicatePatchPathError(CompilationError):
|
|
1231
|
+
def __init__(self, patch_1, existing_patch_path):
|
|
1232
|
+
self.patch_1 = patch_1
|
|
1233
|
+
self.existing_patch_path = existing_patch_path
|
|
1234
|
+
super().__init__(msg=self.get_message())
|
|
1235
|
+
|
|
1236
|
+
def get_message(self) -> str:
|
|
1237
|
+
name = self.patch_1.name
|
|
1238
|
+
fix = self._fix_dupe_msg(
|
|
1239
|
+
self.patch_1.original_file_path,
|
|
1240
|
+
self.existing_patch_path,
|
|
1241
|
+
name,
|
|
1242
|
+
"resource",
|
|
1243
|
+
)
|
|
1244
|
+
msg = (
|
|
1245
|
+
f"dbt found two schema.yml entries for the same resource named "
|
|
1246
|
+
f"{name}. Resources and their associated columns may only be "
|
|
1247
|
+
f"described a single time. To fix this, {fix}"
|
|
1248
|
+
)
|
|
1249
|
+
return msg
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
# should this inherit ParsingError instead?
|
|
1253
|
+
class DuplicateResourceNameError(CompilationError):
|
|
1254
|
+
def __init__(self, node_1, node_2):
|
|
1255
|
+
self.node_1 = node_1
|
|
1256
|
+
self.node_2 = node_2
|
|
1257
|
+
super().__init__(msg=self.get_message())
|
|
1258
|
+
|
|
1259
|
+
def get_message(self) -> str:
|
|
1260
|
+
duped_name = self.node_1.name
|
|
1261
|
+
node_type = NodeType(self.node_1.resource_type)
|
|
1262
|
+
pluralized = (
|
|
1263
|
+
node_type.pluralize()
|
|
1264
|
+
if self.node_1.resource_type == self.node_2.resource_type
|
|
1265
|
+
else "resources" # still raise if ref() collision, e.g. model + seed
|
|
1266
|
+
)
|
|
1267
|
+
|
|
1268
|
+
action = "looking for"
|
|
1269
|
+
# duplicate 'ref' targets
|
|
1270
|
+
if node_type in REFABLE_NODE_TYPES:
|
|
1271
|
+
formatted_name = f'ref("{duped_name}")'
|
|
1272
|
+
# duplicate sources
|
|
1273
|
+
elif node_type == NodeType.Source:
|
|
1274
|
+
duped_name = self.node_1.get_full_source_name()
|
|
1275
|
+
formatted_name = self.node_1.get_source_representation()
|
|
1276
|
+
# duplicate docs blocks
|
|
1277
|
+
elif node_type == NodeType.Documentation:
|
|
1278
|
+
formatted_name = f'doc("{duped_name}")'
|
|
1279
|
+
# duplicate generic tests
|
|
1280
|
+
elif node_type == NodeType.Test and hasattr(self.node_1, "test_metadata"):
|
|
1281
|
+
column_name = (
|
|
1282
|
+
f'column "{self.node_1.column_name}" in ' if self.node_1.column_name else ""
|
|
1283
|
+
)
|
|
1284
|
+
model_name = self.node_1.file_key_name
|
|
1285
|
+
duped_name = f'{self.node_1.name}" defined on {column_name}"{model_name}'
|
|
1286
|
+
action = "running"
|
|
1287
|
+
formatted_name = "tests"
|
|
1288
|
+
# all other resource types
|
|
1289
|
+
else:
|
|
1290
|
+
formatted_name = duped_name
|
|
1291
|
+
|
|
1292
|
+
msg = f"""
|
|
1293
|
+
dbt found two {pluralized} with the name "{duped_name}".
|
|
1294
|
+
|
|
1295
|
+
Since these resources have the same name, dbt will be unable to find the correct resource
|
|
1296
|
+
when {action} {formatted_name}.
|
|
1297
|
+
|
|
1298
|
+
To fix this, change the name of one of these resources:
|
|
1299
|
+
- {self.node_1.unique_id} ({self.node_1.original_file_path})
|
|
1300
|
+
- {self.node_2.unique_id} ({self.node_2.original_file_path})
|
|
1301
|
+
""".strip()
|
|
1302
|
+
return msg
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
class DuplicateVersionedUnversionedError(ParsingError):
|
|
1306
|
+
def __init__(self, versioned_node, unversioned_node):
|
|
1307
|
+
self.versioned_node = versioned_node
|
|
1308
|
+
self.unversioned_node = unversioned_node
|
|
1309
|
+
super().__init__(msg=self.get_message())
|
|
1310
|
+
|
|
1311
|
+
def get_message(self) -> str:
|
|
1312
|
+
msg = f"""
|
|
1313
|
+
dbt found versioned and unversioned models with the name "{self.versioned_node.name}".
|
|
1314
|
+
|
|
1315
|
+
Since these resources have the same name, dbt will be unable to find the correct resource
|
|
1316
|
+
when looking for ref('{self.versioned_node.name}').
|
|
1317
|
+
|
|
1318
|
+
To fix this, change the name of the unversioned resource
|
|
1319
|
+
{self.unversioned_node.unique_id} ({self.unversioned_node.original_file_path})
|
|
1320
|
+
or add the unversioned model to the versions in {self.versioned_node.patch_path}
|
|
1321
|
+
""".strip()
|
|
1322
|
+
return msg
|
|
1323
|
+
|
|
1324
|
+
|
|
1325
|
+
class PropertyYMLError(CompilationError):
|
|
1326
|
+
def __init__(self, path: str, issue: str):
|
|
1327
|
+
self.path = path
|
|
1328
|
+
self.issue = issue
|
|
1329
|
+
super().__init__(msg=self.get_message())
|
|
1330
|
+
|
|
1331
|
+
def get_message(self) -> str:
|
|
1332
|
+
msg = (
|
|
1333
|
+
f"The yml property file at {self.path} is invalid because {self.issue}. "
|
|
1334
|
+
"Please consult the documentation for more information on yml property file "
|
|
1335
|
+
"syntax:\n\nhttps://docs.getdbt.com/reference/configs-and-properties"
|
|
1336
|
+
)
|
|
1337
|
+
return msg
|
|
1338
|
+
|
|
1339
|
+
|
|
1340
|
+
class ContractError(CompilationError):
|
|
1341
|
+
def __init__(self, yaml_columns, sql_columns):
|
|
1342
|
+
self.yaml_columns = yaml_columns
|
|
1343
|
+
self.sql_columns = sql_columns
|
|
1344
|
+
super().__init__(msg=self.get_message())
|
|
1345
|
+
|
|
1346
|
+
def get_mismatches(self) -> "agate.Table":
|
|
1347
|
+
# avoid a circular import
|
|
1348
|
+
from dbt_common.clients.agate_helper import table_from_data_flat
|
|
1349
|
+
|
|
1350
|
+
column_names = ["column_name", "definition_type", "contract_type", "mismatch_reason"]
|
|
1351
|
+
# list of mismatches
|
|
1352
|
+
mismatches: List[Dict[str, str]] = []
|
|
1353
|
+
# track sql cols so we don't need another for loop later
|
|
1354
|
+
sql_col_set = set()
|
|
1355
|
+
# for each sql col list
|
|
1356
|
+
for sql_col in self.sql_columns:
|
|
1357
|
+
# add sql col to set
|
|
1358
|
+
sql_col_set.add(sql_col["name"])
|
|
1359
|
+
# for each yaml col list
|
|
1360
|
+
for i, yaml_col in enumerate(self.yaml_columns):
|
|
1361
|
+
# if name matches
|
|
1362
|
+
if sql_col["name"] == yaml_col["name"]:
|
|
1363
|
+
# if type matches
|
|
1364
|
+
if sql_col["data_type"] == yaml_col["data_type"]:
|
|
1365
|
+
# its a perfect match! don't include in mismatch table
|
|
1366
|
+
break
|
|
1367
|
+
else:
|
|
1368
|
+
# same name, diff type
|
|
1369
|
+
row = [
|
|
1370
|
+
sql_col["name"],
|
|
1371
|
+
sql_col["data_type"],
|
|
1372
|
+
yaml_col["data_type"],
|
|
1373
|
+
"data type mismatch",
|
|
1374
|
+
]
|
|
1375
|
+
mismatches += [dict(zip(column_names, row))]
|
|
1376
|
+
break
|
|
1377
|
+
# if last loop, then no name match
|
|
1378
|
+
if i == len(self.yaml_columns) - 1:
|
|
1379
|
+
row = [sql_col["name"], sql_col["data_type"], "", "missing in contract"]
|
|
1380
|
+
mismatches += [dict(zip(column_names, row))]
|
|
1381
|
+
|
|
1382
|
+
# now add all yaml cols without a match
|
|
1383
|
+
for yaml_col in self.yaml_columns:
|
|
1384
|
+
if yaml_col["name"] not in sql_col_set:
|
|
1385
|
+
row = [yaml_col["name"], "", yaml_col["data_type"], "missing in definition"]
|
|
1386
|
+
mismatches += [dict(zip(column_names, row))]
|
|
1387
|
+
|
|
1388
|
+
mismatches_sorted = sorted(mismatches, key=lambda d: d["column_name"])
|
|
1389
|
+
return table_from_data_flat(mismatches_sorted, column_names)
|
|
1390
|
+
|
|
1391
|
+
def get_message(self) -> str:
|
|
1392
|
+
if not self.yaml_columns:
|
|
1393
|
+
return (
|
|
1394
|
+
"This model has an enforced contract, and its 'columns' specification is missing"
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
table: "agate.Table" = self.get_mismatches()
|
|
1398
|
+
# Hack to get Agate table output as string
|
|
1399
|
+
output = io.StringIO()
|
|
1400
|
+
table.print_table(output=output, max_rows=None, max_column_width=50) # type: ignore
|
|
1401
|
+
mismatches = output.getvalue()
|
|
1402
|
+
|
|
1403
|
+
msg = (
|
|
1404
|
+
"This model has an enforced contract that failed.\n"
|
|
1405
|
+
"Please ensure the name, data_type, and number of columns in your contract "
|
|
1406
|
+
"match the columns in your model's definition.\n\n"
|
|
1407
|
+
f"{mismatches}"
|
|
1408
|
+
)
|
|
1409
|
+
|
|
1410
|
+
return msg
|
|
1411
|
+
|
|
1412
|
+
|
|
1413
|
+
# not modifying these since rpc should be deprecated soon
|
|
1414
|
+
class UnknownAsyncIDException(Exception):
|
|
1415
|
+
CODE = 10012
|
|
1416
|
+
MESSAGE = "RPC server got an unknown async ID"
|
|
1417
|
+
|
|
1418
|
+
def __init__(self, task_id):
|
|
1419
|
+
self.task_id = task_id
|
|
1420
|
+
|
|
1421
|
+
def __str__(self):
|
|
1422
|
+
return f"{self.MESSAGE}: {self.task_id}"
|
|
1423
|
+
|
|
1424
|
+
|
|
1425
|
+
class RPCFailureResult(DbtRuntimeError):
|
|
1426
|
+
CODE = 10002
|
|
1427
|
+
MESSAGE = "RPC execution error"
|
|
1428
|
+
|
|
1429
|
+
|
|
1430
|
+
class RPCTimeoutException(DbtRuntimeError):
|
|
1431
|
+
CODE = 10008
|
|
1432
|
+
MESSAGE = "RPC timeout error"
|
|
1433
|
+
|
|
1434
|
+
def __init__(self, timeout: Optional[float] = None):
|
|
1435
|
+
super().__init__(self.MESSAGE)
|
|
1436
|
+
self.timeout = timeout
|
|
1437
|
+
|
|
1438
|
+
def data(self):
|
|
1439
|
+
result = super().data()
|
|
1440
|
+
result.update(
|
|
1441
|
+
{
|
|
1442
|
+
"timeout": self.timeout,
|
|
1443
|
+
"message": f"RPC timed out after {self.timeout}s",
|
|
1444
|
+
}
|
|
1445
|
+
)
|
|
1446
|
+
return result
|
|
1447
|
+
|
|
1448
|
+
|
|
1449
|
+
class RPCKilledException(DbtRuntimeError):
|
|
1450
|
+
CODE = 10009
|
|
1451
|
+
MESSAGE = "RPC process killed"
|
|
1452
|
+
|
|
1453
|
+
def __init__(self, signum: int):
|
|
1454
|
+
self.signum = signum
|
|
1455
|
+
self.msg = f"RPC process killed by signal {self.signum}"
|
|
1456
|
+
super().__init__(self.msg)
|
|
1457
|
+
|
|
1458
|
+
def data(self):
|
|
1459
|
+
return {
|
|
1460
|
+
"signum": self.signum,
|
|
1461
|
+
"message": self.msg,
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
|
|
1465
|
+
class RPCCompiling(DbtRuntimeError):
|
|
1466
|
+
CODE = 10010
|
|
1467
|
+
MESSAGE = 'RPC server is compiling the project, call the "status" method for' " compile status"
|
|
1468
|
+
|
|
1469
|
+
def __init__(self, msg: Optional[str] = None, node=None):
|
|
1470
|
+
if msg is None:
|
|
1471
|
+
msg = "compile in progress"
|
|
1472
|
+
super().__init__(msg, node)
|
|
1473
|
+
|
|
1474
|
+
|
|
1475
|
+
class RPCLoadException(DbtRuntimeError):
|
|
1476
|
+
CODE = 10011
|
|
1477
|
+
MESSAGE = (
|
|
1478
|
+
'RPC server failed to compile project, call the "status" method for' " compile status"
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
def __init__(self, cause: Dict[str, Any]):
|
|
1482
|
+
self.cause = cause
|
|
1483
|
+
self.msg = f'{self.MESSAGE}: {self.cause["message"]}'
|
|
1484
|
+
super().__init__(self.msg)
|
|
1485
|
+
|
|
1486
|
+
def data(self):
|
|
1487
|
+
return {"cause": self.cause, "message": self.msg}
|