truthound-dashboard 1.4.3__py3-none-any.whl → 1.5.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.
- truthound_dashboard/api/alerts.py +75 -86
- truthound_dashboard/api/anomaly.py +7 -13
- truthound_dashboard/api/cross_alerts.py +38 -52
- truthound_dashboard/api/drift.py +49 -59
- truthound_dashboard/api/drift_monitor.py +234 -79
- truthound_dashboard/api/enterprise_sampling.py +498 -0
- truthound_dashboard/api/history.py +57 -5
- truthound_dashboard/api/lineage.py +3 -48
- truthound_dashboard/api/maintenance.py +104 -49
- truthound_dashboard/api/mask.py +1 -2
- truthound_dashboard/api/middleware.py +2 -1
- truthound_dashboard/api/model_monitoring.py +435 -311
- truthound_dashboard/api/notifications.py +227 -191
- truthound_dashboard/api/notifications_advanced.py +21 -20
- truthound_dashboard/api/observability.py +586 -0
- truthound_dashboard/api/plugins.py +2 -433
- truthound_dashboard/api/profile.py +199 -37
- truthound_dashboard/api/quality_reporter.py +701 -0
- truthound_dashboard/api/reports.py +7 -16
- truthound_dashboard/api/router.py +66 -0
- truthound_dashboard/api/rule_suggestions.py +5 -5
- truthound_dashboard/api/scan.py +17 -19
- truthound_dashboard/api/schedules.py +85 -50
- truthound_dashboard/api/schema_evolution.py +6 -6
- truthound_dashboard/api/schema_watcher.py +667 -0
- truthound_dashboard/api/sources.py +98 -27
- truthound_dashboard/api/tiering.py +1323 -0
- truthound_dashboard/api/triggers.py +14 -11
- truthound_dashboard/api/validations.py +12 -11
- truthound_dashboard/api/versioning.py +1 -6
- truthound_dashboard/core/__init__.py +129 -3
- truthound_dashboard/core/actions/__init__.py +62 -0
- truthound_dashboard/core/actions/custom.py +426 -0
- truthound_dashboard/core/actions/notifications.py +910 -0
- truthound_dashboard/core/actions/storage.py +472 -0
- truthound_dashboard/core/actions/webhook.py +281 -0
- truthound_dashboard/core/anomaly.py +262 -67
- truthound_dashboard/core/anomaly_explainer.py +4 -3
- truthound_dashboard/core/backends/__init__.py +67 -0
- truthound_dashboard/core/backends/base.py +299 -0
- truthound_dashboard/core/backends/errors.py +191 -0
- truthound_dashboard/core/backends/factory.py +423 -0
- truthound_dashboard/core/backends/mock_backend.py +451 -0
- truthound_dashboard/core/backends/truthound_backend.py +718 -0
- truthound_dashboard/core/checkpoint/__init__.py +87 -0
- truthound_dashboard/core/checkpoint/adapters.py +814 -0
- truthound_dashboard/core/checkpoint/checkpoint.py +491 -0
- truthound_dashboard/core/checkpoint/runner.py +270 -0
- truthound_dashboard/core/connections.py +437 -10
- truthound_dashboard/core/converters/__init__.py +14 -0
- truthound_dashboard/core/converters/truthound.py +620 -0
- truthound_dashboard/core/cross_alerts.py +540 -320
- truthound_dashboard/core/datasource_factory.py +1672 -0
- truthound_dashboard/core/drift_monitor.py +216 -20
- truthound_dashboard/core/enterprise_sampling.py +1291 -0
- truthound_dashboard/core/interfaces/__init__.py +225 -0
- truthound_dashboard/core/interfaces/actions.py +652 -0
- truthound_dashboard/core/interfaces/base.py +247 -0
- truthound_dashboard/core/interfaces/checkpoint.py +676 -0
- truthound_dashboard/core/interfaces/protocols.py +664 -0
- truthound_dashboard/core/interfaces/reporters.py +650 -0
- truthound_dashboard/core/interfaces/routing.py +646 -0
- truthound_dashboard/core/interfaces/triggers.py +619 -0
- truthound_dashboard/core/lineage.py +407 -71
- truthound_dashboard/core/model_monitoring.py +431 -3
- truthound_dashboard/core/notifications/base.py +4 -0
- truthound_dashboard/core/notifications/channels.py +501 -1203
- truthound_dashboard/core/notifications/deduplication/__init__.py +81 -115
- truthound_dashboard/core/notifications/deduplication/service.py +131 -348
- truthound_dashboard/core/notifications/dispatcher.py +202 -11
- truthound_dashboard/core/notifications/escalation/__init__.py +119 -106
- truthound_dashboard/core/notifications/escalation/engine.py +168 -358
- truthound_dashboard/core/notifications/routing/__init__.py +88 -128
- truthound_dashboard/core/notifications/routing/engine.py +90 -317
- truthound_dashboard/core/notifications/stats_aggregator.py +246 -1
- truthound_dashboard/core/notifications/throttling/__init__.py +67 -50
- truthound_dashboard/core/notifications/throttling/builder.py +117 -255
- truthound_dashboard/core/notifications/truthound_adapter.py +842 -0
- truthound_dashboard/core/phase5/collaboration.py +1 -1
- truthound_dashboard/core/plugins/lifecycle/__init__.py +0 -13
- truthound_dashboard/core/quality_reporter.py +1359 -0
- truthound_dashboard/core/report_history.py +0 -6
- truthound_dashboard/core/reporters/__init__.py +175 -14
- truthound_dashboard/core/reporters/adapters.py +943 -0
- truthound_dashboard/core/reporters/base.py +0 -3
- truthound_dashboard/core/reporters/builtin/__init__.py +18 -0
- truthound_dashboard/core/reporters/builtin/csv_reporter.py +111 -0
- truthound_dashboard/core/reporters/builtin/html_reporter.py +270 -0
- truthound_dashboard/core/reporters/builtin/json_reporter.py +127 -0
- truthound_dashboard/core/reporters/compat.py +266 -0
- truthound_dashboard/core/reporters/csv_reporter.py +2 -35
- truthound_dashboard/core/reporters/factory.py +526 -0
- truthound_dashboard/core/reporters/interfaces.py +745 -0
- truthound_dashboard/core/reporters/registry.py +1 -10
- truthound_dashboard/core/scheduler.py +165 -0
- truthound_dashboard/core/schema_evolution.py +3 -3
- truthound_dashboard/core/schema_watcher.py +1528 -0
- truthound_dashboard/core/services.py +595 -76
- truthound_dashboard/core/store_manager.py +810 -0
- truthound_dashboard/core/streaming_anomaly.py +169 -4
- truthound_dashboard/core/tiering.py +1309 -0
- truthound_dashboard/core/triggers/evaluators.py +178 -8
- truthound_dashboard/core/truthound_adapter.py +2620 -197
- truthound_dashboard/core/unified_alerts.py +23 -20
- truthound_dashboard/db/__init__.py +8 -0
- truthound_dashboard/db/database.py +8 -2
- truthound_dashboard/db/models.py +944 -25
- truthound_dashboard/db/repository.py +2 -0
- truthound_dashboard/main.py +11 -0
- truthound_dashboard/schemas/__init__.py +177 -16
- truthound_dashboard/schemas/base.py +44 -23
- truthound_dashboard/schemas/collaboration.py +19 -6
- truthound_dashboard/schemas/cross_alerts.py +19 -3
- truthound_dashboard/schemas/drift.py +61 -55
- truthound_dashboard/schemas/drift_monitor.py +67 -23
- truthound_dashboard/schemas/enterprise_sampling.py +653 -0
- truthound_dashboard/schemas/lineage.py +0 -33
- truthound_dashboard/schemas/mask.py +10 -8
- truthound_dashboard/schemas/model_monitoring.py +89 -10
- truthound_dashboard/schemas/notifications_advanced.py +13 -0
- truthound_dashboard/schemas/observability.py +453 -0
- truthound_dashboard/schemas/plugins.py +0 -280
- truthound_dashboard/schemas/profile.py +154 -247
- truthound_dashboard/schemas/quality_reporter.py +403 -0
- truthound_dashboard/schemas/reports.py +2 -2
- truthound_dashboard/schemas/rule_suggestion.py +8 -1
- truthound_dashboard/schemas/scan.py +4 -24
- truthound_dashboard/schemas/schedule.py +11 -3
- truthound_dashboard/schemas/schema_watcher.py +727 -0
- truthound_dashboard/schemas/source.py +17 -2
- truthound_dashboard/schemas/tiering.py +822 -0
- truthound_dashboard/schemas/triggers.py +16 -0
- truthound_dashboard/schemas/unified_alerts.py +7 -0
- truthound_dashboard/schemas/validation.py +0 -13
- truthound_dashboard/schemas/validators/base.py +41 -21
- truthound_dashboard/schemas/validators/business_rule_validators.py +244 -0
- truthound_dashboard/schemas/validators/localization_validators.py +273 -0
- truthound_dashboard/schemas/validators/ml_feature_validators.py +308 -0
- truthound_dashboard/schemas/validators/profiling_validators.py +275 -0
- truthound_dashboard/schemas/validators/referential_validators.py +312 -0
- truthound_dashboard/schemas/validators/registry.py +93 -8
- truthound_dashboard/schemas/validators/timeseries_validators.py +389 -0
- truthound_dashboard/schemas/versioning.py +1 -6
- truthound_dashboard/static/index.html +2 -2
- truthound_dashboard-1.5.0.dist-info/METADATA +309 -0
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/RECORD +149 -148
- truthound_dashboard/core/plugins/hooks/__init__.py +0 -63
- truthound_dashboard/core/plugins/hooks/decorators.py +0 -367
- truthound_dashboard/core/plugins/hooks/manager.py +0 -403
- truthound_dashboard/core/plugins/hooks/protocols.py +0 -265
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +0 -584
- truthound_dashboard/core/reporters/junit_reporter.py +0 -233
- truthound_dashboard/core/reporters/markdown_reporter.py +0 -207
- truthound_dashboard/core/reporters/pdf_reporter.py +0 -209
- truthound_dashboard/static/assets/_baseUniq-BcrSP13d.js +0 -1
- truthound_dashboard/static/assets/arc-DlYjKwIL.js +0 -1
- truthound_dashboard/static/assets/architectureDiagram-VXUJARFQ-Bb2drbQM.js +0 -36
- truthound_dashboard/static/assets/blockDiagram-VD42YOAC-BlsPG1CH.js +0 -122
- truthound_dashboard/static/assets/c4Diagram-YG6GDRKO-B9JdUoaC.js +0 -10
- truthound_dashboard/static/assets/channel-Q6mHF1Hd.js +0 -1
- truthound_dashboard/static/assets/chunk-4BX2VUAB-DmyoPVuJ.js +0 -1
- truthound_dashboard/static/assets/chunk-55IACEB6-Bcz6Siv8.js +0 -1
- truthound_dashboard/static/assets/chunk-B4BG7PRW-Br3G5Rum.js +0 -165
- truthound_dashboard/static/assets/chunk-DI55MBZ5-DuM9c23u.js +0 -220
- truthound_dashboard/static/assets/chunk-FMBD7UC4-DNU-5mvT.js +0 -15
- truthound_dashboard/static/assets/chunk-QN33PNHL-Im2yNcmS.js +0 -1
- truthound_dashboard/static/assets/chunk-QZHKN3VN-kZr8XFm1.js +0 -1
- truthound_dashboard/static/assets/chunk-TZMSLE5B-Q__360q_.js +0 -1
- truthound_dashboard/static/assets/classDiagram-2ON5EDUG-vtixxUyK.js +0 -1
- truthound_dashboard/static/assets/classDiagram-v2-WZHVMYZB-vtixxUyK.js +0 -1
- truthound_dashboard/static/assets/clone-BOt2LwD0.js +0 -1
- truthound_dashboard/static/assets/cose-bilkent-S5V4N54A-CBDw6iac.js +0 -1
- truthound_dashboard/static/assets/dagre-6UL2VRFP-XdKqmmY9.js +0 -4
- truthound_dashboard/static/assets/diagram-PSM6KHXK-DAZ8nx9V.js +0 -24
- truthound_dashboard/static/assets/diagram-QEK2KX5R-BRvDTbGD.js +0 -43
- truthound_dashboard/static/assets/diagram-S2PKOQOG-bQcczUkl.js +0 -24
- truthound_dashboard/static/assets/erDiagram-Q2GNP2WA-DPje7VMN.js +0 -60
- truthound_dashboard/static/assets/flowDiagram-NV44I4VS-B7BVtFVS.js +0 -162
- truthound_dashboard/static/assets/ganttDiagram-JELNMOA3-D6WKSS7U.js +0 -267
- truthound_dashboard/static/assets/gitGraphDiagram-NY62KEGX-D3vtVd3y.js +0 -65
- truthound_dashboard/static/assets/graph-BKgNKZVp.js +0 -1
- truthound_dashboard/static/assets/index-C6JSrkHo.css +0 -1
- truthound_dashboard/static/assets/index-DkU82VsU.js +0 -1800
- truthound_dashboard/static/assets/infoDiagram-WHAUD3N6-DnNCT429.js +0 -2
- truthound_dashboard/static/assets/journeyDiagram-XKPGCS4Q-DGiMozqS.js +0 -139
- truthound_dashboard/static/assets/kanban-definition-3W4ZIXB7-BV2gUgli.js +0 -89
- truthound_dashboard/static/assets/katex-Cu_Erd72.js +0 -261
- truthound_dashboard/static/assets/layout-DI2MfQ5G.js +0 -1
- truthound_dashboard/static/assets/min-DYdgXVcT.js +0 -1
- truthound_dashboard/static/assets/mindmap-definition-VGOIOE7T-C7x4ruxz.js +0 -68
- truthound_dashboard/static/assets/pieDiagram-ADFJNKIX-CAJaAB9f.js +0 -30
- truthound_dashboard/static/assets/quadrantDiagram-AYHSOK5B-DeqwDI46.js +0 -7
- truthound_dashboard/static/assets/requirementDiagram-UZGBJVZJ-e3XDpZIM.js +0 -64
- truthound_dashboard/static/assets/sankeyDiagram-TZEHDZUN-CNnAv5Ux.js +0 -10
- truthound_dashboard/static/assets/sequenceDiagram-WL72ISMW-Dsne-Of3.js +0 -145
- truthound_dashboard/static/assets/stateDiagram-FKZM4ZOC-Ee0sQXyb.js +0 -1
- truthound_dashboard/static/assets/stateDiagram-v2-4FDKWEC3-B26KqW_W.js +0 -1
- truthound_dashboard/static/assets/timeline-definition-IT6M3QCI-DZYi2yl3.js +0 -61
- truthound_dashboard/static/assets/treemap-KMMF4GRG-CY3f8In2.js +0 -128
- truthound_dashboard/static/assets/unmerged_dictionaries-Dd7xcPWG.js +0 -1
- truthound_dashboard/static/assets/xychartDiagram-PRI3JC2R-CS7fydZZ.js +0 -7
- truthound_dashboard-1.4.3.dist-info/METADATA +0 -505
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.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
|
+
)
|