truthound-dashboard 1.3.1__py3-none-any.whl → 1.4.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.
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.0.dist-info}/METADATA +142 -22
  162. truthound_dashboard-1.4.0.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.0.dist-info}/WHEEL +0 -0
  168. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/entry_points.txt +0 -0
  169. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,375 @@
1
+ """Configuration parser for routing rules.
2
+
3
+ This module provides YAML/JSON configuration parsing for
4
+ creating routes and rules from configuration files.
5
+
6
+ Example YAML config:
7
+ routes:
8
+ - name: critical_pagerduty
9
+ rule:
10
+ type: severity
11
+ min_severity: critical
12
+ actions:
13
+ - pagerduty-channel
14
+ priority: 100
15
+
16
+ - name: production_alerts
17
+ rule:
18
+ type: all_of
19
+ rules:
20
+ - type: tag
21
+ tags: ["production"]
22
+ - type: severity
23
+ min_severity: high
24
+ actions:
25
+ - slack-alerts
26
+ priority: 50
27
+
28
+ - name: default
29
+ rule:
30
+ type: always
31
+ actions:
32
+ - slack-general
33
+ priority: 0
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ from pathlib import Path
39
+ from typing import Any
40
+
41
+ from .engine import ActionRouter, Route
42
+ from .rules import BaseRule
43
+
44
+
45
+ class RouteConfigParser:
46
+ """Parser for routing configuration files.
47
+
48
+ Supports both YAML and JSON configuration formats.
49
+
50
+ Example usage:
51
+ # Parse from file
52
+ router = RouteConfigParser.from_file("routes.yaml")
53
+
54
+ # Parse from dict
55
+ router = RouteConfigParser.from_dict(config)
56
+
57
+ # Parse from YAML string
58
+ router = RouteConfigParser.from_yaml(yaml_string)
59
+ """
60
+
61
+ @classmethod
62
+ def from_file(cls, path: str | Path) -> ActionRouter:
63
+ """Load routing configuration from file.
64
+
65
+ Args:
66
+ path: Path to configuration file (.yaml, .yml, or .json).
67
+
68
+ Returns:
69
+ Configured ActionRouter.
70
+
71
+ Raises:
72
+ FileNotFoundError: If file doesn't exist.
73
+ ValueError: If file format is unsupported.
74
+ """
75
+ path = Path(path)
76
+
77
+ if not path.exists():
78
+ raise FileNotFoundError(f"Configuration file not found: {path}")
79
+
80
+ content = path.read_text()
81
+
82
+ if path.suffix in (".yaml", ".yml"):
83
+ return cls.from_yaml(content)
84
+ elif path.suffix == ".json":
85
+ return cls.from_json(content)
86
+ else:
87
+ raise ValueError(f"Unsupported file format: {path.suffix}")
88
+
89
+ @classmethod
90
+ def from_yaml(cls, content: str) -> ActionRouter:
91
+ """Parse routing configuration from YAML string.
92
+
93
+ Args:
94
+ content: YAML configuration string.
95
+
96
+ Returns:
97
+ Configured ActionRouter.
98
+ """
99
+ try:
100
+ import yaml
101
+ except ImportError:
102
+ raise ImportError(
103
+ "PyYAML is required for YAML config parsing. "
104
+ "Install with: pip install pyyaml"
105
+ )
106
+
107
+ data = yaml.safe_load(content)
108
+ return cls.from_dict(data)
109
+
110
+ @classmethod
111
+ def from_json(cls, content: str) -> ActionRouter:
112
+ """Parse routing configuration from JSON string.
113
+
114
+ Args:
115
+ content: JSON configuration string.
116
+
117
+ Returns:
118
+ Configured ActionRouter.
119
+ """
120
+ import json
121
+
122
+ data = json.loads(content)
123
+ return cls.from_dict(data)
124
+
125
+ @classmethod
126
+ def from_dict(cls, data: dict[str, Any]) -> ActionRouter:
127
+ """Parse routing configuration from dictionary.
128
+
129
+ Args:
130
+ data: Configuration dictionary.
131
+
132
+ Returns:
133
+ Configured ActionRouter.
134
+ """
135
+ routes: list[Route] = []
136
+ default_route: Route | None = None
137
+
138
+ # Parse routes
139
+ routes_data = data.get("routes", data.get("routing", {}).get("routes", []))
140
+ for route_data in routes_data:
141
+ route = cls._parse_route(route_data)
142
+ if route:
143
+ routes.append(route)
144
+
145
+ # Parse default route
146
+ default_data = data.get("default_route") or data.get("routing", {}).get("default_route")
147
+ if default_data:
148
+ default_route = cls._parse_route(default_data)
149
+
150
+ return ActionRouter(routes=routes, default_route=default_route)
151
+
152
+ @classmethod
153
+ def _parse_route(cls, data: dict[str, Any]) -> Route | None:
154
+ """Parse a single route from configuration.
155
+
156
+ Args:
157
+ data: Route configuration dictionary.
158
+
159
+ Returns:
160
+ Parsed Route or None if invalid.
161
+ """
162
+ rule_data = data.get("rule")
163
+ if not rule_data:
164
+ return None
165
+
166
+ rule = cls._parse_rule(rule_data)
167
+ if rule is None:
168
+ return None
169
+
170
+ return Route(
171
+ name=data.get("name", "unnamed"),
172
+ rule=rule,
173
+ actions=data.get("actions", []),
174
+ priority=data.get("priority", 0),
175
+ is_active=data.get("is_active", True),
176
+ escalation_policy_id=data.get("escalation_policy_id"),
177
+ stop_on_match=data.get("stop_on_match", False),
178
+ metadata=data.get("metadata", {}),
179
+ )
180
+
181
+ @classmethod
182
+ def _parse_rule(cls, data: dict[str, Any]) -> BaseRule | None:
183
+ """Parse a rule from configuration.
184
+
185
+ Handles nested rules for combinators.
186
+
187
+ Args:
188
+ data: Rule configuration dictionary.
189
+
190
+ Returns:
191
+ Parsed rule or None if invalid.
192
+ """
193
+ rule_type = data.get("type")
194
+ if not rule_type:
195
+ return None
196
+
197
+ # Handle combinator rules with nested rules
198
+ if rule_type in ("all_of", "any_of"):
199
+ nested_rules = []
200
+ for nested_data in data.get("rules", []):
201
+ nested_rule = cls._parse_rule(nested_data)
202
+ if nested_rule:
203
+ nested_rules.append(nested_rule)
204
+
205
+ from .combinators import AllOf, AnyOf
206
+
207
+ if rule_type == "all_of":
208
+ return AllOf(rules=nested_rules)
209
+ else:
210
+ return AnyOf(rules=nested_rules)
211
+
212
+ elif rule_type == "not":
213
+ nested_data = data.get("rule")
214
+ if nested_data:
215
+ nested_rule = cls._parse_rule(nested_data)
216
+ if nested_rule:
217
+ from .combinators import NotRule
218
+
219
+ return NotRule(rule=nested_rule)
220
+ return None
221
+
222
+ # Use registry for simple rules
223
+ return BaseRule.from_dict(data)
224
+
225
+ @classmethod
226
+ def to_yaml(cls, router: ActionRouter) -> str:
227
+ """Export router configuration to YAML string.
228
+
229
+ Args:
230
+ router: ActionRouter to export.
231
+
232
+ Returns:
233
+ YAML configuration string.
234
+ """
235
+ try:
236
+ import yaml
237
+ except ImportError:
238
+ raise ImportError(
239
+ "PyYAML is required for YAML config export. "
240
+ "Install with: pip install pyyaml"
241
+ )
242
+
243
+ data = cls.to_dict(router)
244
+ return yaml.dump(data, default_flow_style=False, sort_keys=False)
245
+
246
+ @classmethod
247
+ def to_json(cls, router: ActionRouter, indent: int = 2) -> str:
248
+ """Export router configuration to JSON string.
249
+
250
+ Args:
251
+ router: ActionRouter to export.
252
+ indent: JSON indentation level.
253
+
254
+ Returns:
255
+ JSON configuration string.
256
+ """
257
+ import json
258
+
259
+ data = cls.to_dict(router)
260
+ return json.dumps(data, indent=indent)
261
+
262
+ @classmethod
263
+ def to_dict(cls, router: ActionRouter) -> dict[str, Any]:
264
+ """Export router configuration to dictionary.
265
+
266
+ Args:
267
+ router: ActionRouter to export.
268
+
269
+ Returns:
270
+ Configuration dictionary.
271
+ """
272
+ return {
273
+ "routes": [route.to_dict() for route in router.routes],
274
+ "default_route": router.default_route.to_dict() if router.default_route else None,
275
+ }
276
+
277
+ @classmethod
278
+ def validate(cls, data: dict[str, Any]) -> list[str]:
279
+ """Validate routing configuration.
280
+
281
+ Args:
282
+ data: Configuration dictionary to validate.
283
+
284
+ Returns:
285
+ List of validation error messages (empty if valid).
286
+ """
287
+ errors: list[str] = []
288
+
289
+ routes_data = data.get("routes", [])
290
+ if not isinstance(routes_data, list):
291
+ errors.append("'routes' must be a list")
292
+ return errors
293
+
294
+ route_names = set()
295
+ for i, route_data in enumerate(routes_data):
296
+ route_errors = cls._validate_route(route_data, i)
297
+ errors.extend(route_errors)
298
+
299
+ # Check for duplicate names
300
+ name = route_data.get("name")
301
+ if name:
302
+ if name in route_names:
303
+ errors.append(f"Route {i}: Duplicate route name '{name}'")
304
+ route_names.add(name)
305
+
306
+ return errors
307
+
308
+ @classmethod
309
+ def _validate_route(cls, data: dict[str, Any], index: int) -> list[str]:
310
+ """Validate a single route configuration."""
311
+ errors: list[str] = []
312
+ prefix = f"Route {index}"
313
+
314
+ if not isinstance(data, dict):
315
+ errors.append(f"{prefix}: Must be an object")
316
+ return errors
317
+
318
+ # Required fields
319
+ if "rule" not in data:
320
+ errors.append(f"{prefix}: Missing required field 'rule'")
321
+
322
+ if "actions" not in data:
323
+ errors.append(f"{prefix}: Missing required field 'actions'")
324
+ elif not isinstance(data["actions"], list):
325
+ errors.append(f"{prefix}: 'actions' must be a list")
326
+ elif not data["actions"]:
327
+ errors.append(f"{prefix}: 'actions' must not be empty")
328
+
329
+ # Validate rule
330
+ rule_data = data.get("rule", {})
331
+ rule_errors = cls._validate_rule(rule_data, prefix)
332
+ errors.extend(rule_errors)
333
+
334
+ return errors
335
+
336
+ @classmethod
337
+ def _validate_rule(cls, data: dict[str, Any], prefix: str) -> list[str]:
338
+ """Validate a rule configuration."""
339
+ from .rules import RuleRegistry
340
+
341
+ errors: list[str] = []
342
+
343
+ if not isinstance(data, dict):
344
+ errors.append(f"{prefix}: Rule must be an object")
345
+ return errors
346
+
347
+ rule_type = data.get("type")
348
+ if not rule_type:
349
+ errors.append(f"{prefix}: Rule missing required field 'type'")
350
+ return errors
351
+
352
+ # Check if rule type exists
353
+ if rule_type not in RuleRegistry.list_types():
354
+ errors.append(f"{prefix}: Unknown rule type '{rule_type}'")
355
+ return errors
356
+
357
+ # Validate nested rules for combinators
358
+ if rule_type in ("all_of", "any_of"):
359
+ nested_rules = data.get("rules", [])
360
+ if not isinstance(nested_rules, list):
361
+ errors.append(f"{prefix}: '{rule_type}' rules must be a list")
362
+ else:
363
+ for i, nested_data in enumerate(nested_rules):
364
+ nested_errors = cls._validate_rule(nested_data, f"{prefix}/{rule_type}[{i}]")
365
+ errors.extend(nested_errors)
366
+
367
+ elif rule_type == "not":
368
+ nested_data = data.get("rule")
369
+ if not nested_data:
370
+ errors.append(f"{prefix}: 'not' rule missing nested 'rule'")
371
+ else:
372
+ nested_errors = cls._validate_rule(nested_data, f"{prefix}/not")
373
+ errors.extend(nested_errors)
374
+
375
+ return errors