dbt-bouncer 1.31.2rc2__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.2rc2.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.2rc2.dist-info/RECORD +0 -35
- {dbt_bouncer-1.31.2rc2.dist-info → dbt_bouncer-2.0.0.dist-info}/WHEEL +0 -0
- {dbt_bouncer-1.31.2rc2.dist-info → dbt_bouncer-2.0.0.dist-info}/entry_points.txt +0 -0
- {dbt_bouncer-1.31.2rc2.dist-info → dbt_bouncer-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {dbt_bouncer-1.31.2rc2.dist-info → dbt_bouncer-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
# mypy: disable-error-code="union-attr"
|
|
2
|
-
|
|
3
1
|
import re
|
|
2
|
+
from pathlib import Path
|
|
4
3
|
from typing import TYPE_CHECKING, ClassVar, Literal
|
|
5
4
|
|
|
6
5
|
from pydantic import Field
|
|
@@ -15,6 +14,7 @@ from jinja2 import Environment, nodes
|
|
|
15
14
|
from jinja2_simple_tags import StandaloneTag
|
|
16
15
|
|
|
17
16
|
from dbt_bouncer.check_base import BaseCheck
|
|
17
|
+
from dbt_bouncer.checks.common import DbtBouncerFailedCheckError
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class TagExtension(StandaloneTag):
|
|
@@ -24,14 +24,17 @@ class TagExtension(StandaloneTag):
|
|
|
24
24
|
class CheckMacroArgumentsDescriptionPopulated(BaseCheck):
|
|
25
25
|
"""Macro arguments must have a populated description.
|
|
26
26
|
|
|
27
|
+
Parameters:
|
|
28
|
+
min_description_length (int | None): Minimum length required for the description to be considered populated.
|
|
29
|
+
|
|
27
30
|
Receives:
|
|
28
31
|
macro (Macros): The Macros object to check.
|
|
29
32
|
|
|
30
33
|
Other Parameters:
|
|
31
|
-
description (
|
|
32
|
-
exclude (
|
|
33
|
-
include (
|
|
34
|
-
severity (
|
|
34
|
+
description (str | None): Description of what the check does and why it is implemented.
|
|
35
|
+
exclude (str | None): Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.
|
|
36
|
+
include (str | None): Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.
|
|
37
|
+
severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.
|
|
35
38
|
|
|
36
39
|
Example(s):
|
|
37
40
|
```yaml
|
|
@@ -44,20 +47,33 @@ class CheckMacroArgumentsDescriptionPopulated(BaseCheck):
|
|
|
44
47
|
- name: check_macro_arguments_description_populated
|
|
45
48
|
include: ^macros/common
|
|
46
49
|
```
|
|
50
|
+
```yaml
|
|
51
|
+
manifest_checks:
|
|
52
|
+
- name: check_macro_arguments_description_populated
|
|
53
|
+
min_description_length: 25 # Setting a stricter requirement for description length
|
|
54
|
+
```
|
|
47
55
|
|
|
48
56
|
"""
|
|
49
57
|
|
|
50
|
-
|
|
58
|
+
min_description_length: int | None = Field(default=None)
|
|
59
|
+
macro: "Macros | None" = Field(default=None)
|
|
51
60
|
name: Literal["check_macro_arguments_description_populated"]
|
|
52
61
|
|
|
53
62
|
def execute(self) -> None:
|
|
54
|
-
"""Execute the check.
|
|
63
|
+
"""Execute the check.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
DbtBouncerFailedCheckError: If macro arguments are not populated.
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
if self.macro is None:
|
|
70
|
+
raise DbtBouncerFailedCheckError("self.macro is None")
|
|
55
71
|
environment = Environment(autoescape=True, extensions=[TagExtension])
|
|
56
72
|
ast = environment.parse(self.macro.macro_sql)
|
|
57
73
|
|
|
58
74
|
if hasattr(ast.body[0], "args"):
|
|
59
75
|
# Assume macro is a "true" macro
|
|
60
|
-
macro_arguments = [a.name for a in ast.body[0]
|
|
76
|
+
macro_arguments = [a.name for a in getattr(ast.body[0], "args", [])]
|
|
61
77
|
else:
|
|
62
78
|
if "materialization" in [
|
|
63
79
|
x.value.value
|
|
@@ -83,17 +99,22 @@ class CheckMacroArgumentsDescriptionPopulated(BaseCheck):
|
|
|
83
99
|
# macro.arguments: List of args manually added to the properties file
|
|
84
100
|
|
|
85
101
|
non_complying_args = []
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
if self.macro.arguments:
|
|
103
|
+
for arg in macro_arguments:
|
|
104
|
+
macro_doc_raw = [x for x in self.macro.arguments if x.name == arg]
|
|
105
|
+
if macro_doc_raw == [] or (
|
|
106
|
+
arg not in [x.name for x in self.macro.arguments]
|
|
107
|
+
or not self._is_description_populated(
|
|
108
|
+
str(macro_doc_raw[0].description or ""),
|
|
109
|
+
self.min_description_length,
|
|
110
|
+
)
|
|
111
|
+
):
|
|
112
|
+
non_complying_args.append(arg)
|
|
113
|
+
|
|
114
|
+
if non_complying_args != []:
|
|
115
|
+
raise DbtBouncerFailedCheckError(
|
|
116
|
+
f"Macro `{self.macro.name}` does not have a populated description for the following argument(s): {non_complying_args}."
|
|
117
|
+
)
|
|
97
118
|
|
|
98
119
|
|
|
99
120
|
class CheckMacroCodeDoesNotContainRegexpPattern(BaseCheck):
|
|
@@ -106,48 +127,59 @@ class CheckMacroCodeDoesNotContainRegexpPattern(BaseCheck):
|
|
|
106
127
|
macro (Macros): The Macros object to check.
|
|
107
128
|
|
|
108
129
|
Other Parameters:
|
|
109
|
-
description (
|
|
110
|
-
exclude (
|
|
111
|
-
include (
|
|
112
|
-
severity (
|
|
130
|
+
description (str | None): Description of what the check does and why it is implemented.
|
|
131
|
+
exclude (str | None): Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.
|
|
132
|
+
include (str | None): Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.
|
|
133
|
+
severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.
|
|
113
134
|
|
|
114
135
|
Example(s):
|
|
115
136
|
```yaml
|
|
116
137
|
manifest_checks:
|
|
117
|
-
# Prefer `coalesce` over `ifnull`: https://docs.sqlfluff.com/en/stable/rules.html#sqlfluff.rules.sphinx.Rule_CV02
|
|
138
|
+
# Prefer `coalesce` over `ifnull`: [https://docs.sqlfluff.com/en/stable/rules.html#sqlfluff.rules.sphinx.Rule_CV02](https://docs.sqlfluff.com/en/stable/rules.html#sqlfluff.rules.sphinx.Rule_CV02)
|
|
118
139
|
- name: check_macro_code_does_not_contain_regexp_pattern
|
|
119
140
|
regexp_pattern: .*[i][f][n][u][l][l].*
|
|
120
141
|
```
|
|
121
142
|
|
|
122
143
|
"""
|
|
123
144
|
|
|
124
|
-
macro: "Macros" = Field(default=None)
|
|
145
|
+
macro: "Macros | None" = Field(default=None)
|
|
125
146
|
name: Literal["check_macro_code_does_not_contain_regexp_pattern"]
|
|
126
147
|
regexp_pattern: str
|
|
127
148
|
|
|
128
149
|
def execute(self) -> None:
|
|
129
|
-
"""Execute the check.
|
|
130
|
-
|
|
150
|
+
"""Execute the check.
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
DbtBouncerFailedCheckError: If macro code contains banned string.
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
if self.macro is None:
|
|
157
|
+
raise DbtBouncerFailedCheckError("self.macro is None")
|
|
158
|
+
if (
|
|
131
159
|
re.compile(self.regexp_pattern.strip(), flags=re.DOTALL).match(
|
|
132
160
|
self.macro.macro_sql
|
|
133
161
|
)
|
|
134
|
-
is None
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
|
|
162
|
+
is not None
|
|
163
|
+
):
|
|
164
|
+
raise DbtBouncerFailedCheckError(
|
|
165
|
+
f"Macro `{self.macro.name}` contains a banned string: `{self.regexp_pattern.strip()}`."
|
|
166
|
+
)
|
|
138
167
|
|
|
139
168
|
|
|
140
169
|
class CheckMacroDescriptionPopulated(BaseCheck):
|
|
141
170
|
"""Macros must have a populated description.
|
|
142
171
|
|
|
172
|
+
Parameters:
|
|
173
|
+
min_description_length (int | None): Minimum length required for the description to be considered populated.
|
|
174
|
+
|
|
143
175
|
Receives:
|
|
144
176
|
macro (Macros): The Macros object to check.
|
|
145
177
|
|
|
146
178
|
Other Parameters:
|
|
147
|
-
description (
|
|
148
|
-
exclude (
|
|
149
|
-
include (
|
|
150
|
-
severity (
|
|
179
|
+
description (str | None): Description of what the check does and why it is implemented.
|
|
180
|
+
exclude (str | None): Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.
|
|
181
|
+
include (str | None): Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.
|
|
182
|
+
severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.
|
|
151
183
|
|
|
152
184
|
Example(s):
|
|
153
185
|
```yaml
|
|
@@ -163,14 +195,25 @@ class CheckMacroDescriptionPopulated(BaseCheck):
|
|
|
163
195
|
|
|
164
196
|
"""
|
|
165
197
|
|
|
166
|
-
macro: "Macros" = Field(default=None)
|
|
198
|
+
macro: "Macros | None" = Field(default=None)
|
|
199
|
+
min_description_length: int | None = Field(default=None)
|
|
167
200
|
name: Literal["check_macro_description_populated"]
|
|
168
201
|
|
|
169
202
|
def execute(self) -> None:
|
|
170
|
-
"""Execute the check.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
203
|
+
"""Execute the check.
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
DbtBouncerFailedCheckError: If macro description is not populated.
|
|
207
|
+
|
|
208
|
+
"""
|
|
209
|
+
if self.macro is None:
|
|
210
|
+
raise DbtBouncerFailedCheckError("self.macro is None")
|
|
211
|
+
if not self._is_description_populated(
|
|
212
|
+
str(self.macro.description or ""), self.min_description_length
|
|
213
|
+
):
|
|
214
|
+
raise DbtBouncerFailedCheckError(
|
|
215
|
+
f"Macro `{self.macro.name}` does not have a populated description."
|
|
216
|
+
)
|
|
174
217
|
|
|
175
218
|
|
|
176
219
|
class CheckMacroMaxNumberOfLines(BaseCheck):
|
|
@@ -183,10 +226,10 @@ class CheckMacroMaxNumberOfLines(BaseCheck):
|
|
|
183
226
|
macro (Macros): The Macros object to check.
|
|
184
227
|
|
|
185
228
|
Other Parameters:
|
|
186
|
-
description (
|
|
187
|
-
exclude (
|
|
188
|
-
include (
|
|
189
|
-
severity (
|
|
229
|
+
description (str | None): Description of what the check does and why it is implemented.
|
|
230
|
+
exclude (str | None): Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.
|
|
231
|
+
include (str | None): Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.
|
|
232
|
+
severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.
|
|
190
233
|
|
|
191
234
|
Example(s):
|
|
192
235
|
```yaml
|
|
@@ -201,17 +244,25 @@ class CheckMacroMaxNumberOfLines(BaseCheck):
|
|
|
201
244
|
|
|
202
245
|
"""
|
|
203
246
|
|
|
204
|
-
macro: "Macros" = Field(default=None)
|
|
247
|
+
macro: "Macros | None" = Field(default=None)
|
|
205
248
|
name: Literal["check_macro_max_number_of_lines"]
|
|
206
249
|
max_number_of_lines: int = Field(default=50)
|
|
207
250
|
|
|
208
251
|
def execute(self) -> None:
|
|
209
|
-
"""Execute the check.
|
|
252
|
+
"""Execute the check.
|
|
253
|
+
|
|
254
|
+
Raises:
|
|
255
|
+
DbtBouncerFailedCheckError: If max lines exceeded.
|
|
256
|
+
|
|
257
|
+
"""
|
|
258
|
+
if self.macro is None:
|
|
259
|
+
raise DbtBouncerFailedCheckError("self.macro is None")
|
|
210
260
|
actual_number_of_lines = self.macro.macro_sql.count("\n") + 1
|
|
211
261
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
262
|
+
if actual_number_of_lines > self.max_number_of_lines:
|
|
263
|
+
raise DbtBouncerFailedCheckError(
|
|
264
|
+
f"Macro `{self.macro.name}` has {actual_number_of_lines} lines, this is more than the maximum permitted number of lines ({self.max_number_of_lines})."
|
|
265
|
+
)
|
|
215
266
|
|
|
216
267
|
|
|
217
268
|
class CheckMacroNameMatchesFileName(BaseCheck):
|
|
@@ -223,10 +274,10 @@ class CheckMacroNameMatchesFileName(BaseCheck):
|
|
|
223
274
|
macro (Macros): The Macros object to check.
|
|
224
275
|
|
|
225
276
|
Other Parameters:
|
|
226
|
-
description (
|
|
227
|
-
exclude (
|
|
228
|
-
include (
|
|
229
|
-
severity (
|
|
277
|
+
description (str | None): Description of what the check does and why it is implemented.
|
|
278
|
+
exclude (str | None): Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.
|
|
279
|
+
include (str | None): Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.
|
|
280
|
+
severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.
|
|
230
281
|
|
|
231
282
|
Example(s):
|
|
232
283
|
```yaml
|
|
@@ -236,27 +287,31 @@ class CheckMacroNameMatchesFileName(BaseCheck):
|
|
|
236
287
|
|
|
237
288
|
"""
|
|
238
289
|
|
|
239
|
-
macro: "Macros" = Field(default=None)
|
|
290
|
+
macro: "Macros | None" = Field(default=None)
|
|
240
291
|
name: Literal["check_macro_name_matches_file_name"]
|
|
241
292
|
|
|
242
293
|
def execute(self) -> None:
|
|
243
|
-
"""Execute the check.
|
|
294
|
+
"""Execute the check.
|
|
295
|
+
|
|
296
|
+
Raises:
|
|
297
|
+
DbtBouncerFailedCheckError: If macro name does not match file name.
|
|
298
|
+
|
|
299
|
+
"""
|
|
300
|
+
if self.macro is None:
|
|
301
|
+
raise DbtBouncerFailedCheckError("self.macro is None")
|
|
302
|
+
file_path = Path(clean_path_str(self.macro.original_file_path))
|
|
303
|
+
file_stem = file_path.stem
|
|
304
|
+
|
|
244
305
|
if self.macro.name.startswith("test_"):
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
.split(".")[0]
|
|
250
|
-
), (
|
|
251
|
-
f"Macro `{self.macro.unique_id}` is not in a file named `{self.macro.name[5:]}.sql`."
|
|
252
|
-
)
|
|
306
|
+
if self.macro.name[5:] != file_stem:
|
|
307
|
+
raise DbtBouncerFailedCheckError(
|
|
308
|
+
f"Macro `{self.macro.unique_id}` is not in a file named `{self.macro.name[5:]}.sql`."
|
|
309
|
+
)
|
|
253
310
|
else:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
.split(".")[0]
|
|
259
|
-
), f"Macro `{self.macro.name}` is not in a file of the same name."
|
|
311
|
+
if self.macro.name != file_stem:
|
|
312
|
+
raise DbtBouncerFailedCheckError(
|
|
313
|
+
f"Macro `{self.macro.name}` is not in a file of the same name."
|
|
314
|
+
)
|
|
260
315
|
|
|
261
316
|
|
|
262
317
|
class CheckMacroPropertyFileLocation(BaseCheck):
|
|
@@ -266,10 +321,10 @@ class CheckMacroPropertyFileLocation(BaseCheck):
|
|
|
266
321
|
macro (Macros): The Macros object to check.
|
|
267
322
|
|
|
268
323
|
Other Parameters:
|
|
269
|
-
description (
|
|
270
|
-
exclude (
|
|
271
|
-
include (
|
|
272
|
-
severity (
|
|
324
|
+
description (str | None): Description of what the check does and why it is implemented.
|
|
325
|
+
exclude (str | None): Regex pattern to match the macro path. Macro paths that match the pattern will not be checked.
|
|
326
|
+
include (str | None): Regex pattern to match the macro path. Only macro paths that match the pattern will be checked.
|
|
327
|
+
severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.
|
|
273
328
|
|
|
274
329
|
Example(s):
|
|
275
330
|
```yaml
|
|
@@ -279,39 +334,57 @@ class CheckMacroPropertyFileLocation(BaseCheck):
|
|
|
279
334
|
|
|
280
335
|
"""
|
|
281
336
|
|
|
282
|
-
macro: "Macros" = Field(default=None)
|
|
337
|
+
macro: "Macros | None" = Field(default=None)
|
|
283
338
|
name: Literal["check_macro_property_file_location"]
|
|
284
339
|
|
|
285
340
|
def execute(self) -> None:
|
|
286
|
-
"""Execute the check.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
341
|
+
"""Execute the check.
|
|
342
|
+
|
|
343
|
+
Raises:
|
|
344
|
+
DbtBouncerFailedCheckError: If property file location is incorrect.
|
|
345
|
+
|
|
346
|
+
"""
|
|
347
|
+
if self.macro is None:
|
|
348
|
+
raise DbtBouncerFailedCheckError("self.macro is None")
|
|
349
|
+
original_path = Path(clean_path_str(self.macro.original_file_path))
|
|
350
|
+
|
|
351
|
+
# Logic matches previous manual splitting:
|
|
352
|
+
# If path is `macros/utils/file.sql`, we want `_utils`.
|
|
353
|
+
# We assume the first part of the path is the root (e.g. 'macros' or 'tests').
|
|
354
|
+
subdir_parts = original_path.parent.parts[1:]
|
|
355
|
+
expected_substr = "_" + "_".join(subdir_parts) if subdir_parts else ""
|
|
356
|
+
|
|
357
|
+
if self.macro.patch_path is None:
|
|
358
|
+
raise DbtBouncerFailedCheckError(
|
|
359
|
+
f"Macro `{self.macro.name}` is not defined in a `.yml` properties file."
|
|
360
|
+
)
|
|
361
|
+
clean_patch_path = clean_path_str(self.macro.patch_path)
|
|
362
|
+
if clean_patch_path is None:
|
|
363
|
+
raise DbtBouncerFailedCheckError(
|
|
364
|
+
f"Macro `{self.macro.name}` has an invalid patch path."
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
patch_path = Path(clean_patch_path)
|
|
368
|
+
properties_yml_name = patch_path.name
|
|
369
|
+
|
|
370
|
+
if original_path.parts[0] == "tests":
|
|
371
|
+
# Do not check generic tests (which are also macros)
|
|
299
372
|
pass
|
|
300
373
|
elif expected_substr == "": # i.e. macro in ./macros
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
374
|
+
if properties_yml_name != "_macros.yml":
|
|
375
|
+
raise DbtBouncerFailedCheckError(
|
|
376
|
+
f"The properties file for `{self.macro.name}` (`{properties_yml_name}`) should be `_macros.yml`."
|
|
377
|
+
)
|
|
304
378
|
else:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
)
|
|
379
|
+
if not properties_yml_name.startswith("_"):
|
|
380
|
+
raise DbtBouncerFailedCheckError(
|
|
381
|
+
f"The properties file for `{self.macro.name}` (`{properties_yml_name}`) does not start with an underscore."
|
|
382
|
+
)
|
|
383
|
+
if expected_substr not in properties_yml_name:
|
|
384
|
+
raise DbtBouncerFailedCheckError(
|
|
385
|
+
f"The properties file for `{self.macro.name}` (`{properties_yml_name}`) does not contain the expected substring (`{expected_substr}`)."
|
|
386
|
+
)
|
|
387
|
+
if not properties_yml_name.endswith("__macros.yml"):
|
|
388
|
+
raise DbtBouncerFailedCheckError(
|
|
389
|
+
f"The properties file for `{self.macro.name}` (`{properties_yml_name}`) does not end with `__macros.yml`."
|
|
390
|
+
)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
# mypy: disable-error-code="union-attr"
|
|
2
|
-
|
|
3
1
|
import re
|
|
4
|
-
from typing import TYPE_CHECKING, Literal
|
|
2
|
+
from typing import TYPE_CHECKING, Literal
|
|
5
3
|
|
|
6
4
|
from pydantic import BaseModel, ConfigDict, Field
|
|
7
5
|
|
|
8
6
|
if TYPE_CHECKING:
|
|
9
|
-
from dbt_bouncer.artifact_parsers.
|
|
7
|
+
from dbt_bouncer.artifact_parsers.parsers_manifest import DbtBouncerManifest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from dbt_bouncer.checks.common import DbtBouncerFailedCheckError
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class CheckProjectName(BaseModel):
|
|
@@ -19,8 +20,8 @@ class CheckProjectName(BaseModel):
|
|
|
19
20
|
manifest_obj (DbtBouncerManifest): The manifest object.
|
|
20
21
|
|
|
21
22
|
Other Parameters:
|
|
22
|
-
description (
|
|
23
|
-
severity (
|
|
23
|
+
description (str | None): Description of what the check does and why it is implemented.
|
|
24
|
+
severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.
|
|
24
25
|
|
|
25
26
|
Example(s):
|
|
26
27
|
```yaml
|
|
@@ -33,33 +34,42 @@ class CheckProjectName(BaseModel):
|
|
|
33
34
|
|
|
34
35
|
model_config = ConfigDict(extra="forbid")
|
|
35
36
|
|
|
36
|
-
description:
|
|
37
|
+
description: str | None = Field(
|
|
37
38
|
default=None,
|
|
38
39
|
description="Description of what the check does and why it is implemented.",
|
|
39
40
|
)
|
|
40
|
-
index:
|
|
41
|
+
index: int | None = Field(
|
|
41
42
|
default=None,
|
|
42
43
|
description="Index to uniquely identify the check, calculated at runtime.",
|
|
43
44
|
)
|
|
44
|
-
manifest_obj: "DbtBouncerManifest" = Field(default=None)
|
|
45
|
+
manifest_obj: "DbtBouncerManifest | None" = Field(default=None)
|
|
45
46
|
name: Literal["check_project_name"]
|
|
46
|
-
package_name:
|
|
47
|
+
package_name: str | None = Field(default=None)
|
|
47
48
|
project_name_pattern: str
|
|
48
|
-
severity:
|
|
49
|
+
severity: Literal["error", "warn"] | None = Field(
|
|
49
50
|
default="error",
|
|
50
51
|
description="Severity of the check, one of 'error' or 'warn'.",
|
|
51
52
|
)
|
|
52
53
|
|
|
53
54
|
def execute(self) -> None:
|
|
54
|
-
"""Execute the check.
|
|
55
|
+
"""Execute the check.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
DbtBouncerFailedCheckError: If project name does not match regex.
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
if self.manifest_obj is None:
|
|
62
|
+
raise DbtBouncerFailedCheckError("self.manifest_obj is None")
|
|
63
|
+
|
|
55
64
|
package_name = (
|
|
56
65
|
self.package_name or self.manifest_obj.manifest.metadata.project_name
|
|
57
66
|
)
|
|
58
|
-
|
|
67
|
+
if (
|
|
59
68
|
re.compile(self.project_name_pattern.strip()).match(
|
|
60
|
-
package_name,
|
|
69
|
+
str(package_name),
|
|
70
|
+
)
|
|
71
|
+
is None
|
|
72
|
+
):
|
|
73
|
+
raise DbtBouncerFailedCheckError(
|
|
74
|
+
f"Project name (`{package_name}`) does not conform to the supplied regex `({self.project_name_pattern.strip()})`."
|
|
61
75
|
)
|
|
62
|
-
is not None
|
|
63
|
-
), (
|
|
64
|
-
f"Project name (`{package_name}`) does not conform to the supplied regex `({self.project_name_pattern.strip()})`."
|
|
65
|
-
)
|