dbt-bouncer 1.31.2rc3__py3-none-any.whl → 2.0.0rc1__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.
Files changed (36) hide show
  1. dbt_bouncer/artifact_parsers/dbt_cloud/catalog_latest.py +21 -21
  2. dbt_bouncer/artifact_parsers/dbt_cloud/manifest_latest.py +1745 -1745
  3. dbt_bouncer/artifact_parsers/dbt_cloud/run_results_latest.py +22 -22
  4. dbt_bouncer/artifact_parsers/parsers_catalog.py +26 -24
  5. dbt_bouncer/artifact_parsers/parsers_common.py +57 -36
  6. dbt_bouncer/artifact_parsers/parsers_manifest.py +98 -69
  7. dbt_bouncer/artifact_parsers/parsers_run_results.py +32 -19
  8. dbt_bouncer/check_base.py +22 -11
  9. dbt_bouncer/checks/catalog/check_catalog_sources.py +22 -12
  10. dbt_bouncer/checks/catalog/check_columns.py +175 -105
  11. dbt_bouncer/checks/common.py +24 -3
  12. dbt_bouncer/checks/manifest/check_exposures.py +79 -52
  13. dbt_bouncer/checks/manifest/check_lineage.py +69 -40
  14. dbt_bouncer/checks/manifest/check_macros.py +177 -104
  15. dbt_bouncer/checks/manifest/check_metadata.py +28 -18
  16. dbt_bouncer/checks/manifest/check_models.py +842 -496
  17. dbt_bouncer/checks/manifest/check_seeds.py +63 -0
  18. dbt_bouncer/checks/manifest/check_semantic_models.py +28 -20
  19. dbt_bouncer/checks/manifest/check_snapshots.py +57 -33
  20. dbt_bouncer/checks/manifest/check_sources.py +246 -137
  21. dbt_bouncer/checks/manifest/check_unit_tests.py +97 -54
  22. dbt_bouncer/checks/run_results/check_run_results.py +34 -20
  23. dbt_bouncer/config_file_parser.py +47 -28
  24. dbt_bouncer/config_file_validator.py +11 -8
  25. dbt_bouncer/global_context.py +31 -0
  26. dbt_bouncer/main.py +128 -67
  27. dbt_bouncer/runner.py +61 -31
  28. dbt_bouncer/utils.py +146 -50
  29. dbt_bouncer/version.py +1 -1
  30. {dbt_bouncer-1.31.2rc3.dist-info → dbt_bouncer-2.0.0rc1.dist-info}/METADATA +15 -15
  31. dbt_bouncer-2.0.0rc1.dist-info/RECORD +37 -0
  32. dbt_bouncer-1.31.2rc3.dist-info/RECORD +0 -35
  33. {dbt_bouncer-1.31.2rc3.dist-info → dbt_bouncer-2.0.0rc1.dist-info}/WHEEL +0 -0
  34. {dbt_bouncer-1.31.2rc3.dist-info → dbt_bouncer-2.0.0rc1.dist-info}/entry_points.txt +0 -0
  35. {dbt_bouncer-1.31.2rc3.dist-info → dbt_bouncer-2.0.0rc1.dist-info}/licenses/LICENSE +0 -0
  36. {dbt_bouncer-1.31.2rc3.dist-info → dbt_bouncer-2.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  import warnings
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
3
+ from typing import TYPE_CHECKING, Any, TypeAlias
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
27
27
 
28
28
  from dbt_bouncer.artifact_parsers.parsers_common import load_dbt_artifact
29
29
 
30
- DbtBouncerRunResultBase = Union[RunResultOutput_v5, RunResultOutput_Latest]
30
+ DbtBouncerRunResultBase: TypeAlias = RunResultOutput_v5 | RunResultOutput_Latest
31
31
 
32
32
 
33
33
  class DbtBouncerRunResult(BaseModel):
@@ -39,8 +39,8 @@ class DbtBouncerRunResult(BaseModel):
39
39
 
40
40
 
41
41
  def parse_run_results(
42
- run_results: Dict[str, Any],
43
- ) -> Union[RunResultsV5, RunResultsLatest]:
42
+ run_results: dict[str, Any],
43
+ ) -> RunResultsV5 | RunResultsLatest:
44
44
  """Parse run-results.json.
45
45
 
46
46
  Args:
@@ -50,7 +50,7 @@ def parse_run_results(
50
50
  ValueError: If the schema version is not supported.
51
51
 
52
52
  Returns:
53
- Union[ RunResultsV5, RunResultsLatest]:
53
+ RunResultsV5 | RunResultsLatest:
54
54
 
55
55
  """
56
56
  dbt_schema_version = run_results["metadata"]["dbt_schema_version"]
