truthound-dashboard 1.4.4__py3-none-any.whl → 1.5.1__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 (205) hide show
  1. truthound_dashboard/api/alerts.py +75 -86
  2. truthound_dashboard/api/anomaly.py +7 -13
  3. truthound_dashboard/api/cross_alerts.py +38 -52
  4. truthound_dashboard/api/drift.py +49 -59
  5. truthound_dashboard/api/drift_monitor.py +234 -79
  6. truthound_dashboard/api/enterprise_sampling.py +498 -0
  7. truthound_dashboard/api/history.py +57 -5
  8. truthound_dashboard/api/lineage.py +3 -48
  9. truthound_dashboard/api/maintenance.py +104 -49
  10. truthound_dashboard/api/mask.py +1 -2
  11. truthound_dashboard/api/middleware.py +2 -1
  12. truthound_dashboard/api/model_monitoring.py +435 -311
  13. truthound_dashboard/api/notifications.py +227 -191
  14. truthound_dashboard/api/notifications_advanced.py +21 -20
  15. truthound_dashboard/api/observability.py +586 -0
  16. truthound_dashboard/api/plugins.py +2 -433
  17. truthound_dashboard/api/profile.py +199 -37
  18. truthound_dashboard/api/quality_reporter.py +701 -0
  19. truthound_dashboard/api/reports.py +7 -16
  20. truthound_dashboard/api/router.py +66 -0
  21. truthound_dashboard/api/rule_suggestions.py +5 -5
  22. truthound_dashboard/api/scan.py +17 -19
  23. truthound_dashboard/api/schedules.py +85 -50
  24. truthound_dashboard/api/schema_evolution.py +6 -6
  25. truthound_dashboard/api/schema_watcher.py +667 -0
  26. truthound_dashboard/api/sources.py +98 -27
  27. truthound_dashboard/api/tiering.py +1323 -0
  28. truthound_dashboard/api/triggers.py +14 -11
  29. truthound_dashboard/api/validations.py +12 -11
  30. truthound_dashboard/api/versioning.py +1 -6
  31. truthound_dashboard/core/__init__.py +129 -3
  32. truthound_dashboard/core/actions/__init__.py +62 -0
  33. truthound_dashboard/core/actions/custom.py +426 -0
  34. truthound_dashboard/core/actions/notifications.py +910 -0
  35. truthound_dashboard/core/actions/storage.py +472 -0
  36. truthound_dashboard/core/actions/webhook.py +281 -0
  37. truthound_dashboard/core/anomaly.py +262 -67
  38. truthound_dashboard/core/anomaly_explainer.py +4 -3
  39. truthound_dashboard/core/backends/__init__.py +67 -0
  40. truthound_dashboard/core/backends/base.py +299 -0
  41. truthound_dashboard/core/backends/errors.py +191 -0
  42. truthound_dashboard/core/backends/factory.py +423 -0
  43. truthound_dashboard/core/backends/mock_backend.py +451 -0
  44. truthound_dashboard/core/backends/truthound_backend.py +718 -0
  45. truthound_dashboard/core/checkpoint/__init__.py +87 -0
  46. truthound_dashboard/core/checkpoint/adapters.py +814 -0
  47. truthound_dashboard/core/checkpoint/checkpoint.py +491 -0
  48. truthound_dashboard/core/checkpoint/runner.py +270 -0
  49. truthound_dashboard/core/connections.py +645 -23
  50. truthound_dashboard/core/converters/__init__.py +14 -0
  51. truthound_dashboard/core/converters/truthound.py +620 -0
  52. truthound_dashboard/core/cross_alerts.py +540 -320
  53. truthound_dashboard/core/datasource_factory.py +1672 -0
  54. truthound_dashboard/core/drift_monitor.py +216 -20
  55. truthound_dashboard/core/enterprise_sampling.py +1291 -0
  56. truthound_dashboard/core/interfaces/__init__.py +225 -0
  57. truthound_dashboard/core/interfaces/actions.py +652 -0
  58. truthound_dashboard/core/interfaces/base.py +247 -0
  59. truthound_dashboard/core/interfaces/checkpoint.py +676 -0
  60. truthound_dashboard/core/interfaces/protocols.py +664 -0
  61. truthound_dashboard/core/interfaces/reporters.py +650 -0
  62. truthound_dashboard/core/interfaces/routing.py +646 -0
  63. truthound_dashboard/core/interfaces/triggers.py +619 -0
  64. truthound_dashboard/core/lineage.py +407 -71
  65. truthound_dashboard/core/model_monitoring.py +431 -3
  66. truthound_dashboard/core/notifications/base.py +4 -0
  67. truthound_dashboard/core/notifications/channels.py +501 -1203
  68. truthound_dashboard/core/notifications/deduplication/__init__.py +81 -115
  69. truthound_dashboard/core/notifications/deduplication/service.py +131 -348
  70. truthound_dashboard/core/notifications/dispatcher.py +202 -11
  71. truthound_dashboard/core/notifications/escalation/__init__.py +119 -106
  72. truthound_dashboard/core/notifications/escalation/engine.py +168 -358
  73. truthound_dashboard/core/notifications/routing/__init__.py +88 -128
  74. truthound_dashboard/core/notifications/routing/engine.py +90 -317
  75. truthound_dashboard/core/notifications/stats_aggregator.py +246 -1
  76. truthound_dashboard/core/notifications/throttling/__init__.py +67 -50
  77. truthound_dashboard/core/notifications/throttling/builder.py +117 -255
  78. truthound_dashboard/core/notifications/truthound_adapter.py +842 -0
  79. truthound_dashboard/core/phase5/collaboration.py +1 -1
  80. truthound_dashboard/core/plugins/lifecycle/__init__.py +0 -13
  81. truthound_dashboard/core/quality_reporter.py +1359 -0
  82. truthound_dashboard/core/report_history.py +0 -6
  83. truthound_dashboard/core/reporters/__init__.py +175 -14
  84. truthound_dashboard/core/reporters/adapters.py +943 -0
  85. truthound_dashboard/core/reporters/base.py +0 -3
  86. truthound_dashboard/core/reporters/builtin/__init__.py +18 -0
  87. truthound_dashboard/core/reporters/builtin/csv_reporter.py +111 -0
  88. truthound_dashboard/core/reporters/builtin/html_reporter.py +270 -0
  89. truthound_dashboard/core/reporters/builtin/json_reporter.py +127 -0
  90. truthound_dashboard/core/reporters/compat.py +266 -0
  91. truthound_dashboard/core/reporters/csv_reporter.py +2 -35
  92. truthound_dashboard/core/reporters/factory.py +526 -0
  93. truthound_dashboard/core/reporters/interfaces.py +745 -0
  94. truthound_dashboard/core/reporters/registry.py +1 -10
  95. truthound_dashboard/core/scheduler.py +165 -0
  96. truthound_dashboard/core/schema_evolution.py +3 -3
  97. truthound_dashboard/core/schema_watcher.py +1528 -0
  98. truthound_dashboard/core/services.py +595 -76
  99. truthound_dashboard/core/store_manager.py +810 -0
  100. truthound_dashboard/core/streaming_anomaly.py +169 -4
  101. truthound_dashboard/core/tiering.py +1309 -0
  102. truthound_dashboard/core/triggers/evaluators.py +178 -8
  103. truthound_dashboard/core/truthound_adapter.py +2620 -197
  104. truthound_dashboard/core/unified_alerts.py +23 -20
  105. truthound_dashboard/db/__init__.py +8 -0
  106. truthound_dashboard/db/database.py +8 -2
  107. truthound_dashboard/db/models.py +944 -25
  108. truthound_dashboard/db/repository.py +2 -0
  109. truthound_dashboard/main.py +15 -0
  110. truthound_dashboard/schemas/__init__.py +177 -16
  111. truthound_dashboard/schemas/base.py +44 -23
  112. truthound_dashboard/schemas/collaboration.py +19 -6
  113. truthound_dashboard/schemas/cross_alerts.py +19 -3
  114. truthound_dashboard/schemas/drift.py +61 -55
  115. truthound_dashboard/schemas/drift_monitor.py +67 -23
  116. truthound_dashboard/schemas/enterprise_sampling.py +653 -0
  117. truthound_dashboard/schemas/lineage.py +0 -33
  118. truthound_dashboard/schemas/mask.py +10 -8
  119. truthound_dashboard/schemas/model_monitoring.py +89 -10
  120. truthound_dashboard/schemas/notifications_advanced.py +13 -0
  121. truthound_dashboard/schemas/observability.py +453 -0
  122. truthound_dashboard/schemas/plugins.py +0 -280
  123. truthound_dashboard/schemas/profile.py +154 -247
  124. truthound_dashboard/schemas/quality_reporter.py +403 -0
  125. truthound_dashboard/schemas/reports.py +2 -2
  126. truthound_dashboard/schemas/rule_suggestion.py +8 -1
  127. truthound_dashboard/schemas/scan.py +4 -24
  128. truthound_dashboard/schemas/schedule.py +11 -3
  129. truthound_dashboard/schemas/schema_watcher.py +727 -0
  130. truthound_dashboard/schemas/source.py +17 -2
  131. truthound_dashboard/schemas/tiering.py +822 -0
  132. truthound_dashboard/schemas/triggers.py +16 -0
  133. truthound_dashboard/schemas/unified_alerts.py +7 -0
  134. truthound_dashboard/schemas/validation.py +0 -13
  135. truthound_dashboard/schemas/validators/base.py +41 -21
  136. truthound_dashboard/schemas/validators/business_rule_validators.py +244 -0
  137. truthound_dashboard/schemas/validators/localization_validators.py +273 -0
  138. truthound_dashboard/schemas/validators/ml_feature_validators.py +308 -0
  139. truthound_dashboard/schemas/validators/profiling_validators.py +275 -0
  140. truthound_dashboard/schemas/validators/referential_validators.py +312 -0
  141. truthound_dashboard/schemas/validators/registry.py +93 -8
  142. truthound_dashboard/schemas/validators/timeseries_validators.py +389 -0
  143. truthound_dashboard/schemas/versioning.py +1 -6
  144. truthound_dashboard/static/index.html +2 -2
  145. truthound_dashboard-1.5.1.dist-info/METADATA +312 -0
  146. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/RECORD +149 -148
  147. truthound_dashboard/core/plugins/hooks/__init__.py +0 -63
  148. truthound_dashboard/core/plugins/hooks/decorators.py +0 -367
  149. truthound_dashboard/core/plugins/hooks/manager.py +0 -403
  150. truthound_dashboard/core/plugins/hooks/protocols.py +0 -265
  151. truthound_dashboard/core/plugins/lifecycle/hot_reload.py +0 -584
  152. truthound_dashboard/core/reporters/junit_reporter.py +0 -233
  153. truthound_dashboard/core/reporters/markdown_reporter.py +0 -207
  154. truthound_dashboard/core/reporters/pdf_reporter.py +0 -209
  155. truthound_dashboard/static/assets/_baseUniq-BcrSP13d.js +0 -1
  156. truthound_dashboard/static/assets/arc-DlYjKwIL.js +0 -1
  157. truthound_dashboard/static/assets/architectureDiagram-VXUJARFQ-Bb2drbQM.js +0 -36
  158. truthound_dashboard/static/assets/blockDiagram-VD42YOAC-BlsPG1CH.js +0 -122
  159. truthound_dashboard/static/assets/c4Diagram-YG6GDRKO-B9JdUoaC.js +0 -10
  160. truthound_dashboard/static/assets/channel-Q6mHF1Hd.js +0 -1
  161. truthound_dashboard/static/assets/chunk-4BX2VUAB-DmyoPVuJ.js +0 -1
  162. truthound_dashboard/static/assets/chunk-55IACEB6-Bcz6Siv8.js +0 -1
  163. truthound_dashboard/static/assets/chunk-B4BG7PRW-Br3G5Rum.js +0 -165
  164. truthound_dashboard/static/assets/chunk-DI55MBZ5-DuM9c23u.js +0 -220
  165. truthound_dashboard/static/assets/chunk-FMBD7UC4-DNU-5mvT.js +0 -15
  166. truthound_dashboard/static/assets/chunk-QN33PNHL-Im2yNcmS.js +0 -1
  167. truthound_dashboard/static/assets/chunk-QZHKN3VN-kZr8XFm1.js +0 -1
  168. truthound_dashboard/static/assets/chunk-TZMSLE5B-Q__360q_.js +0 -1
  169. truthound_dashboard/static/assets/classDiagram-2ON5EDUG-vtixxUyK.js +0 -1
  170. truthound_dashboard/static/assets/classDiagram-v2-WZHVMYZB-vtixxUyK.js +0 -1
  171. truthound_dashboard/static/assets/clone-BOt2LwD0.js +0 -1
  172. truthound_dashboard/static/assets/cose-bilkent-S5V4N54A-CBDw6iac.js +0 -1
  173. truthound_dashboard/static/assets/dagre-6UL2VRFP-XdKqmmY9.js +0 -4
  174. truthound_dashboard/static/assets/diagram-PSM6KHXK-DAZ8nx9V.js +0 -24
  175. truthound_dashboard/static/assets/diagram-QEK2KX5R-BRvDTbGD.js +0 -43
  176. truthound_dashboard/static/assets/diagram-S2PKOQOG-bQcczUkl.js +0 -24
  177. truthound_dashboard/static/assets/erDiagram-Q2GNP2WA-DPje7VMN.js +0 -60
  178. truthound_dashboard/static/assets/flowDiagram-NV44I4VS-B7BVtFVS.js +0 -162
  179. truthound_dashboard/static/assets/ganttDiagram-JELNMOA3-D6WKSS7U.js +0 -267
  180. truthound_dashboard/static/assets/gitGraphDiagram-NY62KEGX-D3vtVd3y.js +0 -65
  181. truthound_dashboard/static/assets/graph-BKgNKZVp.js +0 -1
  182. truthound_dashboard/static/assets/index-C6JSrkHo.css +0 -1
  183. truthound_dashboard/static/assets/index-DkU82VsU.js +0 -1800
  184. truthound_dashboard/static/assets/infoDiagram-WHAUD3N6-DnNCT429.js +0 -2
  185. truthound_dashboard/static/assets/journeyDiagram-XKPGCS4Q-DGiMozqS.js +0 -139
  186. truthound_dashboard/static/assets/kanban-definition-3W4ZIXB7-BV2gUgli.js +0 -89
  187. truthound_dashboard/static/assets/katex-Cu_Erd72.js +0 -261
  188. truthound_dashboard/static/assets/layout-DI2MfQ5G.js +0 -1
  189. truthound_dashboard/static/assets/min-DYdgXVcT.js +0 -1
  190. truthound_dashboard/static/assets/mindmap-definition-VGOIOE7T-C7x4ruxz.js +0 -68
  191. truthound_dashboard/static/assets/pieDiagram-ADFJNKIX-CAJaAB9f.js +0 -30
  192. truthound_dashboard/static/assets/quadrantDiagram-AYHSOK5B-DeqwDI46.js +0 -7
  193. truthound_dashboard/static/assets/requirementDiagram-UZGBJVZJ-e3XDpZIM.js +0 -64
  194. truthound_dashboard/static/assets/sankeyDiagram-TZEHDZUN-CNnAv5Ux.js +0 -10
  195. truthound_dashboard/static/assets/sequenceDiagram-WL72ISMW-Dsne-Of3.js +0 -145
  196. truthound_dashboard/static/assets/stateDiagram-FKZM4ZOC-Ee0sQXyb.js +0 -1
  197. truthound_dashboard/static/assets/stateDiagram-v2-4FDKWEC3-B26KqW_W.js +0 -1
  198. truthound_dashboard/static/assets/timeline-definition-IT6M3QCI-DZYi2yl3.js +0 -61
  199. truthound_dashboard/static/assets/treemap-KMMF4GRG-CY3f8In2.js +0 -128
  200. truthound_dashboard/static/assets/unmerged_dictionaries-Dd7xcPWG.js +0 -1
  201. truthound_dashboard/static/assets/xychartDiagram-PRI3JC2R-CS7fydZZ.js +0 -7
  202. truthound_dashboard-1.4.4.dist-info/METADATA +0 -507
  203. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/WHEEL +0 -0
  204. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/entry_points.txt +0 -0
  205. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,491 @@
