altimate-datapilot-cli 0.0.8__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 (139) hide show
  1. altimate_datapilot_cli-0.0.8.dist-info/AUTHORS.rst +5 -0
  2. altimate_datapilot_cli-0.0.8.dist-info/LICENSE +9 -0
  3. altimate_datapilot_cli-0.0.8.dist-info/METADATA +102 -0
  4. altimate_datapilot_cli-0.0.8.dist-info/RECORD +139 -0
  5. altimate_datapilot_cli-0.0.8.dist-info/WHEEL +5 -0
  6. altimate_datapilot_cli-0.0.8.dist-info/entry_points.txt +4 -0
  7. altimate_datapilot_cli-0.0.8.dist-info/top_level.txt +1 -0
  8. datapilot/__init__.py +1 -0
  9. datapilot/__main__.py +14 -0
  10. datapilot/cli/__init__.py +0 -0
  11. datapilot/cli/main.py +11 -0
  12. datapilot/clients/__init__.py +0 -0
  13. datapilot/clients/altimate/__init__.py +0 -0
  14. datapilot/clients/altimate/client.py +85 -0
  15. datapilot/clients/altimate/utils.py +75 -0
  16. datapilot/config/__init__.py +0 -0
  17. datapilot/config/config.py +16 -0
  18. datapilot/config/utils.py +32 -0
  19. datapilot/core/__init__.py +0 -0
  20. datapilot/core/insights/__init__.py +2 -0
  21. datapilot/core/insights/base/__init__.py +0 -0
  22. datapilot/core/insights/base/insight.py +34 -0
  23. datapilot/core/insights/report.py +16 -0
  24. datapilot/core/insights/schema.py +24 -0
  25. datapilot/core/insights/sql/__init__.py +0 -0
  26. datapilot/core/insights/sql/base/__init__.py +0 -0
  27. datapilot/core/insights/sql/base/insight.py +18 -0
  28. datapilot/core/insights/sql/runtime/__init__.py +0 -0
  29. datapilot/core/insights/sql/static/__init__.py +0 -0
  30. datapilot/core/insights/utils.py +20 -0
  31. datapilot/core/platforms/__init__.py +0 -0
  32. datapilot/core/platforms/dbt/__init__.py +0 -0
  33. datapilot/core/platforms/dbt/cli/__init__.py +0 -0
  34. datapilot/core/platforms/dbt/cli/cli.py +112 -0
  35. datapilot/core/platforms/dbt/constants.py +34 -0
  36. datapilot/core/platforms/dbt/exceptions.py +6 -0
  37. datapilot/core/platforms/dbt/executor.py +157 -0
  38. datapilot/core/platforms/dbt/factory.py +22 -0
  39. datapilot/core/platforms/dbt/formatting.py +45 -0
  40. datapilot/core/platforms/dbt/hooks/__init__.py +0 -0
  41. datapilot/core/platforms/dbt/hooks/executor_hook.py +86 -0
  42. datapilot/core/platforms/dbt/insights/__init__.py +115 -0
  43. datapilot/core/platforms/dbt/insights/base.py +133 -0
  44. datapilot/core/platforms/dbt/insights/checks/__init__.py +0 -0
  45. datapilot/core/platforms/dbt/insights/checks/base.py +26 -0
  46. datapilot/core/platforms/dbt/insights/checks/check_column_desc_are_same.py +105 -0
  47. datapilot/core/platforms/dbt/insights/checks/check_column_name_contract.py +154 -0
  48. datapilot/core/platforms/dbt/insights/checks/check_macro_args_have_desc.py +75 -0
  49. datapilot/core/platforms/dbt/insights/checks/check_macro_has_desc.py +63 -0
  50. datapilot/core/platforms/dbt/insights/checks/check_model_has_all_columns.py +96 -0
  51. datapilot/core/platforms/dbt/insights/checks/check_model_has_labels_keys.py +112 -0
  52. datapilot/core/platforms/dbt/insights/checks/check_model_has_meta_keys.py +108 -0
  53. datapilot/core/platforms/dbt/insights/checks/check_model_has_properties_file.py +64 -0
  54. datapilot/core/platforms/dbt/insights/checks/check_model_has_tests_by_group.py +118 -0
  55. datapilot/core/platforms/dbt/insights/checks/check_model_has_tests_by_name.py +114 -0
  56. datapilot/core/platforms/dbt/insights/checks/check_model_has_tests_by_type.py +119 -0
  57. datapilot/core/platforms/dbt/insights/checks/check_model_materialization_by_childs.py +129 -0
  58. datapilot/core/platforms/dbt/insights/checks/check_model_name_contract.py +132 -0
  59. datapilot/core/platforms/dbt/insights/checks/check_model_parents_and_childs.py +135 -0
  60. datapilot/core/platforms/dbt/insights/checks/check_model_parents_database.py +109 -0
  61. datapilot/core/platforms/dbt/insights/checks/check_model_parents_schema.py +109 -0
  62. datapilot/core/platforms/dbt/insights/checks/check_model_tags.py +87 -0
  63. datapilot/core/platforms/dbt/insights/checks/check_source_childs.py +97 -0
  64. datapilot/core/platforms/dbt/insights/checks/check_source_columns_have_desc.py +96 -0
  65. datapilot/core/platforms/dbt/insights/checks/check_source_has_all_columns.py +103 -0
  66. datapilot/core/platforms/dbt/insights/checks/check_source_has_freshness.py +94 -0
  67. datapilot/core/platforms/dbt/insights/checks/check_source_has_labels_keys.py +110 -0
  68. datapilot/core/platforms/dbt/insights/checks/check_source_has_loader.py +62 -0
  69. datapilot/core/platforms/dbt/insights/checks/check_source_has_meta_keys.py +117 -0
  70. datapilot/core/platforms/dbt/insights/checks/check_source_has_tests.py +82 -0
  71. datapilot/core/platforms/dbt/insights/checks/check_source_has_tests_by_group.py +117 -0
  72. datapilot/core/platforms/dbt/insights/checks/check_source_has_tests_by_name.py +113 -0
  73. datapilot/core/platforms/dbt/insights/checks/check_source_has_tests_by_type.py +119 -0
  74. datapilot/core/platforms/dbt/insights/checks/check_source_table_has_description.py +62 -0
  75. datapilot/core/platforms/dbt/insights/checks/check_source_tags.py +76 -0
  76. datapilot/core/platforms/dbt/insights/dbt_test/__init__.py +0 -0
  77. datapilot/core/platforms/dbt/insights/dbt_test/base.py +23 -0
  78. datapilot/core/platforms/dbt/insights/dbt_test/missing_primary_key_tests.py +130 -0
  79. datapilot/core/platforms/dbt/insights/dbt_test/test_coverage.py +118 -0
  80. datapilot/core/platforms/dbt/insights/governance/__init__.py +0 -0
  81. datapilot/core/platforms/dbt/insights/governance/base.py +23 -0
  82. datapilot/core/platforms/dbt/insights/governance/documentation_on_stale_columns.py +130 -0
  83. datapilot/core/platforms/dbt/insights/governance/exposures_dependent_on_private_models.py +90 -0
  84. datapilot/core/platforms/dbt/insights/governance/public_models_without_contracts.py +89 -0
  85. datapilot/core/platforms/dbt/insights/governance/undocumented_columns.py +148 -0
  86. datapilot/core/platforms/dbt/insights/governance/undocumented_public_models.py +110 -0
  87. datapilot/core/platforms/dbt/insights/modelling/README.md +15 -0
  88. datapilot/core/platforms/dbt/insights/modelling/__init__.py +0 -0
  89. datapilot/core/platforms/dbt/insights/modelling/base.py +31 -0
  90. datapilot/core/platforms/dbt/insights/modelling/direct_join_to_source.py +125 -0
  91. datapilot/core/platforms/dbt/insights/modelling/downstream_models_dependent_on_source.py +113 -0
  92. datapilot/core/platforms/dbt/insights/modelling/duplicate_sources.py +85 -0
  93. datapilot/core/platforms/dbt/insights/modelling/hard_coded_references.py +80 -0
  94. datapilot/core/platforms/dbt/insights/modelling/joining_of_upstream_concepts.py +79 -0
  95. datapilot/core/platforms/dbt/insights/modelling/model_fanout.py +126 -0
  96. datapilot/core/platforms/dbt/insights/modelling/multiple_sources_joined.py +83 -0
  97. datapilot/core/platforms/dbt/insights/modelling/root_model.py +82 -0
  98. datapilot/core/platforms/dbt/insights/modelling/source_fanout.py +102 -0
  99. datapilot/core/platforms/dbt/insights/modelling/staging_model_dependent_on_downstream_models.py +103 -0
  100. datapilot/core/platforms/dbt/insights/modelling/staging_model_dependent_on_staging_models.py +89 -0
  101. datapilot/core/platforms/dbt/insights/modelling/unused_sources.py +59 -0
  102. datapilot/core/platforms/dbt/insights/performance/__init__.py +0 -0
  103. datapilot/core/platforms/dbt/insights/performance/base.py +26 -0
  104. datapilot/core/platforms/dbt/insights/performance/chain_view_linking.py +92 -0
  105. datapilot/core/platforms/dbt/insights/performance/exposure_parent_materializations.py +104 -0
  106. datapilot/core/platforms/dbt/insights/schema.py +72 -0
  107. datapilot/core/platforms/dbt/insights/structure/__init__.py +0 -0
  108. datapilot/core/platforms/dbt/insights/structure/base.py +33 -0
  109. datapilot/core/platforms/dbt/insights/structure/model_directories_structure.py +92 -0
  110. datapilot/core/platforms/dbt/insights/structure/model_naming_conventions.py +97 -0
  111. datapilot/core/platforms/dbt/insights/structure/source_directories_structure.py +80 -0
  112. datapilot/core/platforms/dbt/insights/structure/test_directory_structure.py +74 -0
  113. datapilot/core/platforms/dbt/insights/utils.py +9 -0
  114. datapilot/core/platforms/dbt/schemas/__init__.py +0 -0
  115. datapilot/core/platforms/dbt/schemas/catalog.py +73 -0
  116. datapilot/core/platforms/dbt/schemas/manifest.py +462 -0
  117. datapilot/core/platforms/dbt/utils.py +525 -0
  118. datapilot/core/platforms/dbt/wrappers/__init__.py +0 -0
  119. datapilot/core/platforms/dbt/wrappers/catalog/__init__.py +0 -0
  120. datapilot/core/platforms/dbt/wrappers/catalog/v1/__init__.py +0 -0
  121. datapilot/core/platforms/dbt/wrappers/catalog/v1/wrapper.py +18 -0
  122. datapilot/core/platforms/dbt/wrappers/catalog/wrapper.py +9 -0
  123. datapilot/core/platforms/dbt/wrappers/manifest/__init__.py +0 -0
  124. datapilot/core/platforms/dbt/wrappers/manifest/v11/__init__.py +0 -0
  125. datapilot/core/platforms/dbt/wrappers/manifest/v11/schemas.py +47 -0
  126. datapilot/core/platforms/dbt/wrappers/manifest/v11/wrapper.py +396 -0
  127. datapilot/core/platforms/dbt/wrappers/manifest/wrapper.py +35 -0
  128. datapilot/core/platforms/dbt/wrappers/run_results/__init__.py +0 -0
  129. datapilot/core/platforms/dbt/wrappers/run_results/run_results.py +39 -0
  130. datapilot/exceptions/__init__.py +0 -0
  131. datapilot/exceptions/exceptions.py +10 -0
  132. datapilot/schemas/__init__.py +0 -0
  133. datapilot/schemas/constants.py +5 -0
  134. datapilot/schemas/nodes.py +19 -0
  135. datapilot/schemas/sql.py +10 -0
  136. datapilot/utils/__init__.py +0 -0
  137. datapilot/utils/formatting/__init__.py +0 -0
  138. datapilot/utils/formatting/utils.py +59 -0
  139. datapilot/utils/utils.py +317 -0
