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,814 @@
|
|
|
1
|
+
"""Adapters for integrating truthound checkpoint API with dashboard abstractions.
|
|
2
|
+
|
|
3
|
+
Provides adapter classes that bridge between truthound's checkpoint module
|
|
4
|
+
and the dashboard's abstraction layer, enabling loose coupling while
|
|
5
|
+
supporting the full truthound checkpoint functionality.
|
|
6
|
+
|
|
7
|
+
Key features supported:
|
|
8
|
+
- Checkpoint execution (sync and async)
|
|
9
|
+
- Action orchestration (12 action types)
|
|
10
|
+
- CI/CD reporter integration (12 platforms)
|
|
11
|
+
- Trigger management (cron, schedule, event, file watch)
|
|
12
|
+
- Transaction management (saga pattern, idempotency)
|
|
13
|
+
- Advanced notifications (routing, deduplication, throttling, escalation)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from typing import TYPE_CHECKING, Any
|
|
20
|
+
|
|
21
|
+
from truthound_dashboard.core.interfaces.actions import (
|
|
22
|
+
ActionContext,
|
|
23
|
+
ActionResult,
|
|
24
|
+
ActionStatus,
|
|
25
|
+
BaseAction,
|
|
26
|
+
)
|
|
27
|
+
from truthound_dashboard.core.interfaces.checkpoint import (
|
|
28
|
+
CheckpointConfig,
|
|
29
|
+
CheckpointResult,
|
|
30
|
+
CheckpointStatus,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TruthoundCheckpointAdapter:
|
|
40
|
+
"""Adapter for truthound checkpoint API integration.
|
|
41
|
+
|
|
42
|
+
This adapter provides a bridge between truthound's native checkpoint
|
|
43
|
+
module and the dashboard's abstraction layer. It allows using truthound
|
|
44
|
+
checkpoints while maintaining the dashboard's loose coupling.
|
|
45
|
+
|
|
46
|
+
Features:
|
|
47
|
+
- Convert truthound checkpoint results to dashboard format
|
|
48
|
+
- Map truthound actions to dashboard actions
|
|
49
|
+
- Support truthound's routing and trigger systems
|
|
50
|
+
- Maintain backward compatibility with existing dashboard code
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
from truthound.checkpoint import Checkpoint as TruthoundCheckpoint
|
|
54
|
+
|
|
55
|
+
# Create a truthound checkpoint
|
|
56
|
+
th_checkpoint = TruthoundCheckpoint(
|
|
57
|
+
datasource=source,
|
|
58
|
+
actions=[SlackAction(webhook_url="...")],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Wrap with adapter
|
|
62
|
+
adapter = TruthoundCheckpointAdapter(th_checkpoint)
|
|
63
|
+
|
|
64
|
+
# Use through dashboard interfaces
|
|
65
|
+
result = await adapter.run()
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, truthound_checkpoint: Any) -> None:
|
|
69
|
+
"""Initialize adapter.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
truthound_checkpoint: Native truthound Checkpoint instance.
|
|
73
|
+
"""
|
|
74
|
+
self._checkpoint = truthound_checkpoint
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def name(self) -> str:
|
|
78
|
+
"""Get checkpoint name."""
|
|
79
|
+
return getattr(self._checkpoint, "name", "unnamed")
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def config(self) -> CheckpointConfig:
|
|
83
|
+
"""Get checkpoint configuration as dashboard format."""
|
|
84
|
+
cp = self._checkpoint
|
|
85
|
+
|
|
86
|
+
return CheckpointConfig(
|
|
87
|
+
name=getattr(cp, "name", ""),
|
|
88
|
+
description=getattr(cp, "description", ""),
|
|
89
|
+
source_name=getattr(cp, "datasource_name", ""),
|
|
90
|
+
validators=list(getattr(cp, "validators", [])),
|
|
91
|
+
validator_config=dict(getattr(cp, "validator_config", {})),
|
|
92
|
+
tags=dict(getattr(cp, "tags", {})),
|
|
93
|
+
enabled=getattr(cp, "enabled", True),
|
|
94
|
+
timeout_seconds=getattr(cp, "timeout", 300),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
async def run(
|
|
98
|
+
self,
|
|
99
|
+
trigger_context: dict[str, Any] | None = None,
|
|
100
|
+
) -> CheckpointResult:
|
|
101
|
+
"""Run the truthound checkpoint and convert result.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
trigger_context: Optional trigger context.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Dashboard-format checkpoint result.
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
# Run truthound checkpoint
|
|
111
|
+
th_result = await self._run_truthound()
|
|
112
|
+
|
|
113
|
+
# Convert to dashboard format
|
|
114
|
+
return self._convert_result(th_result, trigger_context)
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error(f"Truthound checkpoint failed: {str(e)}")
|
|
118
|
+
return CheckpointResult(
|
|
119
|
+
checkpoint_name=self.name,
|
|
120
|
+
run_id=str(getattr(self._checkpoint, "run_id", "")),
|
|
121
|
+
status=CheckpointStatus.ERROR,
|
|
122
|
+
error_message=str(e),
|
|
123
|
+
trigger_context=trigger_context or {},
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
async def _run_truthound(self) -> Any:
|
|
127
|
+
"""Run the native truthound checkpoint.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Truthound checkpoint result.
|
|
131
|
+
"""
|
|
132
|
+
import asyncio
|
|
133
|
+
|
|
134
|
+
# Check if checkpoint has async run method
|
|
135
|
+
if hasattr(self._checkpoint, "run_async"):
|
|
136
|
+
return await self._checkpoint.run_async()
|
|
137
|
+
elif hasattr(self._checkpoint, "run"):
|
|
138
|
+
# Run sync method in executor
|
|
139
|
+
loop = asyncio.get_event_loop()
|
|
140
|
+
return await loop.run_in_executor(None, self._checkpoint.run)
|
|
141
|
+
else:
|
|
142
|
+
raise AttributeError("Checkpoint has no run method")
|
|
143
|
+
|
|
144
|
+
def _convert_result(
|
|
145
|
+
self,
|
|
146
|
+
th_result: Any,
|
|
147
|
+
trigger_context: dict[str, Any] | None,
|
|
148
|
+
) -> CheckpointResult:
|
|
149
|
+
"""Convert truthound result to dashboard format.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
th_result: Truthound checkpoint result.
|
|
153
|
+
trigger_context: Trigger context.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Dashboard-format result.
|
|
157
|
+
"""
|
|
158
|
+
# Extract status
|
|
159
|
+
status_str = getattr(th_result, "status", "error")
|
|
160
|
+
status_map = {
|
|
161
|
+
"success": CheckpointStatus.SUCCESS,
|
|
162
|
+
"failure": CheckpointStatus.FAILURE,
|
|
163
|
+
"error": CheckpointStatus.ERROR,
|
|
164
|
+
"warning": CheckpointStatus.WARNING,
|
|
165
|
+
"pending": CheckpointStatus.PENDING,
|
|
166
|
+
"running": CheckpointStatus.RUNNING,
|
|
167
|
+
}
|
|
168
|
+
status = status_map.get(status_str.lower(), CheckpointStatus.ERROR)
|
|
169
|
+
|
|
170
|
+
# Extract validation result
|
|
171
|
+
validation_result = getattr(th_result, "validation_result", None)
|
|
172
|
+
|
|
173
|
+
# Build checkpoint result
|
|
174
|
+
result = CheckpointResult(
|
|
175
|
+
checkpoint_name=self.name,
|
|
176
|
+
run_id=str(getattr(th_result, "run_id", "")),
|
|
177
|
+
status=status,
|
|
178
|
+
started_at=getattr(th_result, "started_at", None),
|
|
179
|
+
completed_at=getattr(th_result, "completed_at", None),
|
|
180
|
+
duration_ms=getattr(th_result, "duration_ms", 0.0),
|
|
181
|
+
source_name=getattr(th_result, "source_name", ""),
|
|
182
|
+
trigger_context=trigger_context or {},
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Extract issue counts from validation result
|
|
186
|
+
if validation_result:
|
|
187
|
+
result.row_count = getattr(validation_result, "row_count", 0)
|
|
188
|
+
result.column_count = getattr(validation_result, "column_count", 0)
|
|
189
|
+
result.issue_count = getattr(validation_result, "total_issues", 0)
|
|
190
|
+
result.critical_count = getattr(validation_result, "critical_issues", 0)
|
|
191
|
+
result.high_count = getattr(validation_result, "high_issues", 0)
|
|
192
|
+
result.medium_count = getattr(validation_result, "medium_issues", 0)
|
|
193
|
+
result.low_count = getattr(validation_result, "low_issues", 0)
|
|
194
|
+
result.has_critical = getattr(validation_result, "has_critical", False)
|
|
195
|
+
result.has_high = getattr(validation_result, "has_high", False)
|
|
196
|
+
result.issues = getattr(validation_result, "issues", [])
|
|
197
|
+
|
|
198
|
+
# Convert action results
|
|
199
|
+
th_action_results = getattr(th_result, "action_results", [])
|
|
200
|
+
result.action_results = [
|
|
201
|
+
self._convert_action_result(ar) for ar in th_action_results
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
return result
|
|
205
|
+
|
|
206
|
+
def _convert_action_result(self, th_result: Any) -> ActionResult:
|
|
207
|
+
"""Convert truthound action result to dashboard format.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
th_result: Truthound action result.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Dashboard-format action result.
|
|
214
|
+
"""
|
|
215
|
+
status_str = getattr(th_result, "status", "failure")
|
|
216
|
+
status_map = {
|
|
217
|
+
"success": ActionStatus.SUCCESS,
|
|
218
|
+
"failure": ActionStatus.FAILURE,
|
|
219
|
+
"skipped": ActionStatus.SKIPPED,
|
|
220
|
+
"pending": ActionStatus.PENDING,
|
|
221
|
+
"running": ActionStatus.RUNNING,
|
|
222
|
+
}
|
|
223
|
+
status = status_map.get(status_str.lower(), ActionStatus.FAILURE)
|
|
224
|
+
|
|
225
|
+
return ActionResult(
|
|
226
|
+
action_name=getattr(th_result, "action_name", "unknown"),
|
|
227
|
+
action_type=getattr(th_result, "action_type", "unknown"),
|
|
228
|
+
status=status,
|
|
229
|
+
message=getattr(th_result, "message", ""),
|
|
230
|
+
started_at=getattr(th_result, "started_at", None),
|
|
231
|
+
completed_at=getattr(th_result, "completed_at", None),
|
|
232
|
+
duration_ms=getattr(th_result, "duration_ms", 0.0),
|
|
233
|
+
details=getattr(th_result, "details", {}),
|
|
234
|
+
error=getattr(th_result, "error", None),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class TruthoundActionAdapter(BaseAction):
|
|
239
|
+
"""Adapter for wrapping truthound actions as dashboard actions.
|
|
240
|
+
|
|
241
|
+
Allows using truthound's native action implementations within
|
|
242
|
+
the dashboard's action framework.
|
|
243
|
+
|
|
244
|
+
Example:
|
|
245
|
+
from truthound.checkpoint.actions import SlackAction
|
|
246
|
+
|
|
247
|
+
th_action = SlackAction(webhook_url="...")
|
|
248
|
+
dashboard_action = TruthoundActionAdapter(th_action)
|
|
249
|
+
|
|
250
|
+
# Use in checkpoint
|
|
251
|
+
checkpoint.add_action(dashboard_action)
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
def __init__(self, truthound_action: Any) -> None:
|
|
255
|
+
"""Initialize adapter.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
truthound_action: Native truthound action instance.
|
|
259
|
+
"""
|
|
260
|
+
from truthound_dashboard.core.interfaces.actions import ActionConfig
|
|
261
|
+
|
|
262
|
+
super().__init__(ActionConfig())
|
|
263
|
+
self._action = truthound_action
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def name(self) -> str:
|
|
267
|
+
"""Get action name."""
|
|
268
|
+
return getattr(self._action, "name", "truthound_action")
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def action_type(self) -> str:
|
|
272
|
+
"""Get action type."""
|
|
273
|
+
return getattr(self._action, "action_type", "external")
|
|
274
|
+
|
|
275
|
+
def _do_execute(self, context: ActionContext) -> ActionResult:
|
|
276
|
+
"""Execute the truthound action.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
context: Action context.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Action result.
|
|
283
|
+
"""
|
|
284
|
+
try:
|
|
285
|
+
# Build truthound context if needed
|
|
286
|
+
th_context = self._build_truthound_context(context)
|
|
287
|
+
|
|
288
|
+
# Execute truthound action
|
|
289
|
+
if hasattr(self._action, "execute"):
|
|
290
|
+
th_result = self._action.execute(th_context)
|
|
291
|
+
elif hasattr(self._action, "__call__"):
|
|
292
|
+
th_result = self._action(th_context)
|
|
293
|
+
else:
|
|
294
|
+
raise AttributeError("Action has no execute method")
|
|
295
|
+
|
|
296
|
+
# Convert result
|
|
297
|
+
return self._convert_result(th_result)
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
return ActionResult(
|
|
301
|
+
action_name=self.name,
|
|
302
|
+
action_type=self.action_type,
|
|
303
|
+
status=ActionStatus.FAILURE,
|
|
304
|
+
message=f"Truthound action failed: {str(e)}",
|
|
305
|
+
error=str(e),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def _build_truthound_context(self, context: ActionContext) -> Any:
|
|
309
|
+
"""Build truthound action context from dashboard context.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
context: Dashboard action context.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Truthound-compatible context.
|
|
316
|
+
"""
|
|
317
|
+
# Try to import truthound context class
|
|
318
|
+
try:
|
|
319
|
+
from truthound.checkpoint.actions import ActionContext as TruthoundActionContext
|
|
320
|
+
return TruthoundActionContext(
|
|
321
|
+
checkpoint_result=context.checkpoint_result,
|
|
322
|
+
run_id=context.run_id,
|
|
323
|
+
checkpoint_name=context.checkpoint_name,
|
|
324
|
+
tags=context.tags,
|
|
325
|
+
metadata=context.metadata,
|
|
326
|
+
)
|
|
327
|
+
except ImportError:
|
|
328
|
+
# Return a dict-like context
|
|
329
|
+
return {
|
|
330
|
+
"checkpoint_result": context.checkpoint_result,
|
|
331
|
+
"run_id": context.run_id,
|
|
332
|
+
"checkpoint_name": context.checkpoint_name,
|
|
333
|
+
"tags": context.tags,
|
|
334
|
+
"metadata": context.metadata,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
def _convert_result(self, th_result: Any) -> ActionResult:
|
|
338
|
+
"""Convert truthound action result.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
th_result: Truthound result.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
Dashboard action result.
|
|
345
|
+
"""
|
|
346
|
+
if th_result is None:
|
|
347
|
+
return ActionResult(
|
|
348
|
+
action_name=self.name,
|
|
349
|
+
action_type=self.action_type,
|
|
350
|
+
status=ActionStatus.SUCCESS,
|
|
351
|
+
message="Action completed",
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
status_str = getattr(th_result, "status", "success")
|
|
355
|
+
status_map = {
|
|
356
|
+
"success": ActionStatus.SUCCESS,
|
|
357
|
+
"failure": ActionStatus.FAILURE,
|
|
358
|
+
"skipped": ActionStatus.SKIPPED,
|
|
359
|
+
}
|
|
360
|
+
status = status_map.get(status_str.lower(), ActionStatus.SUCCESS)
|
|
361
|
+
|
|
362
|
+
return ActionResult(
|
|
363
|
+
action_name=self.name,
|
|
364
|
+
action_type=self.action_type,
|
|
365
|
+
status=status,
|
|
366
|
+
message=getattr(th_result, "message", ""),
|
|
367
|
+
details=getattr(th_result, "details", {}),
|
|
368
|
+
error=getattr(th_result, "error", None),
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def create_checkpoint_from_truthound(th_checkpoint: Any) -> TruthoundCheckpointAdapter:
|
|
373
|
+
"""Factory function to create a dashboard checkpoint from truthound checkpoint.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
th_checkpoint: Truthound checkpoint instance.
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
Wrapped checkpoint adapter.
|
|
380
|
+
"""
|
|
381
|
+
return TruthoundCheckpointAdapter(th_checkpoint)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def wrap_truthound_action(th_action: Any) -> TruthoundActionAdapter:
|
|
385
|
+
"""Factory function to wrap a truthound action for dashboard use.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
th_action: Truthound action instance.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
Wrapped action adapter.
|
|
392
|
+
"""
|
|
393
|
+
return TruthoundActionAdapter(th_action)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
# =============================================================================
|
|
397
|
+
# Factory Functions for Truthound Checkpoints
|
|
398
|
+
# =============================================================================
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def create_truthound_checkpoint(
|
|
402
|
+
name: str,
|
|
403
|
+
data_source: Any,
|
|
404
|
+
validators: list[str] | None = None,
|
|
405
|
+
actions: list[Any] | None = None,
|
|
406
|
+
triggers: list[Any] | None = None,
|
|
407
|
+
**config_options: Any,
|
|
408
|
+
) -> TruthoundCheckpointAdapter | None:
|
|
409
|
+
"""Create a truthound checkpoint with the new 2.x API.
|
|
410
|
+
|
|
411
|
+
This factory function creates a checkpoint using truthound's native
|
|
412
|
+
checkpoint API and wraps it with the dashboard adapter.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
name: Checkpoint name.
|
|
416
|
+
data_source: Data source (file path, DataSource, or config dict).
|
|
417
|
+
validators: List of validator names to run.
|
|
418
|
+
actions: List of action instances.
|
|
419
|
+
triggers: List of trigger instances.
|
|
420
|
+
**config_options: Additional checkpoint configuration options.
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
TruthoundCheckpointAdapter or None if truthound unavailable.
|
|
424
|
+
|
|
425
|
+
Example:
|
|
426
|
+
from truthound.checkpoint.actions import SlackNotification
|
|
427
|
+
|
|
428
|
+
checkpoint = create_truthound_checkpoint(
|
|
429
|
+
name="daily_validation",
|
|
430
|
+
data_source="data.csv",
|
|
431
|
+
validators=["null", "duplicate", "range"],
|
|
432
|
+
actions=[
|
|
433
|
+
SlackNotification(
|
|
434
|
+
webhook_url="https://hooks.slack.com/...",
|
|
435
|
+
notify_on="failure",
|
|
436
|
+
),
|
|
437
|
+
],
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
if checkpoint:
|
|
441
|
+
result = await checkpoint.run()
|
|
442
|
+
"""
|
|
443
|
+
try:
|
|
444
|
+
from truthound.checkpoint import Checkpoint
|
|
445
|
+
|
|
446
|
+
# Create truthound checkpoint
|
|
447
|
+
th_checkpoint = Checkpoint(
|
|
448
|
+
name=name,
|
|
449
|
+
data_source=data_source,
|
|
450
|
+
validators=validators or [],
|
|
451
|
+
actions=actions or [],
|
|
452
|
+
**config_options,
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
# Add triggers
|
|
456
|
+
if triggers:
|
|
457
|
+
for trigger in triggers:
|
|
458
|
+
th_checkpoint.add_trigger(trigger)
|
|
459
|
+
|
|
460
|
+
# Wrap with adapter
|
|
461
|
+
return TruthoundCheckpointAdapter(th_checkpoint)
|
|
462
|
+
|
|
463
|
+
except ImportError:
|
|
464
|
+
logger.warning("truthound.checkpoint not available")
|
|
465
|
+
return None
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def create_checkpoint_from_config(
|
|
469
|
+
config: dict[str, Any] | "CheckpointConfig",
|
|
470
|
+
) -> TruthoundCheckpointAdapter | None:
|
|
471
|
+
"""Create a truthound checkpoint from configuration.
|
|
472
|
+
|
|
473
|
+
This factory function creates a checkpoint from a configuration
|
|
474
|
+
dictionary or CheckpointConfig object.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
config: Configuration dict or CheckpointConfig.
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
TruthoundCheckpointAdapter or None if truthound unavailable.
|
|
481
|
+
|
|
482
|
+
Example:
|
|
483
|
+
config = {
|
|
484
|
+
"name": "daily_validation",
|
|
485
|
+
"data_source": "data.csv",
|
|
486
|
+
"validators": ["null", "duplicate"],
|
|
487
|
+
"actions": [
|
|
488
|
+
{
|
|
489
|
+
"type": "slack",
|
|
490
|
+
"webhook_url": "https://hooks.slack.com/...",
|
|
491
|
+
"notify_on": "failure",
|
|
492
|
+
}
|
|
493
|
+
],
|
|
494
|
+
}
|
|
495
|
+
checkpoint = create_checkpoint_from_config(config)
|
|
496
|
+
"""
|
|
497
|
+
try:
|
|
498
|
+
from truthound.checkpoint import Checkpoint, CheckpointConfig as TruthoundConfig
|
|
499
|
+
|
|
500
|
+
# Convert to dict if needed
|
|
501
|
+
if isinstance(config, CheckpointConfig):
|
|
502
|
+
config_dict = config.to_dict()
|
|
503
|
+
else:
|
|
504
|
+
config_dict = config
|
|
505
|
+
|
|
506
|
+
# Build truthound config
|
|
507
|
+
th_config = TruthoundConfig(
|
|
508
|
+
name=config_dict.get("name", ""),
|
|
509
|
+
data_source=config_dict.get("data_source") or config_dict.get("source_name"),
|
|
510
|
+
validators=config_dict.get("validators", []),
|
|
511
|
+
validator_config=config_dict.get("validator_config", {}),
|
|
512
|
+
schema=config_dict.get("schema_path"),
|
|
513
|
+
auto_schema=config_dict.get("auto_schema", False),
|
|
514
|
+
tags=config_dict.get("tags", {}),
|
|
515
|
+
timeout_seconds=config_dict.get("timeout_seconds", 300),
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
# Create checkpoint
|
|
519
|
+
th_checkpoint = Checkpoint(config=th_config)
|
|
520
|
+
|
|
521
|
+
# Create actions from config
|
|
522
|
+
actions_config = config_dict.get("actions", [])
|
|
523
|
+
for action_config in actions_config:
|
|
524
|
+
action = _create_action_from_config(action_config)
|
|
525
|
+
if action:
|
|
526
|
+
th_checkpoint.add_action(action)
|
|
527
|
+
|
|
528
|
+
# Create triggers from config
|
|
529
|
+
triggers_config = config_dict.get("triggers", [])
|
|
530
|
+
for trigger_config in triggers_config:
|
|
531
|
+
trigger = _create_trigger_from_config(trigger_config)
|
|
532
|
+
if trigger:
|
|
533
|
+
th_checkpoint.add_trigger(trigger)
|
|
534
|
+
|
|
535
|
+
return TruthoundCheckpointAdapter(th_checkpoint)
|
|
536
|
+
|
|
537
|
+
except ImportError:
|
|
538
|
+
logger.warning("truthound.checkpoint not available")
|
|
539
|
+
return None
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _create_action_from_config(config: dict[str, Any]) -> Any:
|
|
543
|
+
"""Create an action from configuration dict.
|
|
544
|
+
|
|
545
|
+
Supports all 12 action types from truthound.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
config: Action configuration.
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
Action instance or None.
|
|
552
|
+
"""
|
|
553
|
+
action_type = config.get("type", "").lower()
|
|
554
|
+
|
|
555
|
+
try:
|
|
556
|
+
if action_type == "store_result":
|
|
557
|
+
from truthound.checkpoint.actions import StoreValidationResult
|
|
558
|
+
|
|
559
|
+
return StoreValidationResult(
|
|
560
|
+
store_path=config.get("store_path", "./results"),
|
|
561
|
+
store_type=config.get("store_type", "file"),
|
|
562
|
+
format=config.get("format", "json"),
|
|
563
|
+
partition_by=config.get("partition_by"),
|
|
564
|
+
retention_days=config.get("retention_days"),
|
|
565
|
+
compress=config.get("compress", False),
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
elif action_type == "update_docs":
|
|
569
|
+
from truthound.checkpoint.actions import UpdateDataDocs
|
|
570
|
+
|
|
571
|
+
return UpdateDataDocs(
|
|
572
|
+
site_path=config.get("site_path", "./docs"),
|
|
573
|
+
format=config.get("format", "html"),
|
|
574
|
+
include_history=config.get("include_history", True),
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
elif action_type == "slack":
|
|
578
|
+
from truthound.checkpoint.actions import SlackNotification
|
|
579
|
+
|
|
580
|
+
return SlackNotification(
|
|
581
|
+
webhook_url=config["webhook_url"],
|
|
582
|
+
channel=config.get("channel"),
|
|
583
|
+
notify_on=config.get("notify_on", "failure"),
|
|
584
|
+
include_details=config.get("include_details", True),
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
elif action_type == "email":
|
|
588
|
+
from truthound.checkpoint.actions import EmailNotification
|
|
589
|
+
|
|
590
|
+
return EmailNotification(
|
|
591
|
+
to=config.get("to", []),
|
|
592
|
+
subject=config.get("subject"),
|
|
593
|
+
notify_on=config.get("notify_on", "failure"),
|
|
594
|
+
smtp_host=config.get("smtp_host"),
|
|
595
|
+
smtp_port=config.get("smtp_port"),
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
elif action_type == "webhook":
|
|
599
|
+
from truthound.checkpoint.actions import WebhookAction
|
|
600
|
+
|
|
601
|
+
return WebhookAction(
|
|
602
|
+
url=config["url"],
|
|
603
|
+
method=config.get("method", "POST"),
|
|
604
|
+
auth_type=config.get("auth_type", "none"),
|
|
605
|
+
auth_credentials=config.get("auth_credentials"),
|
|
606
|
+
headers=config.get("headers"),
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
elif action_type == "pagerduty":
|
|
610
|
+
from truthound.checkpoint.actions import PagerDutyAction
|
|
611
|
+
|
|
612
|
+
return PagerDutyAction(
|
|
613
|
+
service_key=config["service_key"],
|
|
614
|
+
notify_on=config.get("notify_on", "failure"),
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
elif action_type == "github":
|
|
618
|
+
from truthound.checkpoint.actions import GitHubAction
|
|
619
|
+
|
|
620
|
+
return GitHubAction()
|
|
621
|
+
|
|
622
|
+
elif action_type == "teams":
|
|
623
|
+
from truthound.checkpoint.actions import TeamsNotification
|
|
624
|
+
|
|
625
|
+
return TeamsNotification(
|
|
626
|
+
webhook_url=config["webhook_url"],
|
|
627
|
+
notify_on=config.get("notify_on", "failure"),
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
elif action_type == "opsgenie":
|
|
631
|
+
from truthound.checkpoint.actions import OpsGenieAction
|
|
632
|
+
|
|
633
|
+
return OpsGenieAction(
|
|
634
|
+
api_key=config["api_key"],
|
|
635
|
+
notify_on=config.get("notify_on", "failure"),
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
elif action_type == "discord":
|
|
639
|
+
from truthound.checkpoint.actions import DiscordNotification
|
|
640
|
+
|
|
641
|
+
return DiscordNotification(
|
|
642
|
+
webhook_url=config["webhook_url"],
|
|
643
|
+
notify_on=config.get("notify_on", "failure"),
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
elif action_type == "telegram":
|
|
647
|
+
from truthound.checkpoint.actions import TelegramNotification
|
|
648
|
+
|
|
649
|
+
return TelegramNotification(
|
|
650
|
+
bot_token=config["bot_token"],
|
|
651
|
+
chat_id=config["chat_id"],
|
|
652
|
+
notify_on=config.get("notify_on", "failure"),
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
elif action_type == "custom":
|
|
656
|
+
from truthound.checkpoint.actions import CustomAction
|
|
657
|
+
|
|
658
|
+
return CustomAction(
|
|
659
|
+
callback=config.get("callback"),
|
|
660
|
+
shell_command=config.get("shell_command"),
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
else:
|
|
664
|
+
logger.warning(f"Unknown action type: {action_type}")
|
|
665
|
+
return None
|
|
666
|
+
|
|
667
|
+
except (ImportError, KeyError) as e:
|
|
668
|
+
logger.warning(f"Failed to create action {action_type}: {e}")
|
|
669
|
+
return None
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def _create_trigger_from_config(config: dict[str, Any]) -> Any:
|
|
673
|
+
"""Create a trigger from configuration dict.
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
config: Trigger configuration.
|
|
677
|
+
|
|
678
|
+
Returns:
|
|
679
|
+
Trigger instance or None.
|
|
680
|
+
"""
|
|
681
|
+
trigger_type = config.get("type", "").lower()
|
|
682
|
+
|
|
683
|
+
try:
|
|
684
|
+
if trigger_type == "schedule":
|
|
685
|
+
from truthound.checkpoint.triggers import ScheduleTrigger
|
|
686
|
+
|
|
687
|
+
return ScheduleTrigger(
|
|
688
|
+
interval_hours=config.get("interval_hours"),
|
|
689
|
+
interval_minutes=config.get("interval_minutes"),
|
|
690
|
+
run_on_weekdays=config.get("run_on_weekdays"),
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
elif trigger_type == "cron":
|
|
694
|
+
from truthound.checkpoint.triggers import CronTrigger
|
|
695
|
+
|
|
696
|
+
return CronTrigger(expression=config["expression"])
|
|
697
|
+
|
|
698
|
+
elif trigger_type == "event":
|
|
699
|
+
from truthound.checkpoint.triggers import EventTrigger
|
|
700
|
+
|
|
701
|
+
return EventTrigger(
|
|
702
|
+
event_type=config["event_type"],
|
|
703
|
+
event_filter=config.get("event_filter"),
|
|
704
|
+
debounce_seconds=config.get("debounce_seconds"),
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
elif trigger_type == "file_watch":
|
|
708
|
+
from truthound.checkpoint.triggers import FileWatchTrigger
|
|
709
|
+
|
|
710
|
+
return FileWatchTrigger(
|
|
711
|
+
paths=config.get("paths", []),
|
|
712
|
+
patterns=config.get("patterns", ["*"]),
|
|
713
|
+
recursive=config.get("recursive", False),
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
else:
|
|
717
|
+
logger.warning(f"Unknown trigger type: {trigger_type}")
|
|
718
|
+
return None
|
|
719
|
+
|
|
720
|
+
except (ImportError, KeyError) as e:
|
|
721
|
+
logger.warning(f"Failed to create trigger {trigger_type}: {e}")
|
|
722
|
+
return None
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
# =============================================================================
|
|
726
|
+
# CI/CD Integration Helpers
|
|
727
|
+
# =============================================================================
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def get_ci_reporter_for_checkpoint(checkpoint_result: CheckpointResult) -> Any:
|
|
731
|
+
"""Get a CI reporter for the checkpoint result.
|
|
732
|
+
|
|
733
|
+
Auto-detects the CI environment and returns an appropriate reporter.
|
|
734
|
+
|
|
735
|
+
Args:
|
|
736
|
+
checkpoint_result: Checkpoint result to report.
|
|
737
|
+
|
|
738
|
+
Returns:
|
|
739
|
+
CI reporter or None if not in CI environment.
|
|
740
|
+
"""
|
|
741
|
+
from truthound_dashboard.core.reporters import (
|
|
742
|
+
create_ci_reporter,
|
|
743
|
+
is_ci_environment,
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
if not is_ci_environment():
|
|
747
|
+
return None
|
|
748
|
+
|
|
749
|
+
return create_ci_reporter()
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
async def report_checkpoint_to_ci(checkpoint_result: CheckpointResult) -> bool:
|
|
753
|
+
"""Report checkpoint result to CI platform.
|
|
754
|
+
|
|
755
|
+
This function detects the CI environment and reports the result
|
|
756
|
+
appropriately (e.g., GitHub step summary, annotations, etc.).
|
|
757
|
+
|
|
758
|
+
Args:
|
|
759
|
+
checkpoint_result: Result to report.
|
|
760
|
+
|
|
761
|
+
Returns:
|
|
762
|
+
True if reported successfully.
|
|
763
|
+
"""
|
|
764
|
+
from truthound_dashboard.core.reporters import (
|
|
765
|
+
create_ci_reporter,
|
|
766
|
+
is_ci_environment,
|
|
767
|
+
get_detected_ci_platform,
|
|
768
|
+
)
|
|
769
|
+
from truthound_dashboard.core.reporters.interfaces import ReportData
|
|
770
|
+
|
|
771
|
+
if not is_ci_environment():
|
|
772
|
+
return False
|
|
773
|
+
|
|
774
|
+
reporter = create_ci_reporter()
|
|
775
|
+
if not reporter:
|
|
776
|
+
return False
|
|
777
|
+
|
|
778
|
+
try:
|
|
779
|
+
# Convert checkpoint result to report data
|
|
780
|
+
report_data = ReportData(
|
|
781
|
+
validation_id=checkpoint_result.run_id,
|
|
782
|
+
source_id=checkpoint_result.source_name,
|
|
783
|
+
source_name=checkpoint_result.source_name,
|
|
784
|
+
status=checkpoint_result.status.value,
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
# Generate and output report
|
|
788
|
+
output = await reporter.generate(report_data)
|
|
789
|
+
|
|
790
|
+
# Print to stdout for CI
|
|
791
|
+
if isinstance(output.content, str):
|
|
792
|
+
print(output.content)
|
|
793
|
+
else:
|
|
794
|
+
print(output.content.decode())
|
|
795
|
+
|
|
796
|
+
return True
|
|
797
|
+
|
|
798
|
+
except Exception as e:
|
|
799
|
+
logger.error(f"Failed to report to CI: {e}")
|
|
800
|
+
return False
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def is_truthound_checkpoint_available() -> bool:
|
|
804
|
+
"""Check if truthound checkpoint module is available.
|
|
805
|
+
|
|
806
|
+
Returns:
|
|
807
|
+
True if truthound.checkpoint can be imported.
|
|
808
|
+
"""
|
|
809
|
+
try:
|
|
810
|
+
from truthound.checkpoint import Checkpoint # noqa: F401
|
|
811
|
+
|
|
812
|
+
return True
|
|
813
|
+
except ImportError:
|
|
814
|
+
return False
|