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,169 @@
1
+ """Advanced routing engine for notification dispatch.
2
+
3
+ This module provides a flexible, rule-based routing system for directing
4
+ notifications to appropriate channels based on configurable conditions.
5
+
6
+ Features:
7
+ - 13 built-in rule types (severity, issue count, time window, expression, jinja2, etc.)
8
+ - Rule combinators (AllOf, AnyOf, NotRule)
9
+ - Plugin architecture for custom rules
10
+ - YAML/JSON configuration support
11
+ - Priority-based route ordering
12
+ - Safe Python expression evaluation for complex routing conditions
13
+ - Jinja2 template-based rules for flexible matching (optional)
14
+
15
+ Example:
16
+ from truthound_dashboard.core.notifications.routing import (
17
+ ActionRouter,
18
+ Route,
19
+ RouteContext,
20
+ SeverityRule,
21
+ ExpressionRule,
22
+ AllOf,
23
+ )
24
+
25
+ # Create rules
26
+ critical_rule = SeverityRule(min_severity="critical")
27
+ production_rule = TagRule(tags=["production"])
28
+ combined_rule = AllOf(rules=[critical_rule, production_rule])
29
+
30
+ # Python expression-based rule for complex conditions
31
+ expr_rule = ExpressionRule(
32
+ expression="severity == 'critical' and pass_rate < 0.8",
33
+ )
34
+
35
+ # Create route
36
+ route = Route(
37
+ name="critical_production",
38
+ rule=combined_rule,
39
+ actions=["channel-1", "channel-2"],
40
+ priority=100,
41
+ )
42
+
43
+ # Create router and match
44
+ router = ActionRouter(routes=[route])
45
+ context = RouteContext(event=event, metadata={"tags": ["production"]})
46
+ matched_routes = await router.match(context)
47
+ """
48
+
49
+ from .combinators import AllOf, AnyOf, NotRule
50
+ from .config import RouteConfigParser
51
+ from .config_parser import (
52
+ ConfigBuilder,
53
+ ConfigParser,
54
+ RouteConfig,
55
+ RoutingConfig,
56
+ parse_routing_config,
57
+ )
58
+ from .engine import ActionRouter, Route, RouteContext, RoutingResult
59
+ from .expression_engine import (
60
+ ExpressionContext,
61
+ ExpressionError,
62
+ ExpressionRule,
63
+ ExpressionSecurityError,
64
+ ExpressionTimeout,
65
+ SafeExpressionEvaluator,
66
+ )
67
+ from .rules import (
68
+ AlwaysRule,
69
+ BaseRule,
70
+ DataAssetRule,
71
+ ErrorRule,
72
+ IssueCountRule,
73
+ MetadataRule,
74
+ NeverRule,
75
+ PassRateRule,
76
+ RuleRegistry,
77
+ SeverityRule,
78
+ StatusRule,
79
+ TagRule,
80
+ TimeWindowRule,
81
+ )
82
+ from .validator import (
83
+ RuleValidationConfig,
84
+ RuleValidationError,
85
+ RuleValidationResult,
86
+ RuleValidator,
87
+ ValidationError,
88
+ ValidationErrorType,
89
+ ValidationWarning,
90
+ validate_rule_config,
91
+ )
92
+
93
+ # Jinja2 support (optional dependency)
94
+ try:
95
+ from .jinja2_engine import (
96
+ JINJA2_AVAILABLE,
97
+ Jinja2Evaluator,
98
+ Jinja2Rule,
99
+ Jinja2SecurityError,
100
+ Jinja2TemplateError,
101
+ Jinja2TimeoutError,
102
+ TemplateNotificationFormatter,
103
+ )
104
+ except ImportError:
105
+ JINJA2_AVAILABLE = False
106
+ Jinja2Evaluator = None # type: ignore
107
+ Jinja2Rule = None # type: ignore
108
+ Jinja2TemplateError = None # type: ignore
109
+ Jinja2TimeoutError = None # type: ignore
110
+ Jinja2SecurityError = None # type: ignore
111
+ TemplateNotificationFormatter = None # type: ignore
112
+
113
+ __all__ = [
114
+ # Base
115
+ "BaseRule",
116
+ "RuleRegistry",
117
+ # Rules
118
+ "SeverityRule",
119
+ "IssueCountRule",
120
+ "PassRateRule",
121
+ "TimeWindowRule",
122
+ "TagRule",
123
+ "DataAssetRule",
124
+ "MetadataRule",
125
+ "StatusRule",
126
+ "ErrorRule",
127
+ "AlwaysRule",
128
+ "NeverRule",
129
+ "ExpressionRule",
130
+ # Expression Engine
131
+ "ExpressionContext",
132
+ "ExpressionError",
133
+ "ExpressionTimeout",
134
+ "ExpressionSecurityError",
135
+ "SafeExpressionEvaluator",
136
+ # Combinators
137
+ "AllOf",
138
+ "AnyOf",
139
+ "NotRule",
140
+ # Engine
141
+ "ActionRouter",
142
+ "Route",
143
+ "RouteContext",
144
+ "RoutingResult",
145
+ # Config
146
+ "RouteConfigParser",
147
+ "ConfigParser",
148
+ "ConfigBuilder",
149
+ "RouteConfig",
150
+ "RoutingConfig",
151
+ "parse_routing_config",
152
+ # Validator
153
+ "RuleValidator",
154
+ "RuleValidationConfig",
155
+ "RuleValidationResult",
156
+ "RuleValidationError",
157
+ "ValidationError",
158
+ "ValidationErrorType",
159
+ "ValidationWarning",
160
+ "validate_rule_config",
161
+ # Jinja2 (optional)
162
+ "JINJA2_AVAILABLE",
163
+ "Jinja2Evaluator",
164
+ "Jinja2Rule",
165
+ "Jinja2TemplateError",
166
+ "Jinja2TimeoutError",
167
+ "Jinja2SecurityError",
168
+ "TemplateNotificationFormatter",
169
+ ]
@@ -0,0 +1,184 @@
1
+ """Rule combinators for complex routing logic.
2
+
3
+ This module provides combinators that compose multiple rules
4
+ into complex conditions:
5
+
6
+ - AllOf: All rules must match (AND logic)
7
+ - AnyOf: Any rule must match (OR logic)
8
+ - NotRule: Negates a rule
9
+
10
+ Example:
11
+ # Critical severity AND production tag
12
+ rule = AllOf(rules=[
13
+ SeverityRule(min_severity="critical"),
14
+ TagRule(tags=["production"]),
15
+ ])
16
+
17
+ # Critical OR high issue count
18
+ rule = AnyOf(rules=[
19
+ SeverityRule(min_severity="critical"),
20
+ IssueCountRule(min_count=10),
21
+ ])
22
+
23
+ # NOT during business hours
24
+ rule = NotRule(rule=TimeWindowRule(start_hour=9, end_hour=17))
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from dataclasses import dataclass, field
30
+ from typing import TYPE_CHECKING, Any
31
+
32
+ from .rules import BaseRule, RuleRegistry
33
+
34
+ if TYPE_CHECKING:
35
+ from .engine import RouteContext
36
+
37
+
38
+ @RuleRegistry.register("all_of")
39
+ @dataclass
40
+ class AllOf(BaseRule):
41
+ """Combinator that requires all child rules to match.
42
+
43
+ Implements AND logic: the combinator matches only if
44
+ every child rule matches.
45
+
46
+ Attributes:
47
+ rules: List of rules that must all match.
48
+ """
49
+
50
+ rules: list[BaseRule] = field(default_factory=list)
51
+
52
+ @classmethod
53
+ def get_param_schema(cls) -> dict[str, Any]:
54
+ return {
55
+ "rules": {
56
+ "type": "array",
57
+ "required": True,
58
+ "description": "Rules that must all match",
59
+ "items": {"type": "object"},
60
+ }
61
+ }
62
+
63
+ async def matches(self, context: "RouteContext") -> bool:
64
+ """Check if all child rules match."""
65
+ if not self.rules:
66
+ return True
67
+
68
+ for rule in self.rules:
69
+ if not await rule.matches(context):
70
+ return False
71
+ return True
72
+
73
+ def to_dict(self) -> dict[str, Any]:
74
+ return {
75
+ "type": self.rule_type,
76
+ "rules": [rule.to_dict() for rule in self.rules],
77
+ }
78
+
79
+ @classmethod
80
+ def from_dict(cls, data: dict[str, Any]) -> "AllOf":
81
+ """Create AllOf from dictionary."""
82
+ rules_data = data.get("rules", [])
83
+ rules = []
84
+ for rule_data in rules_data:
85
+ rule = BaseRule.from_dict(rule_data)
86
+ if rule:
87
+ rules.append(rule)
88
+ return cls(rules=rules)
89
+
90
+
91
+ @RuleRegistry.register("any_of")
92
+ @dataclass
93
+ class AnyOf(BaseRule):
94
+ """Combinator that requires any child rule to match.
95
+
96
+ Implements OR logic: the combinator matches if
97
+ at least one child rule matches.
98
+
99
+ Attributes:
100
+ rules: List of rules where at least one must match.
101
+ """
102
+
103
+ rules: list[BaseRule] = field(default_factory=list)
104
+
105
+ @classmethod
106
+ def get_param_schema(cls) -> dict[str, Any]:
107
+ return {
108
+ "rules": {
109
+ "type": "array",
110
+ "required": True,
111
+ "description": "Rules where at least one must match",
112
+ "items": {"type": "object"},
113
+ }
114
+ }
115
+
116
+ async def matches(self, context: "RouteContext") -> bool:
117
+ """Check if any child rule matches."""
118
+ if not self.rules:
119
+ return False
120
+
121
+ for rule in self.rules:
122
+ if await rule.matches(context):
123
+ return True
124
+ return False
125
+
126
+ def to_dict(self) -> dict[str, Any]:
127
+ return {
128
+ "type": self.rule_type,
129
+ "rules": [rule.to_dict() for rule in self.rules],
130
+ }
131
+
132
+ @classmethod
133
+ def from_dict(cls, data: dict[str, Any]) -> "AnyOf":
134
+ """Create AnyOf from dictionary."""
135
+ rules_data = data.get("rules", [])
136
+ rules = []
137
+ for rule_data in rules_data:
138
+ rule = BaseRule.from_dict(rule_data)
139
+ if rule:
140
+ rules.append(rule)
141
+ return cls(rules=rules)
142
+
143
+
144
+ @RuleRegistry.register("not")
145
+ @dataclass
146
+ class NotRule(BaseRule):
147
+ """Combinator that negates a rule.
148
+
149
+ Matches when the child rule does NOT match.
150
+
151
+ Attributes:
152
+ rule: The rule to negate.
153
+ """
154
+
155
+ rule: BaseRule | None = None
156
+
157
+ @classmethod
158
+ def get_param_schema(cls) -> dict[str, Any]:
159
+ return {
160
+ "rule": {
161
+ "type": "object",
162
+ "required": True,
163
+ "description": "Rule to negate",
164
+ }
165
+ }
166
+
167
+ async def matches(self, context: "RouteContext") -> bool:
168
+ """Check if child rule does NOT match."""
169
+ if self.rule is None:
170
+ return False
171
+ return not await self.rule.matches(context)
172
+
173
+ def to_dict(self) -> dict[str, Any]:
174
+ return {
175
+ "type": self.rule_type,
176
+ "rule": self.rule.to_dict() if self.rule else None,
177
+ }
178
+
179
+ @classmethod
180
+ def from_dict(cls, data: dict[str, Any]) -> "NotRule":
181
+ """Create NotRule from dictionary."""
182
+ rule_data = data.get("rule")
183
+ rule = BaseRule.from_dict(rule_data) if rule_data else None
184
+ return cls(rule=rule)