@@ -0,0 +1,92 @@
1
+ from typing import List
2
+ from typing import Optional
3
+
4
+ from datapilot.config.utils import get_regex_configuration
5
+ from datapilot.core.insights.utils import get_severity
6
+ from datapilot.core.platforms.dbt.constants import OTHER
7
+ from datapilot.core.platforms.dbt.insights.schema import DBTInsightResult
8
+ from datapilot.core.platforms.dbt.insights.schema import DBTModelInsightResponse
9
+ from datapilot.core.platforms.dbt.insights.structure.base import DBTStructureInsight
10
+ from datapilot.core.platforms.dbt.schemas.manifest import AltimateResourceType
11
+ from datapilot.core.platforms.dbt.utils import _check_model_folder_convention
12
+ from datapilot.core.platforms.dbt.utils import classify_model_type
13
+
14
+
15
+ class DBTModelDirectoryStructure(DBTStructureInsight):
16
+ """
17
+ DBTModelDirectoryStructure checks if models are placed in the correct directories.
18
+ """
19
+
20
+ NAME = "Bad model directory structure"
21
+ ALIAS = "model_directory_structure"
22
+ DESCRIPTION = "This rule identifies models that are not placed in their correct directories. "
23
+ REASON_TO_FLAG = (
24
+ "Placing models in the correct directories is vital for maintaining a structured and "
25
+ "efficient data warehouse. Incorrectly placed models can lead to confusion, hinder "
26
+ "discoverability, and complicate maintenance and scaling of the dbt project."
27
+ )
28
+ FAILURE_MESSAGE = (
29
+ "Incorrect Directory Placement Detected: The model `{model_unique_id}` is incorrectly "
30
+ "placed in the current directory. As a `{model_type}` model, it should be located in "
31
+ "the `{convention}` directory."
32
+ )
33
+ RECOMMENDATION = (
34
+ "To resolve this issue, please move the model `{model_unique_id}` to the `{convention}` "
35
+ "directory. This change will align the model's location with the established directory "
36
+ "structure, improving organization and ease of access in your dbt project."
37
+ )
38
+
39
+ def _build_failure_result(self, model_unique_id: str, model_type: str, convention: Optional[str]) -> DBTInsightResult:
40
+ failure_message = self.FAILURE_MESSAGE.format(
41
+ model_unique_id=model_unique_id,
42
+ model_type=model_type,
43
+ convention=convention,
44
+ )
45
+ return DBTInsightResult(
46
+ name=self.NAME,
47
+ type=self.TYPE,
48
+ message=failure_message,
49
+ recommendation=self.RECOMMENDATION.format(model_unique_id=model_unique_id, convention=convention),
50
+ reason_to_flag=self.REASON_TO_FLAG,
51
+ metadata={
52
+ "model": model_unique_id,
53
+ "model_type": model_type,
54
+ "convention": convention,
55
+ },
56
+ )
57
+
58
+ def generate(self, *args, **kwargs) -> List[DBTModelInsightResponse]:
59
+ insights = []
60
+ regex_configuration = get_regex_configuration(self.config)
61
+ for node in self.nodes.values():
62
+ if self.should_skip_model(node.unique_id):
63
+ self.logger.debug(f"Skipping model {node.unique_id} as it is not enabled for selected models")
64
+ continue
65
+ if node.resource_type == AltimateResourceType.model:
66
+ model_type = classify_model_type(node.name, node.original_file_path, regex_configuration)
67
+ if model_type == OTHER:
68
+ continue
69
+
70
+ valid_convention, message = _check_model_folder_convention(
71
+ model_type,
72
+ node.original_file_path,
73
+ regex_configuration,
74
+ node=node,
75
+ sources=self.sources,
76
+ )
77
+ if not valid_convention:
78
+ insights.append(
79
+ DBTModelInsightResponse(
80
+ unique_id=node.unique_id,
81
+ package_name=node.package_name,
82
+ path=node.path,
83
+ original_file_path=node.original_file_path,
84
+ insight=self._build_failure_result(
85
+ model_unique_id=node.unique_id,
86
+ model_type=model_type,
87
+ convention=message,
88
+ ),
89
+ severity=get_severity(self.config, self.ALIAS, self.DEFAULT_SEVERITY),
90
+ )
91
+ )
92
+ return insights
@@ -0,0 +1,97 @@
1
+ from typing import List
2
+ from typing import Optional
3
+
4
+ from datapilot.config.utils import get_regex_configuration
5
+ from datapilot.core.insights.utils import get_severity
6
+ from datapilot.core.platforms.dbt.constants import MODEL
7
+ from datapilot.core.platforms.dbt.constants import OTHER
8
+ from datapilot.core.platforms.dbt.insights.schema import DBTInsightResult
9
+ from datapilot.core.platforms.dbt.insights.schema import DBTModelInsightResponse
10
+ from datapilot.core.platforms.dbt.insights.structure.base import DBTStructureInsight
11
+ from datapilot.core.platforms.dbt.schemas.manifest import AltimateResourceType
12
+ from datapilot.core.platforms.dbt.utils import _check_model_naming_convention
13
+ from datapilot.core.platforms.dbt.utils import classify_model_type
14
+
15
+
16
+ class DBTModelNamingConvention(DBTStructureInsight):
17
+ """
18
+ DBTModelNamingConvention identifies models that do not follow the naming convention.
19
+ """
20
+
21
+ NAME = "Bad model naming convention"
22
+ ALIAS = "model_naming_convention_check"
23
+ DESCRIPTION = "This rule identifies models that do not follow the naming convention."
24
+ REASON_TO_FLAG = (
25
+ "Inconsistent or unclear naming conventions can lead to confusion and errors in querying the data warehouse. "
26
+ "A well-defined naming convention clarifies the model type and purpose, promoting better understanding "
27
+ "and effective data management. This rule flags models that deviate from established naming standards."
28
+ )
29
+ FAILURE_MESSAGE = (
30
+ "Naming Convention Violation Detected: The model `{model_unique_id}` does not comply with the "
31
+ "established naming convention. It is identified as a `{model_type}` model, but its name does not "
32
+ "reflect the required prefix or convention `{convention}`. Please update the model name to align "
33
+ "with the naming standards."
34
+ )
35
+ RECOMMENDATION = "Please rename the model `{model_unique_id}` to follow the appropriate naming convention. "
36
+
37
+ def _build_failure_result(self, model_unique_id: str, model_type: str, convention: Optional[str]) -> DBTInsightResult:
38
+ if model_type != OTHER:
39
+ failure_message = self.FAILURE_MESSAGE.format(
40
+ model_unique_id=model_unique_id,
41
+ model_type=model_type,
42
+ convention=convention,
43
+ )
44
+ else:
45
+ failure_message = (
46
+ f"The model `{model_unique_id}` was not classified as any of the known model types. "
47
+ "The naming conventions for it may not be appropriate"
48
+ )
49
+
50
+ return DBTInsightResult(
51
+ name=self.NAME,
52
+ type=self.TYPE,
53
+ message=failure_message,
54
+ recommendation=self.RECOMMENDATION.format(model_unique_id=model_unique_id),
55
+ reason_to_flag=self.REASON_TO_FLAG,
56
+ metadata={
57
+ "model": model_unique_id,
58
+ "model_type": model_type,
59
+ "convention": convention,
60
+ },
61
+ )
62
+
63
+ def generate(self, *args, **kwargs) -> List[DBTModelInsightResponse]:
64
+ insights = []
65
+ regex_configuration = get_regex_configuration(self.config)
66
+ for node in self.nodes.values():
67
+ if self.should_skip_model(node.unique_id):
68
+ self.logger.debug(f"Skipping model {node.unique_id} as it is not enabled for selected models")
69
+ continue
70
+ if node.resource_type == AltimateResourceType.model:
71
+ model_type = classify_model_type(node.name, node.original_file_path, regex_configuration)
72
+ if model_type == OTHER:
73
+ insights.append(
74
+ DBTModelInsightResponse(
75
+ unique_id=node.unique_id,
76
+ package_name=node.package_name,
77
+ path=node.path,
78
+ original_file_path=node.original_file_path,
79
+ insight=self._build_failure_result(node.unique_id, model_type, None),
80
+ severity=get_severity(self.config, self.ALIAS, self.DEFAULT_SEVERITY),
81
+ )
82
+ )
83
+ continue
84
+ valid_name, expected_model_type = _check_model_naming_convention(node.name, model_type, regex_configuration.get(MODEL))
85
+ if not valid_name:
86
+ insight_result = self._build_failure_result(node.unique_id, model_type, expected_model_type)
87
+ insights.append(
88
+ DBTModelInsightResponse(
89
+ unique_id=node.unique_id,
90
+ package_name=node.package_name,
91
+ path=node.path,
92
+ original_file_path=node.original_file_path,
93
+ insight=insight_result,
94
+ severity=get_severity(self.config, self.ALIAS, self.DEFAULT_SEVERITY),
95
+ )
96
+ )
97
+ return insights
@@ -0,0 +1,80 @@
1
+ from typing import List
2
+ from typing import Optional
3
+
4
+ from datapilot.config.utils import get_regex_configuration
5
+ from datapilot.core.insights.utils import get_severity
6
+ from datapilot.core.platforms.dbt.insights.schema import DBTInsightResult
7
+ from datapilot.core.platforms.dbt.insights.schema import DBTModelInsightResponse
8
+ from datapilot.core.platforms.dbt.insights.structure.base import DBTStructureInsight
9
+ from datapilot.core.platforms.dbt.utils import _check_source_folder_convention
10
+
11
+
12
+ class DBTSourceDirectoryStructure(DBTStructureInsight):
13
+ """
14
+ DBTSourcesDirectoryStructure checks if sources are placed in the correct directories.
15
+ """
16
+
17
+ NAME = "Bad source directory structure"
18
+ ALIAS = "source_directory_structure"
19
+ DESCRIPTION = "This rule identifies sources that are not placed in their correct directories. "
20
+ REASON_TO_FLAG = (
21
+ "Sources need to be organized in the correct directories to ensure an efficient and "
22
+ "maintainable data architecture. Proper directory structure facilitates easy navigation, "
23
+ "improves readability, and aids in managing the data sources effectively."
24
+ )
25
+ FAILURE_MESSAGE = (
26
+ "Inappropriate Directory Placement Detected: The source file for {source_id} is currently "
27
+ "placed in an incorrect directory. This can lead to organizational issues and hinder "
28
+ "efficient source management."
29
+ )
30
+ RECOMMENDATION = (
31
+ "To address this issue, please move the source file for {source_id} to the appropriate "
32
+ "directory. The recommended directory structure is {convention}, which aligns with best "
33
+ "practices for organizing source files in dbt projects."
34
+ )
35
+
36
+ def _build_failure_result(self, model_unique_id: str, convention: Optional[str]) -> DBTInsightResult:
37
+ failure_message = self.FAILURE_MESSAGE.format(
38
+ source_id=model_unique_id,
39
+ )
40
+ return DBTInsightResult(
41
+ name=self.NAME,
42
+ type=self.TYPE,
43
+ message=failure_message,
44
+ recommendation=self.RECOMMENDATION.format(source_id=model_unique_id, convention=convention),
45
+ reason_to_flag=self.REASON_TO_FLAG,
46
+ metadata={
47
+ "source_id": model_unique_id,
48
+ "convention": convention,
49
+ },
50
+ )
51
+
52
+ def generate(self, *args, **kwargs) -> List[DBTModelInsightResponse]:
53
+ insights = []
54
+ regex_configuration = get_regex_configuration(self.config)
55
+ for source_id, source in self.sources.items():
56
+ if self.should_skip_model(source_id):
57
+ self.logger.debug(f"Skipping model {source_id} as it is not enabled for selected models")
58
+ continue
59
+ valid_convention, expected_directory = _check_source_folder_convention(
60
+ source_name=source.source_name,
61
+ folder_path=source.original_file_path,
62
+ patterns=regex_configuration,
63
+ )
64
+ if not valid_convention:
65
+ insight = self._build_failure_result(
66
+ model_unique_id=source_id,
67
+ convention=expected_directory,
68
+ )
69
+ insights.append(
70
+ DBTModelInsightResponse(
71
+ unique_id=source.unique_id,
72
+ package_name=source.package_name,
73
+ path=source.path,
74
+ original_file_path=source.original_file_path,
75
+ insight=insight,
76
+ severity=get_severity(self.config, self.ALIAS, self.DEFAULT_SEVERITY),
77
+ )
78
+ )
79
+
80
+ return insights
@@ -0,0 +1,74 @@
1
+ from typing import List
2
+ from typing import Optional
3
+
4
+ from datapilot.core.insights.utils import get_severity
5
+ from datapilot.core.platforms.dbt.insights.schema import DBTInsightResult
6
+ from datapilot.core.platforms.dbt.insights.schema import DBTModelInsightResponse
7
+ from datapilot.core.platforms.dbt.insights.structure.base import DBTStructureInsight
8
+ from datapilot.utils.utils import get_dir_path
9
+
10
+
11
+ class DBTTestDirectoryStructure(DBTStructureInsight):
12
+ """
13
+ DBTTestDirectoryStructure checks if tests are placed in the correct directories.
14
+ """
15
+
16
+ NAME = "Bad test directory structure"
17
+ ALIAS = "test_directory_structure"
18
+ DESCRIPTION = "This rule checks if tests are correctly placed in the same directories as their corresponding models."
19
+ REASON_TO_FLAG = (
20
+ "It is important for tests to be placed in the same directory as their corresponding models to maintain "
21
+ "a coherent and easy-to-navigate project structure. This practice enhances the ease of understanding "
22
+ "and updating tests in parallel with model changes."
23
+ )
24
+ FAILURE_MESSAGE = (
25
+ "Incorrect Test Placement Detected: The test `{model_unique_id}` is not in the correct directory. "
26
+ "For consistent project structure and easy maintenance, it should be placed in the same directory as "
27
+ "its corresponding model."
28
+ )
29
+ RECOMMENDATION = (
30
+ "To rectify this, move the test `{model_unique_id}` to the directory `{convention}`, where its corresponding "
31
+ "model is located. This adjustment will align your test's location with best practices for"
32
+ " project organization."
33
+ )
34
+
35
+ def _build_failure_result(self, model_unique_id: str, convention: Optional[str]) -> DBTInsightResult:
36
+ failure_message = self.FAILURE_MESSAGE.format(
37
+ model_unique_id=model_unique_id,
38
+ )
39
+ return DBTInsightResult(
40
+ name=self.NAME,
41
+ type=self.TYPE,
42
+ message=failure_message,
43
+ recommendation=self.RECOMMENDATION.format(model_unique_id=model_unique_id, convention=convention),
44
+ reason_to_flag=self.REASON_TO_FLAG,
45
+ metadata={
46
+ "model": model_unique_id,
47
+ "convention": convention,
48
+ },
49
+ )
50
+
51
+ def generate(self, *args, **kwargs) -> List[DBTModelInsightResponse]:
52
+ insights = []
53
+ for test_id, test in self.tests.items():
54
+ if self.should_skip_model(test_id):
55
+ self.logger.debug(f"Skipping model {test_id} as it is not enabled for selected models")
56
+ continue
57
+ test_file_path = get_dir_path(test_id)
58
+ for node_id in test.depends_on.nodes:
59
+ node = self.get_node(node_id)
60
+ if not node:
61
+ continue
62
+ expected_dir_path = get_dir_path(node_id)
63
+ if expected_dir_path != test_file_path:
64
+ insights.append(
65
+ DBTModelInsightResponse(
66
+ unique_id=test_id,
67
+ package_name=test.package_name,
68
+ path=test.path,
69
+ original_file_path=test.original_file_path,
70
+ insight=self._build_failure_result(test_id, expected_dir_path),
71
+ severity=get_severity(self.config, self.ALIAS, self.DEFAULT_SEVERITY),
72
+ )
73
+ )
74
+ return insights
@@ -0,0 +1,9 @@
1
+ from datapilot.core.platforms.dbt.insights import INSIGHTS
2
+
3
+
4
+ def get_insight_with_configs():
5
+ return [insight.get_config_schema() for insight in INSIGHTS]
6
+
7
+
8
+ def insights_require_catalog(insights):
9
+ return any(insight.requires_catalog() for insight in insights)
File without changes
@@ -0,0 +1,73 @@
1
+ from datetime import datetime
2
+ from typing import ClassVar
3
+ from typing import Dict
4
+ from typing import List
5
+ from typing import Optional
6
+ from typing import Union
7
+
8
+ from pydantic.config import Extra
9
+ from pydantic.main import BaseModel
10
+
11
+
12
+ class AltimateCatalogMetadata(BaseModel):
13
+ class Config:
14
+ extra = Extra.forbid
15
+
16
+ dbt_schema_version: Optional[str] = "https://schemas.getdbt.com/dbt/catalog/v1.json"
17
+ dbt_version: Optional[str] = "0.19.0"
18
+ generated_at: Optional[datetime] = "2021-02-10T04:42:33.680487Z"
19
+ invocation_id: Optional[Optional[str]] = None
20
+ env: ClassVar[Optional[Dict[str, str]]] = {}
21
+
22
+
23
+ class AltimateCatalogTableMetadata(BaseModel):
24
+ class Config:
25
+ extra = Extra.forbid
26
+
27
+ type: str
28
+ database: Optional[Optional[str]] = None
29
+ schema_name: str
30
+ name: str
31
+ comment: Optional[Optional[str]] = None
32
+ owner: Optional[Optional[str]] = None
33
+
34
+
35
+ class AltimateCatalogColumnMetadata(BaseModel):
36
+ class Config:
37
+ extra = Extra.forbid
38
+
39
+ type: str
40
+ comment: Optional[Optional[str]] = None
41
+ index: int
42
+ name: str
43
+
44
+
45
+ class AltimateCatalogStatsItem(BaseModel):
46
+ class Config:
47
+ extra = Extra.forbid
48
+
49
+ id: str
50
+ label: str
51
+ value: Optional[Optional[Union[bool, str, float]]] = None
52
+ description: Optional[Optional[str]] = None
53
+ include: bool
54
+
55
+
56
+ class AltimateCatalogTable(BaseModel):
57
+ class Config:
58
+ extra = Extra.forbid
59
+
60
+ metadata: AltimateCatalogTableMetadata
61
+ columns: Dict[str, AltimateCatalogColumnMetadata]
62
+ stats: Dict[str, AltimateCatalogStatsItem]
63
+ unique_id: Optional[Optional[str]] = None
64
+
65
+
66
+ class AltimateCatalogCatalogV1(BaseModel):
67
+ class Config:
68
+ extra = Extra.forbid
69
+
70
+ metadata: AltimateCatalogMetadata
71
+ nodes: Dict[str, AltimateCatalogTable]
72
+ sources: Dict[str, AltimateCatalogTable]
73
+ errors: Optional[Optional[List[str]]] = None