@@ -64,12 +64,21 @@ def parse_run_results(
64
64
  def parse_run_results_artifact(
65
65
  artifact_dir: "Path",
66
66
  manifest_obj: "DbtBouncerManifest",
67
- package_name: Optional[str] = None,
68
- ) -> List[DbtBouncerRunResult]:
67
+ package_name: str | None = None,
68
+ ) -> list[DbtBouncerRunResult]:
69
69
  """Parse the run_results.json artifact.
70
70
 
71
+ Args:
72
+ artifact_dir (Path): Path to the dbt artifacts directory.
73
+ manifest_obj (DbtBouncerManifest): The manifest object.
74
+ package_name (str | None): The package name to filter results by. If None,
75
+ uses the project name from the manifest.
76
+
71
77
  Returns:
72
- List[DbtBouncerRunResult]: A list of DbtBouncerRunResult objects.
78
+ list[DbtBouncerRunResult]: A list of DbtBouncerRunResult objects.
79
+
80
+ Raises:
81
+ TypeError: If the loaded artifact is not of the expected type.
73
82
 
74
83
  """
75
84
 
@@ -93,24 +102,28 @@ def parse_run_results_artifact(
93
102
  manifest_obj.manifest.exposures[unique_id].original_file_path
94
103
  )
95
104
  else:
96
- return clean_path_str(
97
- manifest_obj.manifest.unit_tests[unique_id].original_file_path
98
- )
105
+ unit_tests = getattr(manifest_obj.manifest, "unit_tests", {})
106
+ if unique_id in unit_tests:
107
+ return clean_path_str(unit_tests[unique_id].original_file_path)
108
+ return ""
99
109
 
