dbt-bouncer 1.25.0__tar.gz → 1.27.0__tar.gz
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-1.25.0 → dbt_bouncer-1.27.0}/PKG-INFO +4 -5
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/pyproject.toml +4 -5
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/catalog/check_columns.py +52 -2
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_exposures.py +47 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_models.py +44 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/runner.py +29 -6
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/utils.py +34 -1
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/version.py +1 -1
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/LICENSE +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/README.md +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/__init__.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/dbt_cloud/README.md +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/dbt_cloud/catalog_latest.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/dbt_cloud/manifest_latest.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/dbt_cloud/run_results_latest.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/parsers_catalog.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/parsers_common.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/parsers_manifest.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/parsers_run_results.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/check_base.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/catalog/check_catalog_sources.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/common.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_lineage.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_macros.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_metadata.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_semantic_models.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_snapshots.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_sources.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_unit_tests.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/run_results/check_run_results.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/config_file_parser.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/config_file_validator.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/logger.py +0 -0
- {dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/main.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dbt-bouncer
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.27.0
|
|
4
4
|
Summary: Configure and enforce conventions for your dbt project.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: python,cli,dbt,CI/CD
|
|
@@ -8,12 +8,10 @@ Author: Padraic Slattery
|
|
|
8
8
|
Author-email: pgoslatara@gmail.com
|
|
9
9
|
Maintainer: Padraic Slattery
|
|
10
10
|
Maintainer-email: pgoslatara@gmail.com
|
|
11
|
-
Requires-Python: >=3.
|
|
11
|
+
Requires-Python: >=3.11,<3.14
|
|
12
12
|
Classifier: Programming Language :: Python
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -22,12 +20,13 @@ Requires-Dist: dbt-artifacts-parser (>=0.8)
|
|
|
22
20
|
Requires-Dist: h11 (>=0.16.0)
|
|
23
21
|
Requires-Dist: jinja2 (>=3,<4)
|
|
24
22
|
Requires-Dist: jinja2-simple-tags (<1)
|
|
25
|
-
Requires-Dist: levenshtein (>=0.26.1,<
|
|
23
|
+
Requires-Dist: levenshtein (>=0.26.1,<0.27.3)
|
|
26
24
|
Requires-Dist: packaging (<25)
|
|
27
25
|
Requires-Dist: poetry (>=2.0.1,<3.0.0)
|
|
28
26
|
Requires-Dist: progress
|
|
29
27
|
Requires-Dist: pydantic (>=2,<3)
|
|
30
28
|
Requires-Dist: pyyaml (<7)
|
|
29
|
+
Requires-Dist: rapidfuzz (<3.14.0)
|
|
31
30
|
Requires-Dist: requests (>=2,<3)
|
|
32
31
|
Requires-Dist: semver (<4)
|
|
33
32
|
Requires-Dist: tabulate (<1)
|
|
@@ -4,8 +4,6 @@ classifiers = [
|
|
|
4
4
|
"Programming Language :: Python",
|
|
5
5
|
"Programming Language :: Python :: 3",
|
|
6
6
|
"Programming Language :: Python :: 3 :: Only",
|
|
7
|
-
"Programming Language :: Python :: 3.9",
|
|
8
|
-
"Programming Language :: Python :: 3.10",
|
|
9
7
|
"Programming Language :: Python :: 3.11",
|
|
10
8
|
"Programming Language :: Python :: 3.12",
|
|
11
9
|
"Programming Language :: Python :: 3.13",
|
|
@@ -16,11 +14,12 @@ dependencies =[
|
|
|
16
14
|
"h11 (>=0.16.0)", # To fix security warning
|
|
17
15
|
"jinja2 (>=3,<4)",
|
|
18
16
|
"jinja2-simple-tags (<1)",
|
|
19
|
-
"levenshtein (>=0.26.1,<
|
|
17
|
+
"levenshtein (>=0.26.1,<0.27.3)",
|
|
20
18
|
"packaging (<25)",
|
|
21
19
|
"progress",
|
|
22
20
|
"pydantic (>=2,<3)",
|
|
23
21
|
"pyyaml (<7)",
|
|
22
|
+
"rapidfuzz (<3.14.0)",
|
|
24
23
|
"requests (>=2,<3)",
|
|
25
24
|
"tabulate (<1)",
|
|
26
25
|
"toml (<1)",
|
|
@@ -39,8 +38,8 @@ maintainers = [{name="Padraic Slattery",email="pgoslatara@gmail.com"}]
|
|
|
39
38
|
name = "dbt-bouncer"
|
|
40
39
|
readme = "README.md"
|
|
41
40
|
repository = "https://github.com/godatadriven/dbt-bouncer"
|
|
42
|
-
requires-python = ">=3.
|
|
43
|
-
version = "1.
|
|
41
|
+
requires-python = ">=3.11,<3.14"
|
|
42
|
+
version = "1.27.0"
|
|
44
43
|
|
|
45
44
|
[project.scripts]
|
|
46
45
|
dbt-bouncer = "dbt_bouncer.main:cli"
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import re
|
|
4
4
|
from typing import TYPE_CHECKING, List, Literal, Optional
|
|
5
5
|
|
|
6
|
+
from pydantic import ConfigDict, Field
|
|
7
|
+
|
|
6
8
|
if TYPE_CHECKING:
|
|
7
9
|
import warnings
|
|
8
10
|
|
|
@@ -16,7 +18,7 @@ if TYPE_CHECKING:
|
|
|
16
18
|
DbtBouncerTestBase,
|
|
17
19
|
)
|
|
18
20
|
|
|
19
|
-
from pydantic import
|
|
21
|
+
from pydantic import model_validator
|
|
20
22
|
|
|
21
23
|
from dbt_bouncer.check_base import BaseCheck
|
|
22
24
|
|
|
@@ -130,7 +132,7 @@ class CheckColumnHasSpecifiedTest(BaseCheck):
|
|
|
130
132
|
class CheckColumnNameCompliesToColumnType(BaseCheck):
|
|
131
133
|
"""Columns with the specified regexp naming pattern must have data types that comply to the specified regexp pattern or list of data types.
|
|
132
134
|
|
|
133
|
-
Note:
|
|
135
|
+
Note: One of `type_pattern` or `types` must be specified.
|
|
134
136
|
|
|
135
137
|
Parameters:
|
|
136
138
|
column_name_pattern (str): Regex pattern to match the model name.
|
|
@@ -228,6 +230,54 @@ class CheckColumnNameCompliesToColumnType(BaseCheck):
|
|
|
228
230
|
return self
|
|
229
231
|
|
|
230
232
|
|
|
233
|
+
class CheckColumnNames(BaseCheck):
|
|
234
|
+
"""Columns must have a name that matches the supplied regex.
|
|
235
|
+
|
|
236
|
+
Parameters:
|
|
237
|
+
columns_name_pattern (str): Regexp the column name must match.
|
|
238
|
+
|
|
239
|
+
Receives:
|
|
240
|
+
catalog_node (CatalogNodes): The CatalogNodes object to check.
|
|
241
|
+
models (List[DbtBouncerModelBase]): List of DbtBouncerModelBase objects parsed from `manifest.json`.
|
|
242
|
+
|
|
243
|
+
Other Parameters:
|
|
244
|
+
description (Optional[str]): Description of what the check does and why it is implemented.
|
|
245
|
+
exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
|
|
246
|
+
include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
|
|
247
|
+
materialization (Optional[Literal["ephemeral", "incremental", "table", "view"]]): Limit check to models with the specified materialization.
|
|
248
|
+
severity (Optional[Literal["error", "warn"]]): Severity level of the check. Default: `error`.
|
|
249
|
+
|
|
250
|
+
Example(s):
|
|
251
|
+
```yaml
|
|
252
|
+
catalog_checks:
|
|
253
|
+
- name: check_column_names
|
|
254
|
+
column_name_pattern: [a-z_] # Lowercase only, underscores allowed
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
260
|
+
|
|
261
|
+
catalog_node: "CatalogNodes" = Field(default=None)
|
|
262
|
+
column_name_pattern: str
|
|
263
|
+
models: List["DbtBouncerModelBase"] = Field(default=[])
|
|
264
|
+
name: Literal["check_column_names"]
|
|
265
|
+
|
|
266
|
+
def execute(self) -> None:
|
|
267
|
+
"""Execute the check."""
|
|
268
|
+
if self.is_catalog_node_a_model(self.catalog_node, self.models):
|
|
269
|
+
non_complying_columns: List[str] = []
|
|
270
|
+
non_complying_columns.extend(
|
|
271
|
+
v.name
|
|
272
|
+
for _, v in self.catalog_node.columns.items()
|
|
273
|
+
if re.fullmatch(self.column_name_pattern.strip(), v.name) is None
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
assert not non_complying_columns, (
|
|
277
|
+
f"`{self.catalog_node.unique_id.split('.')[-1]}` has columns ({non_complying_columns}) that do not match the supplied regex: `{self.column_name_pattern.strip()}`."
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
231
281
|
class CheckColumnsAreAllDocumented(BaseCheck):
|
|
232
282
|
"""All columns in a model should be included in the model's properties file, i.e. `.yml` file.
|
|
233
283
|
|
{dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_exposures.py
RENAMED
|
@@ -14,6 +14,53 @@ if TYPE_CHECKING:
|
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
class CheckExposureOnModel(BaseCheck):
|
|
18
|
+
"""Exposures should depend on a model.
|
|
19
|
+
|
|
20
|
+
Parameters:
|
|
21
|
+
max_number_of_models (Optional[int]): The maximum number of models an exposure can depend on, defaults to 100.
|
|
22
|
+
min_number_of_models (Optional[int]): The minimum number of models an exposure can depend on, defaults to 1.
|
|
23
|
+
|
|
24
|
+
Receives:
|
|
25
|
+
exposure (DbtBouncerExposureBase): The DbtBouncerExposureBase object to check.
|
|
26
|
+
|
|
27
|
+
Other Parameters:
|
|
28
|
+
description (Optional[str]): Description of what the check does and why it is implemented.
|
|
29
|
+
exclude (Optional[str]): Regex pattern to match the exposure path (i.e the .yml file where the exposure is configured). Exposure paths that match the pattern will not be checked.
|
|
30
|
+
include (Optional[str]): Regex pattern to match the exposure path (i.e the .yml file where the exposure is configured). Only exposure paths that match the pattern will be checked.
|
|
31
|
+
severity (Optional[Literal["error", "warn"]]): Severity level of the check. Default: `error`.
|
|
32
|
+
|
|
33
|
+
Example(s):
|
|
34
|
+
```yaml
|
|
35
|
+
manifest_checks:
|
|
36
|
+
- name: check_exposure_based_on_model
|
|
37
|
+
```
|
|
38
|
+
```yaml
|
|
39
|
+
manifest_checks:
|
|
40
|
+
- name: check_exposure_based_on_model
|
|
41
|
+
maximum_number_of_models: 3
|
|
42
|
+
minimum_number_of_models: 1
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
exposure: "DbtBouncerExposureBase" = Field(default=None)
|
|
48
|
+
maximum_number_of_models: int = Field(default=100)
|
|
49
|
+
minimum_number_of_models: int = Field(default=1)
|
|
50
|
+
name: Literal["check_exposure_based_on_model"]
|
|
51
|
+
|
|
52
|
+
def execute(self) -> None:
|
|
53
|
+
"""Execute the check."""
|
|
54
|
+
number_of_upstream_models = len(self.exposure.depends_on.nodes)
|
|
55
|
+
|
|
56
|
+
assert self.minimum_number_of_models <= number_of_upstream_models, (
|
|
57
|
+
f"`{self.exposure.name}` is based on less models ({number_of_upstream_models}) than the minimum permitted ({self.minimum_number_of_models})."
|
|
58
|
+
)
|
|
59
|
+
assert number_of_upstream_models <= self.maximum_number_of_models, (
|
|
60
|
+
f"`{self.exposure.name}` is based on more models ({number_of_upstream_models}) than the maximum permitted ({self.maximum_number_of_models})."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
17
64
|
class CheckExposureOnNonPublicModels(BaseCheck):
|
|
18
65
|
"""Exposures should be based on public models only.
|
|
19
66
|
|
|
@@ -14,6 +14,7 @@ if TYPE_CHECKING:
|
|
|
14
14
|
UnitTests,
|
|
15
15
|
)
|
|
16
16
|
from dbt_bouncer.artifact_parsers.parsers_common import (
|
|
17
|
+
DbtBouncerExposureBase,
|
|
17
18
|
DbtBouncerManifest,
|
|
18
19
|
DbtBouncerModelBase,
|
|
19
20
|
DbtBouncerTestBase,
|
|
@@ -521,6 +522,49 @@ class CheckModelHasContractsEnforced(BaseCheck):
|
|
|
521
522
|
)
|
|
522
523
|
|
|
523
524
|
|
|
525
|
+
class CheckModelHasExposure(BaseCheck):
|
|
526
|
+
"""Models must have an exposure.
|
|
527
|
+
|
|
528
|
+
Receives:
|
|
529
|
+
exposures (List[DbtBouncerExposureBase]): List of DbtBouncerExposureBase objects parsed from `manifest.json`.
|
|
530
|
+
model (DbtBouncerModelBase): The DbtBouncerModelBase object to check.
|
|
531
|
+
|
|
532
|
+
Other Parameters:
|
|
533
|
+
description (Optional[str]): Description of what the check does and why it is implemented.
|
|
534
|
+
exclude (Optional[str]): Regex pattern to match the model path. Model paths that match the pattern will not be checked.
|
|
535
|
+
include (Optional[str]): Regex pattern to match the model path. Only model paths that match the pattern will be checked.
|
|
536
|
+
materialization (Optional[Literal["ephemeral", "incremental", "table", "view"]]): Limit check to models with the specified materialization.
|
|
537
|
+
severity (Optional[Literal["error", "warn"]]): Severity level of the check. Default: `error`.
|
|
538
|
+
|
|
539
|
+
Example(s):
|
|
540
|
+
```yaml
|
|
541
|
+
manifest_checks:
|
|
542
|
+
- name: check_model_has_exposure
|
|
543
|
+
description: Ensure all marts are part of an exposure.
|
|
544
|
+
include: ^models/marts
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
"""
|
|
548
|
+
|
|
549
|
+
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
|
550
|
+
|
|
551
|
+
exposures: List["DbtBouncerExposureBase"] = Field(default=[])
|
|
552
|
+
model: "DbtBouncerModelBase" = Field(default=None)
|
|
553
|
+
name: Literal["check_model_has_exposure"]
|
|
554
|
+
|
|
555
|
+
def execute(self) -> None:
|
|
556
|
+
"""Execute the check."""
|
|
557
|
+
has_exposure = False
|
|
558
|
+
for e in self.exposures:
|
|
559
|
+
for m in e.depends_on.nodes:
|
|
560
|
+
if m == self.model.unique_id:
|
|
561
|
+
has_exposure = True
|
|
562
|
+
|
|
563
|
+
assert has_exposure, (
|
|
564
|
+
f"`{get_clean_model_name(self.model.unique_id)}` does not have an associated exposure."
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
|
|
524
568
|
class CheckModelHasMetaKeys(BaseCheck):
|
|
525
569
|
"""The `meta` config for models must have the specified keys.
|
|
526
570
|
|
|
@@ -14,6 +14,7 @@ from tabulate import tabulate
|
|
|
14
14
|
from dbt_bouncer.utils import (
|
|
15
15
|
create_github_comment_file,
|
|
16
16
|
get_check_objects,
|
|
17
|
+
get_nested_value,
|
|
17
18
|
resource_in_path,
|
|
18
19
|
)
|
|
19
20
|
|
|
@@ -114,14 +115,36 @@ def runner(
|
|
|
114
115
|
iterate_value = next(iter(iterate_over_value))
|
|
115
116
|
for i in locals()[f"{iterate_value}s"]:
|
|
116
117
|
check_i = copy.deepcopy(check)
|
|
118
|
+
if iterate_value in ["model", "semantic_model", "snapshot", "source"]:
|
|
119
|
+
try:
|
|
120
|
+
d = getattr(i, iterate_value).config.meta
|
|
121
|
+
except Exception:
|
|
122
|
+
d = getattr(i, iterate_value).meta
|
|
123
|
+
elif iterate_value in ["catalog_node", "run_result"]:
|
|
124
|
+
d = {}
|
|
125
|
+
elif iterate_value in ["macro"]:
|
|
126
|
+
d = i.meta
|
|
127
|
+
else:
|
|
128
|
+
try:
|
|
129
|
+
d = i.config.meta
|
|
130
|
+
except Exception:
|
|
131
|
+
d = i.meta
|
|
132
|
+
meta_config = get_nested_value(
|
|
133
|
+
d,
|
|
134
|
+
["dbt-bouncer", "skip_checks"],
|
|
135
|
+
[],
|
|
136
|
+
)
|
|
117
137
|
if resource_in_path(check_i, i) and (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
138
|
+
(
|
|
139
|
+
iterate_over_value != {"model"}
|
|
140
|
+
or (
|
|
141
|
+
iterate_over_value == {"model"}
|
|
142
|
+
and check_i.materialization == i.model.config.materialized
|
|
143
|
+
if check_i.materialization is not None
|
|
144
|
+
else True
|
|
145
|
+
)
|
|
124
146
|
)
|
|
147
|
+
and (check_i.name not in meta_config if meta_config != [] else True)
|
|
125
148
|
):
|
|
126
149
|
check_run_id = (
|
|
127
150
|
f"{check_i.name}:{check_i.index}:{i.unique_id.split('.')[-1]}"
|
|
@@ -9,7 +9,7 @@ import re
|
|
|
9
9
|
import sys
|
|
10
10
|
from functools import lru_cache
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import TYPE_CHECKING, Any, List, Mapping, Type
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Type
|
|
13
13
|
|
|
14
14
|
import click
|
|
15
15
|
import yaml
|
|
@@ -58,6 +58,39 @@ def create_github_comment_file(
|
|
|
58
58
|
f.write(md_formatted_comment)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
def get_nested_value(
|
|
62
|
+
d: Dict[str, Any], keys: List[str], default: Optional[Any] = None
|
|
63
|
+
) -> Any:
|
|
64
|
+
"""Retrieve a value from a nested dictionary using a list of keys.
|
|
65
|
+
|
|
66
|
+
This function safely traverses a dictionary structure, allowing access to
|
|
67
|
+
deeply nested values. If any key in the `keys` list does not exist at
|
|
68
|
+
its respective level, the function returns the specified default value.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
d: The dictionary to traverse.
|
|
72
|
+
keys: A list of strings representing the sequence of keys to follow
|
|
73
|
+
to reach the desired nested value.
|
|
74
|
+
default: The value to return if any key in the `keys` list is not
|
|
75
|
+
found at its corresponding level, or if an intermediate
|
|
76
|
+
value is not a dictionary. Defaults to None.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The value found at the specified nested path, or the `default` value
|
|
80
|
+
if any part of the path is invalid or not found.
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
current_level = d
|
|
84
|
+
for key in keys:
|
|
85
|
+
if isinstance(current_level, dict):
|
|
86
|
+
current_level = current_level.get(key, default) # type: ignore[assignment]
|
|
87
|
+
if current_level is default and key != keys[-1]:
|
|
88
|
+
return default
|
|
89
|
+
else:
|
|
90
|
+
return default
|
|
91
|
+
return current_level
|
|
92
|
+
|
|
93
|
+
|
|
61
94
|
def resource_in_path(check, resource) -> bool:
|
|
62
95
|
"""Validate that a check is applicable to a specific resource path.
|
|
63
96
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/dbt_cloud/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/parsers_catalog.py
RENAMED
|
File without changes
|
{dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/parsers_common.py
RENAMED
|
File without changes
|
{dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/parsers_manifest.py
RENAMED
|
File without changes
|
{dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/artifact_parsers/parsers_run_results.py
RENAMED
|
File without changes
|
|
File without changes
|
{dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/catalog/check_catalog_sources.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_semantic_models.py
RENAMED
|
File without changes
|
{dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_snapshots.py
RENAMED
|
File without changes
|
|
File without changes
|
{dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/manifest/check_unit_tests.py
RENAMED
|
File without changes
|
{dbt_bouncer-1.25.0 → dbt_bouncer-1.27.0}/src/dbt_bouncer/checks/run_results/check_run_results.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|