dbt-bouncer 1.31.2rc3__py3-none-any.whl → 2.0.0__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.
- dbt_bouncer/artifact_parsers/dbt_cloud/catalog_latest.py +21 -21
- dbt_bouncer/artifact_parsers/dbt_cloud/manifest_latest.py +1745 -1745
- dbt_bouncer/artifact_parsers/dbt_cloud/run_results_latest.py +22 -22
- dbt_bouncer/artifact_parsers/parsers_catalog.py +26 -24
- dbt_bouncer/artifact_parsers/parsers_common.py +57 -36
- dbt_bouncer/artifact_parsers/parsers_manifest.py +98 -69
- dbt_bouncer/artifact_parsers/parsers_run_results.py +32 -19
- dbt_bouncer/check_base.py +22 -11
- dbt_bouncer/checks/catalog/check_catalog_sources.py +22 -12
- dbt_bouncer/checks/catalog/check_columns.py +175 -105
- dbt_bouncer/checks/common.py +24 -3
- dbt_bouncer/checks/manifest/check_exposures.py +79 -52
- dbt_bouncer/checks/manifest/check_lineage.py +69 -40
- dbt_bouncer/checks/manifest/check_macros.py +177 -104
- dbt_bouncer/checks/manifest/check_metadata.py +28 -18
- dbt_bouncer/checks/manifest/check_models.py +842 -496
- dbt_bouncer/checks/manifest/check_seeds.py +63 -0
- dbt_bouncer/checks/manifest/check_semantic_models.py +28 -20
- dbt_bouncer/checks/manifest/check_snapshots.py +57 -33
- dbt_bouncer/checks/manifest/check_sources.py +246 -137
- dbt_bouncer/checks/manifest/check_unit_tests.py +97 -54
- dbt_bouncer/checks/run_results/check_run_results.py +34 -20
- dbt_bouncer/config_file_parser.py +47 -28
- dbt_bouncer/config_file_validator.py +11 -8
- dbt_bouncer/global_context.py +31 -0
- dbt_bouncer/main.py +128 -67
- dbt_bouncer/runner.py +61 -31
- dbt_bouncer/utils.py +146 -50
- dbt_bouncer/version.py +1 -1
- {dbt_bouncer-1.31.2rc3.dist-info → dbt_bouncer-2.0.0.dist-info}/METADATA +15 -15
- dbt_bouncer-2.0.0.dist-info/RECORD +37 -0
- dbt_bouncer-1.31.2rc3.dist-info/RECORD +0 -35
- {dbt_bouncer-1.31.2rc3.dist-info → dbt_bouncer-2.0.0.dist-info}/WHEEL +0 -0
- {dbt_bouncer-1.31.2rc3.dist-info → dbt_bouncer-2.0.0.dist-info}/entry_points.txt +0 -0
- {dbt_bouncer-1.31.2rc3.dist-info → dbt_bouncer-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {dbt_bouncer-1.31.2rc3.dist-info → dbt_bouncer-2.0.0.dist-info}/top_level.txt +0 -0
dbt_bouncer/main.py
CHANGED
|
@@ -1,70 +1,43 @@
|
|
|
1
|
+
import importlib
|
|
1
2
|
import logging
|
|
3
|
+
import sys
|
|
2
4
|
from pathlib import Path, PurePath
|
|
3
|
-
from typing import Union
|
|
4
5
|
|
|
5
6
|
import click
|
|
6
7
|
|
|
8
|
+
from dbt_bouncer.global_context import BouncerContext, set_context
|
|
7
9
|
from dbt_bouncer.logger import configure_console_logging
|
|
8
10
|
from dbt_bouncer.version import version
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"--output-file",
|
|
36
|
-
default=None,
|
|
37
|
-
help="Location of the json file where check metadata will be saved.",
|
|
38
|
-
required=False,
|
|
39
|
-
type=Path,
|
|
40
|
-
)
|
|
41
|
-
@click.option(
|
|
42
|
-
"--output-only-failures",
|
|
43
|
-
help="If passed then only failures will be included in the output file.",
|
|
44
|
-
is_flag=True,
|
|
45
|
-
)
|
|
46
|
-
@click.option(
|
|
47
|
-
"--show-all-failures",
|
|
48
|
-
help="If passed then all failures will be printed to the console.",
|
|
49
|
-
is_flag=True,
|
|
50
|
-
)
|
|
51
|
-
@click.option("-v", "--verbosity", help="Verbosity.", default=0, count=True)
|
|
52
|
-
@click.pass_context
|
|
53
|
-
@click.version_option()
|
|
54
|
-
def cli(
|
|
55
|
-
ctx: click.Context,
|
|
56
|
-
config_file: PurePath,
|
|
57
|
-
create_pr_comment_file: bool,
|
|
58
|
-
only: str,
|
|
59
|
-
output_file: Union[Path, None],
|
|
60
|
-
output_only_failures: bool,
|
|
61
|
-
show_all_failures: bool,
|
|
62
|
-
verbosity: int,
|
|
63
|
-
) -> None:
|
|
64
|
-
"""Entrypoint for dbt-bouncer.
|
|
13
|
+
def run_bouncer(
|
|
14
|
+
config_file: PurePath | None = None,
|
|
15
|
+
create_pr_comment_file: bool = False,
|
|
16
|
+
only: str = "",
|
|
17
|
+
output_file: Path | None = None,
|
|
18
|
+
output_only_failures: bool = False,
|
|
19
|
+
show_all_failures: bool = False,
|
|
20
|
+
verbosity: int = 0,
|
|
21
|
+
config_file_source: str | None = None,
|
|
22
|
+
) -> int:
|
|
23
|
+
"""Programmatic entrypoint for dbt-bouncer.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
config_file: Location of the YML config file.
|
|
27
|
+
create_pr_comment_file: Create a `github-comment.md` file.
|
|
28
|
+
only: Limit the checks run to specific categories.
|
|
29
|
+
output_file: Location of the json file where check metadata will be saved.
|
|
30
|
+
output_only_failures: Only failures will be included in the output file.
|
|
31
|
+
show_all_failures: All failures will be printed to the console.
|
|
32
|
+
verbosity: Verbosity level.
|
|
33
|
+
config_file_source: Source of the config file ("COMMANDLINE", "DEFAULT", etc.).
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
int: Exit code (0 for success, 1 for failure).
|
|
65
37
|
|
|
66
38
|
Raises:
|
|
67
|
-
|
|
39
|
+
AssertionError: If config_file_source is None.
|
|
40
|
+
RuntimeError: If output file has an invalid extension or other runtime errors.
|
|
68
41
|
|
|
69
42
|
"""
|
|
70
43
|
configure_console_logging(verbosity)
|
|
@@ -94,11 +67,20 @@ def cli(
|
|
|
94
67
|
load_config_file_contents,
|
|
95
68
|
)
|
|
96
69
|
|
|
70
|
+
if config_file is None:
|
|
71
|
+
config_file = Path("dbt-bouncer.yml")
|
|
72
|
+
if config_file_source is None:
|
|
73
|
+
config_file_source = "DEFAULT"
|
|
74
|
+
else:
|
|
75
|
+
if config_file_source is None:
|
|
76
|
+
config_file_source = "COMMANDLINE"
|
|
77
|
+
|
|
78
|
+
if config_file_source is None:
|
|
79
|
+
raise AssertionError("config_file_source cannot be None")
|
|
80
|
+
|
|
97
81
|
config_file_path = get_config_file_path(
|
|
98
82
|
config_file=config_file,
|
|
99
|
-
config_file_source=
|
|
100
|
-
.get_parameter_source("config_file")
|
|
101
|
-
.name, # type: ignore[union-attr]
|
|
83
|
+
config_file_source=config_file_source,
|
|
102
84
|
)
|
|
103
85
|
config_file_contents = load_config_file_contents(
|
|
104
86
|
config_file_path, allow_default_config_file_creation=True
|
|
@@ -121,16 +103,23 @@ def cli(
|
|
|
121
103
|
]
|
|
122
104
|
logging.debug(f"{check_categories=}")
|
|
123
105
|
|
|
124
|
-
# Set
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
106
|
+
# Set global context for dbt_bouncer.utils.get_check_objects() and config_file_parser
|
|
107
|
+
set_context(
|
|
108
|
+
BouncerContext(
|
|
109
|
+
config_file_path=config_file_path,
|
|
110
|
+
custom_checks_dir=config_file_contents.get("custom_checks_dir"),
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# If config_file_parser is already loaded, reload it so that check types are updated (necessary for parallel tests)
|
|
115
|
+
if "dbt_bouncer.config_file_parser" in sys.modules:
|
|
116
|
+
importlib.reload(sys.modules["dbt_bouncer.config_file_parser"])
|
|
129
117
|
|
|
130
118
|
from dbt_bouncer.config_file_validator import validate_conf
|
|
131
119
|
|
|
132
120
|
bouncer_config = validate_conf(
|
|
133
|
-
check_categories=check_categories,
|
|
121
|
+
check_categories=check_categories,
|
|
122
|
+
config_file_contents=dict(config_file_contents),
|
|
134
123
|
)
|
|
135
124
|
del config_file_contents
|
|
136
125
|
logging.debug(f"{bouncer_config=}")
|
|
@@ -152,7 +141,9 @@ def cli(
|
|
|
152
141
|
|
|
153
142
|
logging.debug(f"{bouncer_config=}")
|
|
154
143
|
|
|
155
|
-
dbt_artifacts_dir =
|
|
144
|
+
dbt_artifacts_dir = Path(
|
|
145
|
+
config_file_path.parent / (bouncer_config.dbt_artifacts_dir or "target")
|
|
146
|
+
)
|
|
156
147
|
|
|
157
148
|
from dbt_bouncer.artifact_parsers.parsers_common import parse_dbt_artifacts
|
|
158
149
|
|
|
@@ -161,6 +152,7 @@ def cli(
|
|
|
161
152
|
project_exposures,
|
|
162
153
|
project_macros,
|
|
163
154
|
project_models,
|
|
155
|
+
project_seeds,
|
|
164
156
|
project_semantic_models,
|
|
165
157
|
project_snapshots,
|
|
166
158
|
project_sources,
|
|
@@ -189,6 +181,7 @@ def cli(
|
|
|
189
181
|
output_file=output_file,
|
|
190
182
|
output_only_failures=output_only_failures,
|
|
191
183
|
run_results=project_run_results,
|
|
184
|
+
seeds=project_seeds,
|
|
192
185
|
semantic_models=project_semantic_models,
|
|
193
186
|
show_all_failures=show_all_failures,
|
|
194
187
|
snapshots=project_snapshots,
|
|
@@ -196,4 +189,72 @@ def cli(
|
|
|
196
189
|
tests=project_tests,
|
|
197
190
|
unit_tests=project_unit_tests,
|
|
198
191
|
)
|
|
199
|
-
|
|
192
|
+
return results[0]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@click.command()
|
|
196
|
+
@click.option(
|
|
197
|
+
"--config-file",
|
|
198
|
+
default=Path("dbt-bouncer.yml"),
|
|
199
|
+
help="Location of the YML config file.",
|
|
200
|
+
required=False,
|
|
201
|
+
type=PurePath,
|
|
202
|
+
)
|
|
203
|
+
@click.option(
|
|
204
|
+
"--create-pr-comment-file",
|
|
205
|
+
default=False,
|
|
206
|
+
help="Create a `github-comment.md` file that will be sent to GitHub as a PR comment. Defaults to True when `dbt-bouncer` is run as a GitHub Action.",
|
|
207
|
+
hidden=True,
|
|
208
|
+
required=False,
|
|
209
|
+
type=click.BOOL,
|
|
210
|
+
)
|
|
211
|
+
@click.option(
|
|
212
|
+
"--only",
|
|
213
|
+
default="",
|
|
214
|
+
help="Limit the checks run to specific categories, comma-separated. Examples: 'manifest_checks', 'catalog_checks,manifest_checks'.",
|
|
215
|
+
required=False,
|
|
216
|
+
type=str,
|
|
217
|
+
)
|
|
218
|
+
@click.option(
|
|
219
|
+
"--output-file",
|
|
220
|
+
default=None,
|
|
221
|
+
help="Location of the json file where check metadata will be saved.",
|
|
222
|
+
required=False,
|
|
223
|
+
type=Path,
|
|
224
|
+
)
|
|
225
|
+
@click.option(
|
|
226
|
+
"--output-only-failures",
|
|
227
|
+
help="If passed then only failures will be included in the output file.",
|
|
228
|
+
is_flag=True,
|
|
229
|
+
)
|
|
230
|
+
@click.option(
|
|
231
|
+
"--show-all-failures",
|
|
232
|
+
help="If passed then all failures will be printed to the console.",
|
|
233
|
+
is_flag=True,
|
|
234
|
+
)
|
|
235
|
+
@click.option("-v", "--verbosity", help="Verbosity.", default=0, count=True)
|
|
236
|
+
@click.pass_context
|
|
237
|
+
@click.version_option()
|
|
238
|
+
def cli(
|
|
239
|
+
ctx: click.Context,
|
|
240
|
+
config_file: PurePath,
|
|
241
|
+
create_pr_comment_file: bool,
|
|
242
|
+
only: str,
|
|
243
|
+
output_file: Path | None,
|
|
244
|
+
output_only_failures: bool,
|
|
245
|
+
show_all_failures: bool,
|
|
246
|
+
verbosity: int,
|
|
247
|
+
) -> None:
|
|
248
|
+
"""Entrypoint for dbt-bouncer."""
|
|
249
|
+
config_file_source = ctx.get_parameter_source("config_file").name # type: ignore[union-attr]
|
|
250
|
+
exit_code = run_bouncer(
|
|
251
|
+
config_file=config_file,
|
|
252
|
+
create_pr_comment_file=create_pr_comment_file,
|
|
253
|
+
only=only,
|
|
254
|
+
output_file=output_file,
|
|
255
|
+
output_only_failures=output_only_failures,
|
|
256
|
+
show_all_failures=show_all_failures,
|
|
257
|
+
verbosity=verbosity,
|
|
258
|
+
config_file_source=config_file_source,
|
|
259
|
+
)
|
|
260
|
+
ctx.exit(exit_code)
|
dbt_bouncer/runner.py
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
"""Assemble and run all checks."""
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
|
+
import inspect
|
|
4
5
|
import json
|
|
5
6
|
import logging
|
|
6
7
|
import operator
|
|
7
8
|
import traceback
|
|
8
9
|
from pathlib import Path
|
|
9
|
-
from typing import TYPE_CHECKING, Any
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
10
11
|
|
|
12
|
+
import click
|
|
11
13
|
from progress.bar import Bar
|
|
12
14
|
from tabulate import tabulate
|
|
13
15
|
|
|
16
|
+
from dbt_bouncer.checks.common import DbtBouncerFailedCheckError
|
|
14
17
|
from dbt_bouncer.utils import (
|
|
15
18
|
create_github_comment_file,
|
|
16
19
|
get_check_objects,
|
|
@@ -20,8 +23,6 @@ from dbt_bouncer.utils import (
|
|
|
20
23
|
|
|
21
24
|
if TYPE_CHECKING:
|
|
22
25
|
from dbt_bouncer.artifact_parsers.dbt_cloud.manifest_latest import (
|
|
23
|
-
Exposures,
|
|
24
|
-
Macros,
|
|
25
26
|
UnitTests,
|
|
26
27
|
)
|
|
27
28
|
from dbt_bouncer.artifact_parsers.parsers_common import (
|
|
@@ -29,11 +30,16 @@ if TYPE_CHECKING:
|
|
|
29
30
|
DbtBouncerManifest,
|
|
30
31
|
DbtBouncerModel,
|
|
31
32
|
DbtBouncerRunResult,
|
|
33
|
+
DbtBouncerSeed,
|
|
32
34
|
DbtBouncerSemanticModel,
|
|
33
35
|
DbtBouncerSnapshot,
|
|
34
36
|
DbtBouncerSource,
|
|
35
37
|
DbtBouncerTest,
|
|
36
38
|
)
|
|
39
|
+
from dbt_bouncer.artifact_parsers.parsers_manifest import (
|
|
40
|
+
DbtBouncerExposureBase,
|
|
41
|
+
DbtBouncerMacroBase,
|
|
42
|
+
)
|
|
37
43
|
from dbt_bouncer.config_file_parser import (
|
|
38
44
|
DbtBouncerConfAllCategories as DbtBouncerConf,
|
|
39
45
|
)
|
|
@@ -41,36 +47,46 @@ if TYPE_CHECKING:
|
|
|
41
47
|
|
|
42
48
|
def runner(
|
|
43
49
|
bouncer_config: "DbtBouncerConf",
|
|
44
|
-
catalog_nodes:
|
|
45
|
-
catalog_sources:
|
|
46
|
-
check_categories:
|
|
50
|
+
catalog_nodes: list["DbtBouncerCatalogNode"],
|
|
51
|
+
catalog_sources: list["DbtBouncerCatalogNode"],
|
|
52
|
+
check_categories: list[str],
|
|
47
53
|
create_pr_comment_file: bool,
|
|
48
|
-
exposures:
|
|
49
|
-
macros:
|
|
54
|
+
exposures: list["DbtBouncerExposureBase"],
|
|
55
|
+
macros: list["DbtBouncerMacroBase"],
|
|
50
56
|
manifest_obj: "DbtBouncerManifest",
|
|
51
|
-
models:
|
|
52
|
-
output_file:
|
|
53
|
-
run_results:
|
|
54
|
-
|
|
57
|
+
models: list["DbtBouncerModel"],
|
|
58
|
+
output_file: Path | None,
|
|
59
|
+
run_results: list["DbtBouncerRunResult"],
|
|
60
|
+
seeds: list["DbtBouncerSeed"],
|
|
61
|
+
semantic_models: list["DbtBouncerSemanticModel"],
|
|
55
62
|
output_only_failures: bool,
|
|
56
63
|
show_all_failures: bool,
|
|
57
|
-
snapshots:
|
|
58
|
-
sources:
|
|
59
|
-
tests:
|
|
60
|
-
unit_tests:
|
|
61
|
-
) -> tuple[int,
|
|
64
|
+
snapshots: list["DbtBouncerSnapshot"],
|
|
65
|
+
sources: list["DbtBouncerSource"],
|
|
66
|
+
tests: list["DbtBouncerTest"],
|
|
67
|
+
unit_tests: list["UnitTests"],
|
|
68
|
+
) -> tuple[int, list[Any]]:
|
|
62
69
|
"""Run dbt-bouncer checks.
|
|
63
70
|
|
|
64
71
|
Returns:
|
|
65
|
-
tuple[int,
|
|
72
|
+
tuple[int, list[Any]]: A tuple containing the exit code and a list of failed checks.
|
|
66
73
|
|
|
67
74
|
Raises:
|
|
68
75
|
RuntimeError: If more than one "iterate_over" argument is found.
|
|
69
76
|
|
|
70
77
|
"""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
try:
|
|
79
|
+
ctx = click.get_current_context()
|
|
80
|
+
config_file_path = ctx.obj.get("config_file_path")
|
|
81
|
+
custom_checks_dir = ctx.obj.get("custom_checks_dir")
|
|
82
|
+
if custom_checks_dir:
|
|
83
|
+
custom_checks_dir = config_file_path.parent / custom_checks_dir
|
|
84
|
+
except (RuntimeError, AttributeError, KeyError):
|
|
85
|
+
custom_checks_dir = None
|
|
86
|
+
|
|
87
|
+
check_classes: list[dict[str, Any | str]] = [
|
|
88
|
+
{"class": x, "source_file": inspect.getfile(x)}
|
|
89
|
+
for x in get_check_objects(custom_checks_dir)
|
|
74
90
|
]
|
|
75
91
|
for c in check_classes:
|
|
76
92
|
locals()[c["class"].__name__] = c["class"] # type: ignore[union-attr]
|
|
@@ -83,6 +99,7 @@ def runner(
|
|
|
83
99
|
"manifest_obj": manifest_obj,
|
|
84
100
|
"models": [m.model for m in models],
|
|
85
101
|
"run_results": [r.run_result for r in run_results],
|
|
102
|
+
"seeds": [s.seed for s in seeds],
|
|
86
103
|
"semantic_models": [s.semantic_model for s in semantic_models],
|
|
87
104
|
"snapshots": [s.snapshot for s in snapshots],
|
|
88
105
|
"sources": sources,
|
|
@@ -103,19 +120,26 @@ def runner(
|
|
|
103
120
|
"macro",
|
|
104
121
|
"model",
|
|
105
122
|
"run_result",
|
|
123
|
+
"seed",
|
|
106
124
|
"semantic_model",
|
|
107
125
|
"snapshot",
|
|
108
126
|
"source",
|
|
109
127
|
"unit_test",
|
|
110
128
|
}
|
|
111
129
|
iterate_over_value = valid_iterate_over_values.intersection(
|
|
112
|
-
set(check.__annotations__.keys()),
|
|
130
|
+
set(check.__class__.__annotations__.keys()),
|
|
113
131
|
)
|
|
114
132
|
if len(iterate_over_value) == 1:
|
|
115
133
|
iterate_value = next(iter(iterate_over_value))
|
|
116
134
|
for i in locals()[f"{iterate_value}s"]:
|
|
117
135
|
check_i = copy.deepcopy(check)
|
|
118
|
-
if iterate_value in [
|
|
136
|
+
if iterate_value in [
|
|
137
|
+
"model",
|
|
138
|
+
"seed",
|
|
139
|
+
"semantic_model",
|
|
140
|
+
"snapshot",
|
|
141
|
+
"source",
|
|
142
|
+
]:
|
|
119
143
|
try:
|
|
120
144
|
d = getattr(i, iterate_value).config.meta
|
|
121
145
|
except Exception:
|
|
@@ -153,7 +177,9 @@ def runner(
|
|
|
153
177
|
)
|
|
154
178
|
setattr(check_i, iterate_value, getattr(i, iterate_value, i))
|
|
155
179
|
|
|
156
|
-
for x in
|
|
180
|
+
for x in (
|
|
181
|
+
parsed_data.keys() & check_i.__class__.__annotations__.keys()
|
|
182
|
+
):
|
|
157
183
|
setattr(check_i, x, parsed_data[x])
|
|
158
184
|
|
|
159
185
|
checks_to_run.append(
|
|
@@ -169,7 +195,7 @@ def runner(
|
|
|
169
195
|
)
|
|
170
196
|
else:
|
|
171
197
|
check_run_id = f"{check.name}:{check.index}"
|
|
172
|
-
for x in parsed_data.keys() & check.__annotations__.keys():
|
|
198
|
+
for x in parsed_data.keys() & check.__class__.__annotations__.keys():
|
|
173
199
|
setattr(check, x, parsed_data[x])
|
|
174
200
|
checks_to_run.append(
|
|
175
201
|
{
|
|
@@ -188,21 +214,25 @@ def runner(
|
|
|
188
214
|
check["check"].execute()
|
|
189
215
|
check["outcome"] = "success"
|
|
190
216
|
except Exception as e:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
217
|
+
if isinstance(e, DbtBouncerFailedCheckError):
|
|
218
|
+
failure_message = e.message
|
|
219
|
+
else:
|
|
220
|
+
failure_message_full = list(
|
|
221
|
+
traceback.TracebackException.from_exception(e).format(),
|
|
222
|
+
)
|
|
223
|
+
failure_message = failure_message_full[-1].strip()
|
|
224
|
+
|
|
195
225
|
if check["check"].description:
|
|
196
226
|
failure_message = f"{check['check'].description} - {failure_message}"
|
|
197
227
|
|
|
198
228
|
logging.debug(
|
|
199
|
-
f"Check {check['check_run_id']} failed: {' '.join(
|
|
229
|
+
f"Check {check['check_run_id']} failed: {' '.join(failure_message)}"
|
|
200
230
|
)
|
|
201
231
|
check["outcome"] = "failed"
|
|
202
232
|
check["failure_message"] = failure_message
|
|
203
233
|
|
|
204
234
|
# If a check encountered an issue, change severity to warn
|
|
205
|
-
if not isinstance(e,
|
|
235
|
+
if not isinstance(e, DbtBouncerFailedCheckError):
|
|
206
236
|
check["severity"] = "warn"
|
|
207
237
|
check["failure_message"] = (
|
|
208
238
|
f"`dbt-bouncer` encountered an error ({failure_message}), run with `-v` to see more details or report an issue at https://github.com/godatadriven/dbt-bouncer/issues."
|