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
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import TYPE_CHECKING, Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import ConfigDict, Field
|
|
5
|
+
|
|
6
|
+
from dbt_bouncer.check_base import BaseCheck
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from dbt_bouncer.artifact_parsers.parsers_manifest import (
|
|
10
|
+
DbtBouncerSeedBase,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from dbt_bouncer.checks.common import DbtBouncerFailedCheckError
|
|
14
|
+
from dbt_bouncer.utils import get_clean_model_name
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CheckSeedNames(BaseCheck):
|
|
18
|
+
"""Seed must have a name that matches the supplied regex.
|
|
19
|
+
|
|
20
|
+
Parameters:
|
|
21
|
+
seed_name_pattern (str): Regexp the seed name must match.
|
|
22
|
+
|
|
23
|
+
Receives:
|
|
24
|
+
seed (DbtBouncerSeedBase): The DbtBouncerSeedBase object to check.
|
|
25
|
+
|
|
26
|
+
Other Parameters:
|
|
27
|
+
description (str | None): Description of what the check does and why it is implemented.
|
|
28
|
+
exclude (str | None): Regex pattern to match the seed path. Seed paths that match the pattern will not be checked.
|
|
29
|
+
include (str | None): Regex pattern to match the seed path. Only seed paths that match the pattern will be checked.
|
|
30
|
+
severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.
|
|
31
|
+
|
|
32
|
+
Example(s):
|
|
33
|
+
```yaml
|
|
34
|
+
manifest_checks:
|
|
35
|
+
- name: check_seed_names
|
|
36
|
+
include: ^seeds
|
|
37
|
+
model_name_pattern: ^raw_
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
43
|
+
|
|
44
|
+
name: Literal["check_seed_names"]
|
|
45
|
+
seed: "DbtBouncerSeedBase | None" = Field(default=None)
|
|
46
|
+
seed_name_pattern: str
|
|
47
|
+
|
|
48
|
+
def execute(self) -> None:
|
|
49
|
+
"""Execute the check.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
DbtBouncerFailedCheckError: If model name does not match regex.
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
if self.seed is None:
|
|
56
|
+
raise DbtBouncerFailedCheckError("self.seed is None")
|
|
57
|
+
if (
|
|
58
|
+
re.compile(self.seed_name_pattern.strip()).match(str(self.seed.name))
|
|
59
|
+
is None
|
|
60
|
+
):
|
|
61
|
+
raise DbtBouncerFailedCheckError(
|
|
62
|
+
f"`{get_clean_model_name(self.seed.unique_id)}` does not match the supplied regex `{self.seed_name_pattern.strip()}`."
|
|
63
|
+
)
|
|
@@ -1,31 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from typing import TYPE_CHECKING, List, Literal
|
|
1
|
+
from typing import TYPE_CHECKING, Literal
|
|
5
2
|
|
|
6
3
|
from dbt_bouncer.check_base import BaseCheck
|
|
7
4
|
|
|
8
5
|
if TYPE_CHECKING:
|
|
9
|
-
from dbt_bouncer.artifact_parsers.
|
|
6
|
+
from dbt_bouncer.artifact_parsers.parsers_manifest import (
|
|
10
7
|
DbtBouncerModelBase,
|
|
11
8
|
DbtBouncerSemanticModelBase,
|
|
12
9
|
)
|
|
13
10
|
|
|
14
11
|
from pydantic import Field
|
|
15
12
|
|
|
13
|
+
from dbt_bouncer.checks.common import DbtBouncerFailedCheckError
|
|
14
|
+
|
|
16
15
|
|
|
17
16
|
class CheckSemanticModelOnNonPublicModels(BaseCheck):
|
|
18
17
|
"""Semantic models should be based on public models only.
|
|
19
18
|
|
|
20
19
|
Receives:
|
|
21
|
-
models (
|
|
20
|
+
models (list[DbtBouncerModelBase]): List of DbtBouncerModelBase objects parsed from `manifest.json`.
|
|
22
21
|
semantic_model (DbtBouncerSemanticModelBase): The DbtBouncerSemanticModelBase object to check.
|
|
23
22
|
|
|
24
23
|
Other Parameters:
|
|
25
|
-
description (
|
|
26
|
-
exclude (
|
|
27
|
-
include (
|
|
28
|
-
severity (
|
|
24
|
+
description (str | None): Description of what the check does and why it is implemented.
|
|
25
|
+
exclude (str | None): Regex pattern to match the semantic model path (i.e the .yml file where the semantic model is configured). Semantic model paths that match the pattern will not be checked.
|
|
26
|
+
include (str | None): Regex pattern to match the semantic model path (i.e the .yml file where the semantic model is configured). Only semantic model paths that match the pattern will be checked.
|
|
27
|
+
severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.
|
|
29
28
|
|
|
30
29
|
Example(s):
|
|
31
30
|
```yaml
|
|
@@ -35,24 +34,33 @@ class CheckSemanticModelOnNonPublicModels(BaseCheck):
|
|
|
35
34
|
|
|
36
35
|
"""
|
|
37
36
|
|
|
38
|
-
models:
|
|
37
|
+
models: list["DbtBouncerModelBase"] = Field(default=[])
|
|
39
38
|
name: Literal["check_semantic_model_based_on_non_public_models"]
|
|
40
|
-
semantic_model: "DbtBouncerSemanticModelBase" = Field(default=None)
|
|
39
|
+
semantic_model: "DbtBouncerSemanticModelBase | None" = Field(default=None)
|
|
41
40
|
|
|
42
41
|
def execute(self) -> None:
|
|
43
|
-
"""Execute the check.
|
|
42
|
+
"""Execute the check.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
DbtBouncerFailedCheckError: If semantic model is based on non-public models.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
if self.semantic_model is None:
|
|
49
|
+
raise DbtBouncerFailedCheckError("self.semantic_model is None")
|
|
50
|
+
|
|
44
51
|
non_public_upstream_dependencies = []
|
|
45
|
-
for model in self.semantic_model.depends_on
|
|
52
|
+
for model in getattr(self.semantic_model.depends_on, "nodes", []) or []:
|
|
46
53
|
if (
|
|
47
54
|
next(m for m in self.models if m.unique_id == model).resource_type
|
|
48
55
|
== "model"
|
|
49
56
|
and next(m for m in self.models if m.unique_id == model).package_name
|
|
50
57
|
== self.semantic_model.package_name
|
|
51
58
|
):
|
|
52
|
-
|
|
53
|
-
if
|
|
54
|
-
non_public_upstream_dependencies.append(
|
|
59
|
+
model_obj = next(m for m in self.models if m.unique_id == model)
|
|
60
|
+
if model_obj.access and model_obj.access.value != "public":
|
|
61
|
+
non_public_upstream_dependencies.append(model_obj.name)
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
if non_public_upstream_dependencies:
|
|
64
|
+
raise DbtBouncerFailedCheckError(
|
|
65
|
+
f"Semantic model `{self.semantic_model.name}` is based on a model(s) that is not public: {non_public_upstream_dependencies}."
|
|
66
|
+
)
|
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
# mypy: disable-error-code="union-attr"
|
|
2
1
|
import re
|
|
3
|
-
from typing import TYPE_CHECKING,
|
|
2
|
+
from typing import TYPE_CHECKING, Literal
|
|
4
3
|
|
|
5
4
|
from pydantic import Field
|
|
6
5
|
|
|
7
6
|
from dbt_bouncer.check_base import BaseCheck
|
|
8
7
|
|
|
9
8
|
if TYPE_CHECKING:
|
|
10
|
-
from dbt_bouncer.artifact_parsers.
|
|
9
|
+
from dbt_bouncer.artifact_parsers.parsers_manifest import (
|
|
11
10
|
DbtBouncerSnapshotBase,
|
|
12
11
|
)
|
|
13
12
|
|
|
13
|
+
from dbt_bouncer.checks.common import DbtBouncerFailedCheckError
|
|
14
|
+
|
|
14
15
|
|
|
15
16
|
class CheckSnapshotHasTags(BaseCheck):
|
|
16
17
|
"""Snapshots must have the specified tags.
|
|
17
18
|
|
|
18
19
|
Parameters:
|
|
19
|
-
criteria: (
|
|
20
|
+
criteria: (Literal["any", "all", "one"] | None): Whether the snapshot must have any, all, or exactly one of the specified tags. Default: `all`.
|
|
20
21
|
snapshot (DbtBouncerSnapshotBase): The DbtBouncerSnapshotBase object to check.
|
|
21
|
-
tags (
|
|
22
|
+
tags (list[str]): List of tags to check for.
|
|
22
23
|
|
|
23
24
|
Other Parameters:
|
|
24
|
-
description (
|
|
25
|
-
exclude (
|
|
26
|
-
include (
|
|
27
|
-
severity (
|
|
25
|
+
description (str | None): Description of what the check does and why it is implemented.
|
|
26
|
+
exclude (str | None): Regex pattern to match the snapshot path. Snapshot paths that match the pattern will not be checked.
|
|
27
|
+
include (str | None): Regex pattern to match the snapshot path. Only snapshot paths that match the pattern will be checked.
|
|
28
|
+
severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.
|
|
28
29
|
|
|
29
30
|
Example(s):
|
|
30
31
|
```yaml
|
|
@@ -39,22 +40,35 @@ class CheckSnapshotHasTags(BaseCheck):
|
|
|
39
40
|
|
|
40
41
|
criteria: Literal["any", "all", "one"] = Field(default="all")
|
|
41
42
|
name: Literal["check_snapshot_has_tags"]
|
|
42
|
-
snapshot: "DbtBouncerSnapshotBase" = Field(default=None)
|
|
43
|
-
tags:
|
|
43
|
+
snapshot: "DbtBouncerSnapshotBase | None" = Field(default=None)
|
|
44
|
+
tags: list[str]
|
|
44
45
|
|
|
45
46
|
def execute(self) -> None:
|
|
46
|
-
"""Execute the check.
|
|
47
|
+
"""Execute the check.
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
DbtBouncerFailedCheckError: If snapshot does not have required tags.
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
if self.snapshot is None:
|
|
54
|
+
raise DbtBouncerFailedCheckError("self.snapshot is None")
|
|
55
|
+
snapshot_tags = self.snapshot.tags or []
|
|
47
56
|
if self.criteria == "any":
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
if not any(tag in snapshot_tags for tag in self.tags):
|
|
58
|
+
raise DbtBouncerFailedCheckError(
|
|
59
|
+
f"`{self.snapshot.name}` does not have any of the required tags: {self.tags}."
|
|
60
|
+
)
|
|
51
61
|
elif self.criteria == "all":
|
|
52
|
-
missing_tags = [tag for tag in self.tags if tag not in
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
missing_tags = [tag for tag in self.tags if tag not in snapshot_tags]
|
|
63
|
+
if missing_tags:
|
|
64
|
+
raise DbtBouncerFailedCheckError(
|
|
65
|
+
f"`{self.snapshot.name}` is missing required tags: {missing_tags}."
|
|
66
|
+
)
|
|
67
|
+
elif (
|
|
68
|
+
self.criteria == "one"
|
|
69
|
+
and sum(tag in snapshot_tags for tag in self.tags) != 1
|
|
70
|
+
):
|
|
71
|
+
raise DbtBouncerFailedCheckError(
|
|
58
72
|
f"`{self.snapshot.name}` must have exactly one of the required tags: {self.tags}."
|
|
59
73
|
)
|
|
60
74
|
|
|
@@ -69,10 +83,10 @@ class CheckSnapshotNames(BaseCheck):
|
|
|
69
83
|
snapshot (DbtBouncerSnapshotBase): The DbtBouncerSnapshotBase object to check.
|
|
70
84
|
|
|
71
85
|
Other Parameters:
|
|
72
|
-
description (
|
|
73
|
-
exclude (
|
|
74
|
-
include (
|
|
75
|
-
severity (
|
|
86
|
+
description (str | None): Description of what the check does and why it is implemented.
|
|
87
|
+
exclude (str | None): Regex pattern to match the snapshot path. Snapshot paths that match the pattern will not be checked.
|
|
88
|
+
include (str | None): Regex pattern to match the snapshot path. Only snapshot paths that match the pattern will be checked.
|
|
89
|
+
severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.
|
|
76
90
|
|
|
77
91
|
Example(s):
|
|
78
92
|
```yaml
|
|
@@ -85,14 +99,24 @@ class CheckSnapshotNames(BaseCheck):
|
|
|
85
99
|
"""
|
|
86
100
|
|
|
87
101
|
name: Literal["check_snapshot_names"]
|
|
88
|
-
snapshot: "DbtBouncerSnapshotBase" = Field(default=None)
|
|
102
|
+
snapshot: "DbtBouncerSnapshotBase | None" = Field(default=None)
|
|
89
103
|
snapshot_name_pattern: str
|
|
90
104
|
|
|
91
105
|
def execute(self) -> None:
|
|
92
|
-
"""Execute the check.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
"""Execute the check.
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
DbtBouncerFailedCheckError: If snapshot name does not match regex.
|
|
110
|
+
|
|
111
|
+
"""
|
|
112
|
+
if self.snapshot is None:
|
|
113
|
+
raise DbtBouncerFailedCheckError("self.snapshot is None")
|
|
114
|
+
if (
|
|
115
|
+
re.compile(self.snapshot_name_pattern.strip()).match(
|
|
116
|
+
str(self.snapshot.name)
|
|
117
|
+
)
|
|
118
|
+
is None
|
|
119
|
+
):
|
|
120
|
+
raise DbtBouncerFailedCheckError(
|
|
121
|
+
f"`{self.snapshot.name}` does not match the supplied regex `{self.snapshot_name_pattern.strip()})`."
|
|
122
|
+
)
|