truthound-dashboard 1.3.1__py3-none-any.whl → 1.4.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 (169) hide show
  1. truthound_dashboard/api/alerts.py +258 -0
  2. truthound_dashboard/api/anomaly.py +1302 -0
  3. truthound_dashboard/api/cross_alerts.py +352 -0
  4. truthound_dashboard/api/deps.py +143 -0
  5. truthound_dashboard/api/drift_monitor.py +540 -0
  6. truthound_dashboard/api/lineage.py +1151 -0
  7. truthound_dashboard/api/maintenance.py +363 -0
  8. truthound_dashboard/api/middleware.py +373 -1
  9. truthound_dashboard/api/model_monitoring.py +805 -0
  10. truthound_dashboard/api/notifications_advanced.py +2452 -0
  11. truthound_dashboard/api/plugins.py +2096 -0
  12. truthound_dashboard/api/profile.py +211 -14
  13. truthound_dashboard/api/reports.py +853 -0
  14. truthound_dashboard/api/router.py +147 -0
  15. truthound_dashboard/api/rule_suggestions.py +310 -0
  16. truthound_dashboard/api/schema_evolution.py +231 -0
  17. truthound_dashboard/api/sources.py +47 -3
  18. truthound_dashboard/api/triggers.py +190 -0
  19. truthound_dashboard/api/validations.py +13 -0
  20. truthound_dashboard/api/validators.py +333 -4
  21. truthound_dashboard/api/versioning.py +309 -0
  22. truthound_dashboard/api/websocket.py +301 -0
  23. truthound_dashboard/core/__init__.py +27 -0
  24. truthound_dashboard/core/anomaly.py +1395 -0
  25. truthound_dashboard/core/anomaly_explainer.py +633 -0
  26. truthound_dashboard/core/cache.py +206 -0
  27. truthound_dashboard/core/cached_services.py +422 -0
  28. truthound_dashboard/core/charts.py +352 -0
  29. truthound_dashboard/core/connections.py +1069 -42
  30. truthound_dashboard/core/cross_alerts.py +837 -0
  31. truthound_dashboard/core/drift_monitor.py +1477 -0
  32. truthound_dashboard/core/drift_sampling.py +669 -0
  33. truthound_dashboard/core/i18n/__init__.py +42 -0
  34. truthound_dashboard/core/i18n/detector.py +173 -0
  35. truthound_dashboard/core/i18n/messages.py +564 -0
  36. truthound_dashboard/core/lineage.py +971 -0
  37. truthound_dashboard/core/maintenance.py +443 -5
  38. truthound_dashboard/core/model_monitoring.py +1043 -0
  39. truthound_dashboard/core/notifications/channels.py +1020 -1
  40. truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
  41. truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
  42. truthound_dashboard/core/notifications/deduplication/service.py +400 -0
  43. truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
  44. truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
  45. truthound_dashboard/core/notifications/dispatcher.py +43 -0
  46. truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
  47. truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
  48. truthound_dashboard/core/notifications/escalation/engine.py +429 -0
  49. truthound_dashboard/core/notifications/escalation/models.py +336 -0
  50. truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
  51. truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
  52. truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
  53. truthound_dashboard/core/notifications/events.py +49 -0
  54. truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
  55. truthound_dashboard/core/notifications/metrics/base.py +528 -0
  56. truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
  57. truthound_dashboard/core/notifications/routing/__init__.py +169 -0
  58. truthound_dashboard/core/notifications/routing/combinators.py +184 -0
  59. truthound_dashboard/core/notifications/routing/config.py +375 -0
  60. truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
  61. truthound_dashboard/core/notifications/routing/engine.py +382 -0
  62. truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
  63. truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
  64. truthound_dashboard/core/notifications/routing/rules.py +625 -0
  65. truthound_dashboard/core/notifications/routing/validator.py +678 -0
  66. truthound_dashboard/core/notifications/service.py +2 -0
  67. truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
  68. truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
  69. truthound_dashboard/core/notifications/throttling/builder.py +311 -0
  70. truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
  71. truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
  72. truthound_dashboard/core/openlineage.py +1028 -0
  73. truthound_dashboard/core/plugins/__init__.py +39 -0
  74. truthound_dashboard/core/plugins/docs/__init__.py +39 -0
  75. truthound_dashboard/core/plugins/docs/extractor.py +703 -0
  76. truthound_dashboard/core/plugins/docs/renderers.py +804 -0
  77. truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
  78. truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
  79. truthound_dashboard/core/plugins/hooks/manager.py +403 -0
  80. truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
  81. truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
  82. truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
  83. truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
  84. truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
  85. truthound_dashboard/core/plugins/loader.py +504 -0
  86. truthound_dashboard/core/plugins/registry.py +810 -0
  87. truthound_dashboard/core/plugins/reporter_executor.py +588 -0
  88. truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
  89. truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
  90. truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
  91. truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
  92. truthound_dashboard/core/plugins/sandbox.py +617 -0
  93. truthound_dashboard/core/plugins/security/__init__.py +68 -0
  94. truthound_dashboard/core/plugins/security/analyzer.py +535 -0
  95. truthound_dashboard/core/plugins/security/policies.py +311 -0
  96. truthound_dashboard/core/plugins/security/protocols.py +296 -0
  97. truthound_dashboard/core/plugins/security/signing.py +842 -0
  98. truthound_dashboard/core/plugins/security.py +446 -0
  99. truthound_dashboard/core/plugins/validator_executor.py +401 -0
  100. truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
  101. truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
  102. truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
  103. truthound_dashboard/core/plugins/versioning/semver.py +266 -0
  104. truthound_dashboard/core/profile_comparison.py +601 -0
  105. truthound_dashboard/core/report_history.py +570 -0
  106. truthound_dashboard/core/reporters/__init__.py +57 -0
  107. truthound_dashboard/core/reporters/base.py +296 -0
  108. truthound_dashboard/core/reporters/csv_reporter.py +155 -0
  109. truthound_dashboard/core/reporters/html_reporter.py +598 -0
  110. truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
  111. truthound_dashboard/core/reporters/i18n/base.py +494 -0
  112. truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
  113. truthound_dashboard/core/reporters/json_reporter.py +160 -0
  114. truthound_dashboard/core/reporters/junit_reporter.py +233 -0
  115. truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
  116. truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
  117. truthound_dashboard/core/reporters/registry.py +272 -0
  118. truthound_dashboard/core/rule_generator.py +2088 -0
  119. truthound_dashboard/core/scheduler.py +822 -12
  120. truthound_dashboard/core/schema_evolution.py +858 -0
  121. truthound_dashboard/core/services.py +152 -9
  122. truthound_dashboard/core/statistics.py +718 -0
  123. truthound_dashboard/core/streaming_anomaly.py +883 -0
  124. truthound_dashboard/core/triggers/__init__.py +45 -0
  125. truthound_dashboard/core/triggers/base.py +226 -0
  126. truthound_dashboard/core/triggers/evaluators.py +609 -0
  127. truthound_dashboard/core/triggers/factory.py +363 -0
  128. truthound_dashboard/core/unified_alerts.py +870 -0
  129. truthound_dashboard/core/validation_limits.py +509 -0
  130. truthound_dashboard/core/versioning.py +709 -0
  131. truthound_dashboard/core/websocket/__init__.py +59 -0
  132. truthound_dashboard/core/websocket/manager.py +512 -0
  133. truthound_dashboard/core/websocket/messages.py +130 -0
  134. truthound_dashboard/db/__init__.py +30 -0
  135. truthound_dashboard/db/models.py +3375 -3
  136. truthound_dashboard/main.py +22 -0
  137. truthound_dashboard/schemas/__init__.py +396 -1
  138. truthound_dashboard/schemas/anomaly.py +1258 -0
  139. truthound_dashboard/schemas/base.py +4 -0
  140. truthound_dashboard/schemas/cross_alerts.py +334 -0
  141. truthound_dashboard/schemas/drift_monitor.py +890 -0
  142. truthound_dashboard/schemas/lineage.py +428 -0
  143. truthound_dashboard/schemas/maintenance.py +154 -0
  144. truthound_dashboard/schemas/model_monitoring.py +374 -0
  145. truthound_dashboard/schemas/notifications_advanced.py +1363 -0
  146. truthound_dashboard/schemas/openlineage.py +704 -0
  147. truthound_dashboard/schemas/plugins.py +1293 -0
  148. truthound_dashboard/schemas/profile.py +420 -34
  149. truthound_dashboard/schemas/profile_comparison.py +242 -0
  150. truthound_dashboard/schemas/reports.py +285 -0
  151. truthound_dashboard/schemas/rule_suggestion.py +434 -0
  152. truthound_dashboard/schemas/schema_evolution.py +164 -0
  153. truthound_dashboard/schemas/source.py +117 -2
  154. truthound_dashboard/schemas/triggers.py +511 -0
  155. truthound_dashboard/schemas/unified_alerts.py +223 -0
  156. truthound_dashboard/schemas/validation.py +25 -1
  157. truthound_dashboard/schemas/validators/__init__.py +11 -0
  158. truthound_dashboard/schemas/validators/base.py +151 -0
  159. truthound_dashboard/schemas/versioning.py +152 -0
  160. truthound_dashboard/static/index.html +2 -2
  161. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/METADATA +147 -23
  162. truthound_dashboard-1.4.1.dist-info/RECORD +239 -0
  163. truthound_dashboard/static/assets/index-BZG20KuF.js +0 -586
  164. truthound_dashboard/static/assets/index-D_HyZ3pb.css +0 -1
  165. truthound_dashboard/static/assets/unmerged_dictionaries-CtpqQBm0.js +0 -1
  166. truthound_dashboard-1.3.1.dist-info/RECORD +0 -110
  167. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/WHEEL +0 -0
  168. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/entry_points.txt +0 -0
  169. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,363 @@