1
+ """Checkpoint implementation for validation pipeline orchestration.
2
+
3
+ Provides the core Checkpoint class that orchestrates the complete
4
+ validation pipeline from data loading through action execution.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import logging
11
+ import uuid
12
+ from dataclasses import dataclass, field
13
+ from datetime import datetime
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ from truthound_dashboard.core.interfaces.actions import (
17
+ ActionContext,
18
+ ActionProtocol,
19
+ ActionResult,
20
+ ActionStatus,
21
+ AsyncActionProtocol,
22
+ BaseAction,
23
+ AsyncBaseAction,
24
+ )
25
+ from truthound_dashboard.core.interfaces.checkpoint import (
26
+ CheckpointConfig,
27
+ CheckpointProtocol,
28
+ CheckpointResult,
29
+ CheckpointStatus,
30
+ )
31
+ from truthound_dashboard.core.interfaces.routing import (
32
+ Route,
33
+ RouteContext,
34
+ Router,
35
+ RouteMode,
36
+ )
37
+ from truthound_dashboard.core.interfaces.triggers import BaseTrigger
38
+
39
+ if TYPE_CHECKING:
40
+ from truthound_dashboard.core.backends.base import BaseDataQualityBackend
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+
45
+ class Checkpoint(CheckpointProtocol):
46
+ """Concrete checkpoint implementation.
47
+
48
+ Orchestrates the complete validation pipeline:
49
+ 1. Load data from configured source
50
+ 2. Run validators
51
+ 3. Build result
52
+ 4. Evaluate routing rules
53
+ 5. Execute matched actions
54
+ 6. Return comprehensive result
55
+
56
+ Example:
57
+ checkpoint = Checkpoint(
58
+ config=CheckpointConfig(
59
+ name="daily_orders",
60
+ source_id="orders_db",
61
+ validators=["null", "uniqueness"],
62
+ ),
63
+ )
64
+
65
+ # Add actions
66
+ checkpoint.add_action(SlackNotificationAction(...))
67
+ checkpoint.add_action(FileStorageAction(...))
68
+
69
+ # Add routing
70
+ checkpoint.set_router(Router(routes=[...]))
71
+
72
+ # Run
73
+ result = await checkpoint.run()
74
+ """
75
+
76
+ def __init__(
77
+ self,
78
+ config: CheckpointConfig | dict[str, Any],
79
+ actions: list[BaseAction | AsyncBaseAction] | None = None,
80
+ triggers: list[BaseTrigger] | None = None,
81
+ router: Router | None = None,
82
+ backend: "BaseDataQualityBackend | None" = None,
83
+ ) -> None:
84
+ """Initialize checkpoint.
85
+
86
+ Args:
87
+ config: Checkpoint configuration.
88
+ actions: List of actions to execute.
89
+ triggers: List of triggers for this checkpoint.
90
+ router: Router for conditional action execution.
91
+ backend: Data quality backend (uses default if None).
92
+ """
93
+ if isinstance(config, dict):
94
+ config = CheckpointConfig.from_dict(config)
95
+
96
+ self._config = config
97
+ self._actions: dict[str, BaseAction | AsyncBaseAction] = {}
98
+ self._triggers: list[BaseTrigger] = triggers or []
99
+ self._router = router
100
+ self._backend = backend
101
+
102
+ # Register provided actions
103
+ if actions:
104
+ for action in actions:
105
+ self.add_action(action)
106
+
107
+ @property
108
+ def name(self) -> str:
109
+ """Get checkpoint name."""
110
+ return self._config.name
111
+
112
+ @property
113
+ def config(self) -> CheckpointConfig:
114
+ """Get checkpoint configuration."""
115
+ return self._config
116
+
117
+ @property
118
+ def actions(self) -> list[BaseAction | AsyncBaseAction]:
119
+ """Get configured actions."""
120
+ return list(self._actions.values())
121
+
122
+ @property
123
+ def triggers(self) -> list[BaseTrigger]:
124
+ """Get configured triggers."""
125
+ return self._triggers
126
+
127
+ @property
128
+ def router(self) -> Router | None:
129
+ """Get the router."""
130
+ return self._router
131
+
132
+ def add_action(self, action: BaseAction | AsyncBaseAction) -> None:
133
+ """Add an action to the checkpoint.
134
+
135
+ Args:
136
+ action: Action to add.
137
+ """
138
+ self._actions[action.name] = action
139
+
140
+ def remove_action(self, name: str) -> bool:
141
+ """Remove an action by name.
142
+
143
+ Args:
144
+ name: Action name.
145
+
146
+ Returns:
147
+ True if action was removed.
148
+ """
149
+ if name in self._actions:
150
+ del self._actions[name]
151
+ return True
152
+ return False
153
+
154
+ def add_trigger(self, trigger: BaseTrigger) -> None:
155
+ """Add a trigger to the checkpoint.
156
+
157
+ Args:
158
+ trigger: Trigger to add.
159
+ """
160
+ self._triggers.append(trigger)
161
+
162
+ def set_router(self, router: Router) -> None:
163
+ """Set the router for action routing.
164
+
165
+ Args:
166
+ router: Router to use.
167
+ """
168
+ self._router = router
169
+
170
+ def _get_backend(self) -> "BaseDataQualityBackend":
171
+ """Get the backend, initializing if needed.
172
+
173
+ Returns:
174
+ Data quality backend.
175
+ """
176
+ if self._backend is None:
177
+ from truthound_dashboard.core.backends.factory import BackendFactory
178
+ self._backend = BackendFactory.get_backend()
179
+ return self._backend
180
+
181
+ async def run(
182
+ self,
183
+ trigger_context: dict[str, Any] | None = None,
184
+ ) -> CheckpointResult:
185
+ """Run the checkpoint.
186
+
187
+ Executes the full validation pipeline:
188
+ 1. Validate configuration
189
+ 2. Load and validate data
190
+ 3. Build checkpoint result
191
+ 4. Execute actions based on routing
192
+
193
+ Args:
194
+ trigger_context: Optional context from the trigger.
195
+
196
+ Returns:
197
+ Checkpoint result with validation and action results.
198
+ """
199
+ run_id = str(uuid.uuid4())
200
+ started_at = datetime.now()
201
+
202
+ logger.info(f"Starting checkpoint run: {self.name} (run_id={run_id})")
203
+
204
+ try:
205
+ # Run validation
206
+ check_result = await self._run_validation()
207
+
208
+ # Build checkpoint result
209
+ result = CheckpointResult.from_check_result(
210
+ check_result=check_result,
211
+ checkpoint_name=self.name,
212
+ run_id=run_id,
213
+ config=self._config,
214
+ )
215
+ result.started_at = started_at
216
+ result.completed_at = datetime.now()
217
+ result.duration_ms = (result.completed_at - started_at).total_seconds() * 1000
218
+ result.trigger_context = trigger_context or {}
219
+
220
+ # Execute actions
221
+ action_results = await self._execute_actions(result, run_id)
222
+ result.action_results = action_results
223
+
224
+ logger.info(
225
+ f"Checkpoint completed: {self.name} "
226
+ f"status={result.status.value} "
227
+ f"issues={result.issue_count} "
228
+ f"duration={result.duration_ms:.1f}ms"
229
+ )
230
+
231
+ return result
232
+
233
+ except Exception as e:
234
+ logger.error(f"Checkpoint failed: {self.name} error={str(e)}")
235
+ completed_at = datetime.now()
236
+
237
+ return CheckpointResult(
238
+ checkpoint_name=self.name,
239
+ run_id=run_id,
240
+ status=CheckpointStatus.ERROR,
241
+ started_at=started_at,
242
+ completed_at=completed_at,
243
+ duration_ms=(completed_at - started_at).total_seconds() * 1000,
244
+ error_message=str(e),
245
+ trigger_context=trigger_context or {},
246
+ )
247
+
248
+ async def _run_validation(self) -> Any:
249
+ """Run the validation using the backend.
250
+
251
+ Returns:
252
+ Check result from the backend.
253
+ """
254
+ backend = self._get_backend()
255
+
256
+ # Build data input
257
+ data_input = await self._get_data_input()
258
+
259
+ # Run check with configured validators
260
+ return await backend.check(
261
+ data=data_input,
262
+ validators=self._config.validators or None,
263
+ validator_config=self._config.validator_config or None,
264
+ schema=self._config.schema_path,
265
+ auto_schema=self._config.auto_schema,
266
+ )
267
+
268
+ async def _get_data_input(self) -> Any:
269
+ """Get the data input for validation.
270
+
271
+ Returns:
272
+ Data input (path or DataSource).
273
+ """
274
+ # If source_id is set, load from database
275
+ if self._config.source_id:
276
+ from truthound_dashboard.core.services import get_source_data_input
277
+ return await get_source_data_input(self._config.source_id)
278
+
279
+ # Otherwise use source_name as path
280
+ if self._config.source_name:
281
+ return self._config.source_name
282
+
283
+ raise ValueError("No source_id or source_name configured for checkpoint")
284
+
285
+ async def _execute_actions(
286
+ self,
287
+ result: CheckpointResult,
288
+ run_id: str,
289
+ ) -> list[ActionResult]:
290
+ """Execute actions based on routing rules.
291
+
292
+ Args:
293
+ result: Checkpoint result.
294
+ run_id: Run identifier.
295
+
296
+ Returns:
297
+ List of action results.
298
+ """
299
+ action_results: list[ActionResult] = []
300
+
301
+ # Determine which actions to execute
302
+ if self._router:
303
+ # Use router to determine actions
304
+ route_context = RouteContext(
305
+ checkpoint_result=result,
306
+ run_id=run_id,
307
+ checkpoint_name=self.name,
308
+ tags=self._config.tags,
309
+ metadata=self._config.metadata,
310
+ )
311
+ action_names = self._router.route(route_context)
312
+ else:
313
+ # Execute all actions
314
+ action_names = list(self._actions.keys())
315
+
316
+ # Build action context
317
+ action_context = ActionContext(
318
+ checkpoint_result=result,
319
+ run_id=run_id,
320
+ checkpoint_name=self.name,
321
+ tags=self._config.tags,
322
+ metadata=self._config.metadata,
323
+ )
324
+
325
+ # Execute actions
326
+ for action_name in action_names:
327
+ action = self._actions.get(action_name)
328
+ if action is None:
329
+ logger.warning(f"Action not found: {action_name}")
330
+ continue
331
+
332
+ try:
333
+ if isinstance(action, AsyncBaseAction):
334
+ action_result = await action.execute_async(action_context)
335
+ else:
336
+ action_result = action.execute(action_context)
337
+ action_results.append(action_result)
338
+ except Exception as e:
339
+ logger.error(f"Action failed: {action_name} error={str(e)}")
340
+ action_results.append(ActionResult(
341
+ action_name=action_name,
342
+ action_type=getattr(action, "action_type", "unknown"),
343
+ status=ActionStatus.FAILURE,
344
+ message=f"Action failed: {str(e)}",
345
+ error=str(e),
346
+ ))
347
+
348
+ return action_results
349
+
350
+
351
+ class CheckpointBuilder:
352
+ """Builder pattern for constructing checkpoints.
353
+
354
+ Provides a fluent interface for building checkpoints.
355
+
356
+ Example:
357
+ checkpoint = (
358
+ CheckpointBuilder()
359
+ .name("daily_orders")
360
+ .source("orders_db")
361
+ .validators(["null", "uniqueness", "range"])
362
+ .action(SlackNotificationAction(...))
363
+ .action(FileStorageAction(...))
364
+ .route_on_failure(["slack", "pagerduty"])
365
+ .build()
366
+ )
367
+ """
368
+
369
+ def __init__(self) -> None:
370
+ """Initialize builder."""
371
+ self._config = CheckpointConfig()
372
+ self._actions: list[BaseAction | AsyncBaseAction] = []
373
+ self._triggers: list[BaseTrigger] = []
374
+ self._routes: list[Route] = []
375
+ self._router_mode = RouteMode.ALL_MATCHES
376
+
377
+ def name(self, name: str) -> "CheckpointBuilder":
378
+ """Set checkpoint name."""
379
+ self._config.name = name
380
+ return self
381
+
382
+ def description(self, description: str) -> "CheckpointBuilder":
383
+ """Set checkpoint description."""
384
+ self._config.description = description
385
+ return self
386
+
387
+ def source(self, source_id: str) -> "CheckpointBuilder":
388
+ """Set source ID."""
389
+ self._config.source_id = source_id
390
+ return self
391
+
392
+ def source_name(self, name: str) -> "CheckpointBuilder":
393
+ """Set source name/path."""
394
+ self._config.source_name = name
395
+ return self
396
+
397
+ def validators(self, validators: list[str]) -> "CheckpointBuilder":
398
+ """Set validators to run."""
399
+ self._config.validators = validators
400
+ return self
401
+
402
+ def validator_config(
403
+ self, config: dict[str, dict[str, Any]]
404
+ ) -> "CheckpointBuilder":
405
+ """Set validator configuration."""
406
+ self._config.validator_config = config
407
+ return self
408
+
409
+ def schema(self, path: str) -> "CheckpointBuilder":
410
+ """Set schema path."""
411
+ self._config.schema_path = path
412
+ return self
413
+
414
+ def auto_schema(self, enabled: bool = True) -> "CheckpointBuilder":
415
+ """Enable/disable auto schema learning."""
416
+ self._config.auto_schema = enabled
417
+ return self
418
+
419
+ def tags(self, tags: dict[str, str]) -> "CheckpointBuilder":
420
+ """Set tags."""
421
+ self._config.tags = tags
422
+ return self
423
+
424
+ def tag(self, key: str, value: str) -> "CheckpointBuilder":
425
+ """Add a single tag."""
426
+ self._config.tags[key] = value
427
+ return self
428
+
429
+ def timeout(self, seconds: int) -> "CheckpointBuilder":
430
+ """Set timeout in seconds."""
431
+ self._config.timeout_seconds = seconds
432
+ return self
433
+
434
+ def action(self, action: BaseAction | AsyncBaseAction) -> "CheckpointBuilder":
435
+ """Add an action."""
436
+ self._actions.append(action)
437
+ return self
438
+
439
+ def trigger(self, trigger: BaseTrigger) -> "CheckpointBuilder":
440
+ """Add a trigger."""
441
+ self._triggers.append(trigger)
442
+ return self
443
+
444
+ def route(self, route: Route) -> "CheckpointBuilder":
445
+ """Add a route."""
446
+ self._routes.append(route)
447
+ return self
448
+
449
+ def route_on_failure(self, actions: list[str]) -> "CheckpointBuilder":
450
+ """Add a route that triggers on failure."""
451
+ from truthound_dashboard.core.interfaces.routing import Jinja2Rule
452
+
453
+ self._routes.append(Route(
454
+ name="on_failure",
455
+ rule=Jinja2Rule("failure", "failed"),
456
+ actions=actions,
457
+ ))
458
+ return self
459
+
460
+ def route_on_critical(self, actions: list[str]) -> "CheckpointBuilder":
461
+ """Add a route that triggers on critical issues."""
462
+ from truthound_dashboard.core.interfaces.routing import Jinja2Rule
463
+
464
+ self._routes.append(Route(
465
+ name="on_critical",
466
+ rule=Jinja2Rule("critical", "has_critical"),
467
+ actions=actions,
468
+ ))
469
+ return self
470
+
471
+ def router_mode(self, mode: RouteMode) -> "CheckpointBuilder":
472
+ """Set router mode."""
473
+ self._router_mode = mode
474
+ return self
475
+
476
+ def build(self) -> Checkpoint:
477
+ """Build the checkpoint.
478
+
479
+ Returns:
480
+ Configured Checkpoint instance.
481
+ """
482
+ router = None
483
+ if self._routes:
484
+ router = Router(mode=self._router_mode, routes=self._routes)
485
+
486
+ return Checkpoint(
487
+ config=self._config,
488
+ actions=self._actions,
489
+ triggers=self._triggers,
490
+ router=router,
491
+ )