100
- run_results_obj: Union[RunResultsV5, RunResultsLatest] = load_dbt_artifact(
110
+ run_results_obj = load_dbt_artifact(
101
111
  artifact_name="run_results.json",
102
112
  dbt_artifacts_dir=artifact_dir,
103
113
  )
104
114
 
115
+ if not isinstance(run_results_obj, (RunResultsV5, RunResultsLatest)):
116
+ raise TypeError(
117
+ f"Expected RunResultsV5 or RunResultsLatest, got {type(run_results_obj)}"
118
+ )
119
+
105
120
  project_run_results = [
106
121
  DbtBouncerRunResult(
107
- **{
108
- "original_file_path": (
109
- get_clean_path_str(unique_id=r.unique_id, manifest_obj=manifest_obj)
110
- ),
111
- "run_result": r,
112
- "unique_id": r.unique_id,
113
- },
122
+ original_file_path=get_clean_path_str(
123
+ unique_id=r.unique_id, manifest_obj=manifest_obj
124
+ ),
125
+ run_result=r,
126
+ unique_id=r.unique_id,
114
127
  )
115
128
  for r in run_results_obj.results
116
129
  if r.unique_id.split(".")[1]
dbt_bouncer/check_base.py CHANGED
@@ -1,7 +1,9 @@
1
- from typing import TYPE_CHECKING, List, Literal, Optional
1
+ from typing import TYPE_CHECKING, ClassVar, Literal
2
2
 
3
3
  from pydantic import BaseModel, ConfigDict, Field
4
4
 
5
+ from dbt_bouncer.utils import is_description_populated
6
+
5
7
  if TYPE_CHECKING:
6
8
  import warnings
7
9
 
@@ -20,42 +22,44 @@ class BaseCheck(BaseModel):
20
22
 
21
23
  model_config = ConfigDict(extra="forbid")
22
24
 
23
- description: Optional[str] = Field(
25
+ description: str | None = Field(
24
26
  default=None,
25
27
  description="Description of what the check does and why it is implemented.",
26
28
  )
27
- exclude: Optional[str] = Field(
29
+ exclude: str | None = Field(
28
30
  default=None,
29
31
  description="Regexp to match which paths to exclude.",
30
32
  )
31
- include: Optional[str] = Field(
33
+ include: str | None = Field(
32
34
  default=None,
33
35
  description="Regexp to match which paths to include.",
34
36
  )
35
- index: Optional[int] = Field(
37
+ index: int | None = Field(
36
38
  default=None,
37
39
  description="Index to uniquely identify the check, calculated at runtime.",
38
40
  )
39
- materialization: Optional[Literal["ephemeral", "incremental", "table", "view"]] = (
41
+ materialization: Literal["ephemeral", "incremental", "table", "view"] | None = (
40
42
  Field(
41
43
  default=None,
42
44
  description="Limit check to models with the specified materialization.",
43
45
  )
44
46
  )
45
- severity: Optional[Literal["error", "warn"]] = Field(
47
+ severity: Literal["error", "warn"] | None = Field(
46
48
  default="error",
47
49
  description="Severity of the check, one of 'error' or 'warn'.",
48
50
  )
49
51
 
52
+ _min_description_length: ClassVar[int] = 4
53
+
50
54
  # Helper methods
51
55
  def is_catalog_node_a_model(
52
- self, catalog_node: "CatalogNodes", models: List["DbtBouncerModelBase"]
56
+ self, catalog_node: "CatalogNodes", models: list["DbtBouncerModelBase"]
53
57
  ) -> bool:
54
58
  """Check if a catalog node is a model.
55
59
 
56
60
  Args:
57
61
  catalog_node (CatalogNodes): The CatalogNodes object to check.
58
- models (List[DbtBouncerModelBase]): List of DbtBouncerModelBase objects parsed from `manifest.json`.
62
+ models (list[DbtBouncerModelBase]): List of DbtBouncerModelBase objects parsed from `manifest.json`.
59
63
 
60
64
  Returns:
61
65
  bool: Whether a catalog node is a model.
@@ -69,14 +73,21 @@ class BaseCheck(BaseModel):
69
73
  else:
70
74
  return False
71
75
 
72
- def is_description_populated(self, description: str) -> bool:
76
+ def _is_description_populated(
77
+ self, description: str, min_description_length: int | None
78
+ ) -> bool:
73
79
  """Check if a description is populated.
74
80
 
75
81
  Args:
76
82
  description (str): Description.
83
+ min_description_length (int): Minimum length of the description.
77
84
 
78
85
  Returns:
79
86
  bool: Whether a description is validly populated.
80
87
 
81
88
  """
82
- return len(description.strip()) > 4
89
+ return is_description_populated(
90
+ description=description,
91
+ min_description_length=min_description_length
92
+ or self._min_description_length,
93
+ )
@@ -1,8 +1,9 @@
1
- from typing import TYPE_CHECKING, List, Literal
1
+ from typing import TYPE_CHECKING, Literal
2
2
 
3
3
  from pydantic import Field
4
4
 
5
5
  from dbt_bouncer.check_base import BaseCheck
6
+ from dbt_bouncer.checks.common import DbtBouncerFailedCheckError
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  import warnings
@@ -23,13 +24,13 @@ class CheckSourceColumnsAreAllDocumented(BaseCheck):
23
24
 
24
25
  Receives:
25
26
  catalog_source (CatalogNodes): The CatalogNodes object to check.
26
- sources (List[DbtBouncerSourceBase]): List of DbtBouncerSourceBase objects parsed from `catalog.json`.
27
+ sources (list[DbtBouncerSourceBase]): List of DbtBouncerSourceBase objects parsed from `catalog.json`.
27
28
 
28
29
  Other Parameters:
29
- description (Optional[str]): Description of what the check does and why it is implemented.
30
- exclude (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.
31
- include (Optional[str]): Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.
32
- severity (Optional[Literal["error", "warn"]]): Severity level of the check. Default: `error`.
30
+ description (str | None): Description of what the check does and why it is implemented.
31
+ exclude (str | None): Regex pattern to match the source path (i.e the .yml file where the source is configured). Source paths that match the pattern will not be checked.
32
+ include (str | None): Regex pattern to match the source path (i.e the .yml file where the source is configured). Only source paths that match the pattern will be checked.
33
+ severity (Literal["error", "warn"] | None): Severity level of the check. Default: `error`.
33
34
 
34
35
  Example(s):
35
36
  ```yaml
@@ -39,12 +40,20 @@ class CheckSourceColumnsAreAllDocumented(BaseCheck):
39
40
 
40
41
  """
41
42
 
42
- catalog_source: "CatalogNodes" = Field(default=None)
43
+ catalog_source: "CatalogNodes | None" = Field(default=None)
43
44
  name: Literal["check_source_columns_are_all_documented"]
44
- sources: List["DbtBouncerSourceBase"] = Field(default=[])
45
+ sources: list["DbtBouncerSourceBase"] = Field(default=[])
45
46
 
46
47
  def execute(self) -> None:
47
- """Execute the check."""
48
+ """Execute the check.
49
+
50
+ Raises:
51
+ DbtBouncerFailedCheckError: If columns are undocumented.
52
+
53
+ """
54
+ if self.catalog_source is None:
55
+ raise DbtBouncerFailedCheckError("self.catalog_source is None")
56
+
48
57
  source = next(
49
58
  s for s in self.sources if s.unique_id == self.catalog_source.unique_id
50
59
  )
@@ -53,6 +62,7 @@ class CheckSourceColumnsAreAllDocumented(BaseCheck):
53
62
  for _, v in self.catalog_source.columns.items()
54
63
  if v.name not in source.columns
55
64
  ]
56
- assert not undocumented_columns, (
57
- f"`{self.catalog_source.unique_id}` has columns that are not included in the sources properties file: {undocumented_columns}"
58
- )
65
+ if undocumented_columns:
66
+ raise DbtBouncerFailedCheckError(
67
+ f"`{self.catalog_source.unique_id}` has columns that are not included in the sources properties file: {undocumented_columns}"
68
+ )