1
+ """Trigger factory for creating and managing triggers.
2
+
3
+ Provides a high-level API for creating triggers from configuration
4
+ and integrating with the scheduler.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from datetime import datetime
11
+ from typing import Any
12
+
13
+ from truthound_dashboard.db import Schedule
14
+ from truthound_dashboard.schemas.triggers import (
15
+ TriggerType,
16
+ parse_trigger_config,
17
+ )
18
+
19
+ from .base import (
20
+ BaseTrigger,
21
+ TriggerContext,
22
+ TriggerEvaluation,
23
+ TriggerRegistry,
24
+ )
25
+
26
+ # Import evaluators to register them
27
+ from . import evaluators # noqa: F401
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class TriggerFactory:
33
+ """Factory for creating and managing trigger instances.
34
+
35
+ Provides methods to:
36
+ - Create triggers from Schedule models
37
+ - Create triggers from raw configuration
38
+ - Evaluate triggers with proper context
39
+ - Get trigger metadata and descriptions
40
+ """
41
+
42
+ @classmethod
43
+ def from_schedule(cls, schedule: Schedule) -> BaseTrigger | None:
44
+ """Create a trigger from a Schedule model.
45
+
46
+ Handles both new trigger_config and legacy cron_expression fields.
47
+
48
+ Args:
49
+ schedule: Schedule model instance.
50
+
51
+ Returns:
52
+ BaseTrigger instance or None if creation fails.
53
+ """
54
+ trigger_type = schedule.trigger_type or TriggerType.CRON.value
55
+
56
+ # Build config from schedule
57
+ if schedule.trigger_config:
58
+ config = schedule.trigger_config.copy()
59
+ else:
60
+ config = {}
61
+
62
+ # Handle legacy cron_expression field
63
+ if trigger_type == TriggerType.CRON.value:
64
+ if "expression" not in config and schedule.cron_expression:
65
+ config["expression"] = schedule.cron_expression
66
+
67
+ return cls.create(trigger_type, config)
68
+
69
+ @classmethod
70
+ def create(
71
+ cls, trigger_type: str, config: dict[str, Any]
72
+ ) -> BaseTrigger | None:
73
+ """Create a trigger from type and configuration.
74
+
75
+ Args:
76
+ trigger_type: Type identifier string.
77
+ config: Trigger-specific configuration.
78
+
79
+ Returns:
80
+ BaseTrigger instance or None if creation fails.
81
+ """
82
+ try:
83
+ # Normalize type to lowercase
84
+ trigger_type = trigger_type.lower()
85
+
86
+ trigger = TriggerRegistry.create(trigger_type, config)
87
+ if trigger is None:
88
+ logger.warning(f"Unknown trigger type: {trigger_type}")
89
+ return trigger
90
+
91
+ except ValueError as e:
92
+ logger.error(f"Invalid trigger config for {trigger_type}: {e}")
93
+ return None
94
+ except Exception as e:
95
+ logger.error(f"Failed to create trigger {trigger_type}: {e}")
96
+ return None
97
+
98
+ @classmethod
99
+ def create_from_dict(cls, data: dict[str, Any]) -> BaseTrigger | None:
100
+ """Create a trigger from a dictionary with type and config.
101
+
102
+ Args:
103
+ data: Dictionary with 'type' and optional config fields.
104
+
105
+ Returns:
106
+ BaseTrigger instance or None if creation fails.
107
+ """
108
+ trigger_type = data.get("type")
109
+ if not trigger_type:
110
+ logger.error("Trigger data missing 'type' field")
111
+ return None
112
+
113
+ # Extract config (all fields except 'type')
114
+ config = {k: v for k, v in data.items() if k != "type"}
115
+
116
+ return cls.create(trigger_type, config)
117
+
118
+ @classmethod
119
+ async def evaluate_schedule(
120
+ cls,
121
+ schedule: Schedule,
122
+ *,
123
+ profile_data: dict[str, Any] | None = None,
124
+ baseline_profile: dict[str, Any] | None = None,
125
+ event_data: dict[str, Any] | None = None,
126
+ force_trigger: bool = False,
127
+ ) -> TriggerEvaluation:
128
+ """Evaluate a schedule's trigger.
129
+
130
+ Args:
131
+ schedule: Schedule to evaluate.
132
+ profile_data: Current profile data (for data change triggers).
133
+ baseline_profile: Baseline profile for comparison.
134
+ event_data: Event data (for event triggers).
135
+ force_trigger: Force trigger regardless of conditions.
136
+
137
+ Returns:
138
+ TriggerEvaluation with result.
139
+ """
140
+ trigger = cls.from_schedule(schedule)
141
+
142
+ if trigger is None:
143
+ return TriggerEvaluation(
144
+ should_trigger=False,
145
+ reason=f"Failed to create trigger: {schedule.trigger_type}",
146
+ details={"error": "trigger_creation_failed"},
147
+ )
148
+
149
+ context = TriggerContext(
150
+ schedule_id=schedule.id,
151
+ source_id=schedule.source_id,
152
+ last_run_at=schedule.last_run_at,
153
+ trigger_count=schedule.trigger_count,
154
+ profile_data=profile_data,
155
+ baseline_profile=baseline_profile,
156
+ event_data=event_data,
157
+ custom_data={"force_trigger": force_trigger},
158
+ )
159
+
160
+ try:
161
+ return await trigger.evaluate(context)
162
+ except Exception as e:
163
+ logger.error(f"Trigger evaluation failed for schedule {schedule.id}: {e}")
164
+ return TriggerEvaluation(
165
+ should_trigger=False,
166
+ reason=f"Evaluation error: {str(e)}",
167
+ details={"error": str(e)},
168
+ )
169
+
170
+ @classmethod
171
+ def get_trigger_types(cls) -> list[dict[str, Any]]:
172
+ """Get information about all registered trigger types.
173
+
174
+ Returns:
175
+ List of trigger type information dictionaries.
176
+ """
177
+ types = []
178
+ for trigger_type in TriggerRegistry.list_types():
179
+ trigger_class = TriggerRegistry.get(trigger_type)
180
+ if trigger_class:
181
+ types.append({
182
+ "type": trigger_type,
183
+ "name": trigger_type.replace("_", " ").title(),
184
+ "description": _get_type_description(trigger_type),
185
+ "config_schema": _get_config_schema(trigger_type),
186
+ })
187
+ return types
188
+
189
+ @classmethod
190
+ def validate_config(
191
+ cls, trigger_type: str, config: dict[str, Any]
192
+ ) -> tuple[bool, str | None]:
193
+ """Validate trigger configuration without creating the trigger.
194
+
195
+ Args:
196
+ trigger_type: Type identifier.
197
+ config: Configuration to validate.
198
+
199
+ Returns:
200
+ Tuple of (is_valid, error_message).
201
+ """
202
+ try:
203
+ trigger = cls.create(trigger_type, config)
204
+ if trigger is None:
205
+ return False, f"Unknown trigger type: {trigger_type}"
206
+ return True, None
207
+ except ValueError as e:
208
+ return False, str(e)
209
+ except Exception as e:
210
+ return False, f"Validation error: {str(e)}"
211
+
212
+ @classmethod
213
+ def get_next_run_time(
214
+ cls,
215
+ schedule: Schedule,
216
+ from_time: datetime | None = None,
217
+ ) -> datetime | None:
218
+ """Calculate the next run time for a schedule.
219
+
220
+ Args:
221
+ schedule: Schedule to check.
222
+ from_time: Base time for calculation (defaults to now).
223
+
224
+ Returns:
225
+ Next run datetime or None.
226
+ """
227
+ trigger = cls.from_schedule(schedule)
228
+ if trigger is None:
229
+ return None
230
+
231
+ context = TriggerContext(
232
+ schedule_id=schedule.id,
233
+ source_id=schedule.source_id,
234
+ last_run_at=schedule.last_run_at,
235
+ trigger_count=schedule.trigger_count,
236
+ current_time=from_time or datetime.utcnow(),
237
+ )
238
+
239
+ return trigger.get_next_evaluation_time(context)
240
+
241
+
242
+ def _get_type_description(trigger_type: str) -> str:
243
+ """Get description for a trigger type."""
244
+ descriptions = {
245
+ "cron": "Schedule using cron expressions (e.g., '0 0 * * *' for daily at midnight)",
246
+ "interval": "Run at fixed time intervals (e.g., every 6 hours)",
247
+ "data_change": "Trigger when data profile changes by a threshold percentage",
248
+ "composite": "Combine multiple triggers with AND/OR logic",
249
+ "event": "Trigger in response to system events (e.g., schema changes)",
250
+ "manual": "Only run when manually triggered via API",
251
+ }
252
+ return descriptions.get(trigger_type, "Unknown trigger type")
253
+
254
+
255
+ def _get_config_schema(trigger_type: str) -> dict[str, Any]:
256
+ """Get configuration schema for a trigger type."""
257
+ schemas = {
258
+ "cron": {
259
+ "expression": {
260
+ "type": "string",
261
+ "required": True,
262
+ "description": "Cron expression (minute hour day month weekday)",
263
+ "examples": ["0 0 * * *", "0 */6 * * *", "0 8 * * 1-5"],
264
+ },
265
+ "timezone": {
266
+ "type": "string",
267
+ "required": False,
268
+ "default": "UTC",
269
+ "description": "Timezone for scheduling",
270
+ },
271
+ },
272
+ "interval": {
273
+ "seconds": {
274
+ "type": "integer",
275
+ "required": False,
276
+ "min": 1,
277
+ "description": "Interval in seconds",
278
+ },
279
+ "minutes": {
280
+ "type": "integer",
281
+ "required": False,
282
+ "min": 1,
283
+ "description": "Interval in minutes",
284
+ },
285
+ "hours": {
286
+ "type": "integer",
287
+ "required": False,
288
+ "min": 1,
289
+ "description": "Interval in hours",
290
+ },
291
+ "days": {
292
+ "type": "integer",
293
+ "required": False,
294
+ "min": 1,
295
+ "description": "Interval in days",
296
+ },
297
+ },
298
+ "data_change": {
299
+ "change_threshold": {
300
+ "type": "number",
301
+ "required": False,
302
+ "default": 0.05,
303
+ "min": 0.0,
304
+ "max": 1.0,
305
+ "description": "Minimum change percentage to trigger (0.0-1.0)",
306
+ },
307
+ "metrics": {
308
+ "type": "array",
309
+ "items": "string",
310
+ "required": False,
311
+ "default": ["row_count", "null_percentage", "distinct_count"],
312
+ "description": "Metrics to monitor for changes",
313
+ },
314
+ "check_interval_minutes": {
315
+ "type": "integer",
316
+ "required": False,
317
+ "default": 60,
318
+ "min": 1,
319
+ "description": "How often to check for changes",
320
+ },
321
+ },
322
+ "composite": {
323
+ "operator": {
324
+ "type": "string",
325
+ "required": False,
326
+ "default": "and",
327
+ "enum": ["and", "or"],
328
+ "description": "How to combine triggers",
329
+ },
330
+ "triggers": {
331
+ "type": "array",
332
+ "items": "object",
333
+ "required": True,
334
+ "min_items": 2,
335
+ "description": "List of trigger configurations to combine",
336
+ },
337
+ },
338
+ "event": {
339
+ "event_types": {
340
+ "type": "array",
341
+ "items": "string",
342
+ "required": True,
343
+ "description": "Event types that trigger execution",
344
+ "enum": [
345
+ "validation_completed",
346
+ "validation_failed",
347
+ "schema_changed",
348
+ "drift_detected",
349
+ "profile_updated",
350
+ "source_created",
351
+ "source_updated",
352
+ ],
353
+ },
354
+ "source_filter": {
355
+ "type": "array",
356
+ "items": "string",
357
+ "required": False,
358
+ "description": "Optional source IDs to filter events",
359
+ },
360
+ },
361
+ "manual": {},
362
+ }
363
+ return schemas.get(trigger_type, {})