truthound-dashboard 1.3.0__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.
- truthound_dashboard/api/alerts.py +258 -0
- truthound_dashboard/api/anomaly.py +1302 -0
- truthound_dashboard/api/cross_alerts.py +352 -0
- truthound_dashboard/api/deps.py +143 -0
- truthound_dashboard/api/drift_monitor.py +540 -0
- truthound_dashboard/api/lineage.py +1151 -0
- truthound_dashboard/api/maintenance.py +363 -0
- truthound_dashboard/api/middleware.py +373 -1
- truthound_dashboard/api/model_monitoring.py +805 -0
- truthound_dashboard/api/notifications_advanced.py +2452 -0
- truthound_dashboard/api/plugins.py +2096 -0
- truthound_dashboard/api/profile.py +211 -14
- truthound_dashboard/api/reports.py +853 -0
- truthound_dashboard/api/router.py +147 -0
- truthound_dashboard/api/rule_suggestions.py +310 -0
- truthound_dashboard/api/schema_evolution.py +231 -0
- truthound_dashboard/api/sources.py +47 -3
- truthound_dashboard/api/triggers.py +190 -0
- truthound_dashboard/api/validations.py +13 -0
- truthound_dashboard/api/validators.py +333 -4
- truthound_dashboard/api/versioning.py +309 -0
- truthound_dashboard/api/websocket.py +301 -0
- truthound_dashboard/core/__init__.py +27 -0
- truthound_dashboard/core/anomaly.py +1395 -0
- truthound_dashboard/core/anomaly_explainer.py +633 -0
- truthound_dashboard/core/cache.py +206 -0
- truthound_dashboard/core/cached_services.py +422 -0
- truthound_dashboard/core/charts.py +352 -0
- truthound_dashboard/core/connections.py +1069 -42
- truthound_dashboard/core/cross_alerts.py +837 -0
- truthound_dashboard/core/drift_monitor.py +1477 -0
- truthound_dashboard/core/drift_sampling.py +669 -0
- truthound_dashboard/core/i18n/__init__.py +42 -0
- truthound_dashboard/core/i18n/detector.py +173 -0
- truthound_dashboard/core/i18n/messages.py +564 -0
- truthound_dashboard/core/lineage.py +971 -0
- truthound_dashboard/core/maintenance.py +443 -5
- truthound_dashboard/core/model_monitoring.py +1043 -0
- truthound_dashboard/core/notifications/channels.py +1020 -1
- truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
- truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
- truthound_dashboard/core/notifications/deduplication/service.py +400 -0
- truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
- truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
- truthound_dashboard/core/notifications/dispatcher.py +43 -0
- truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
- truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
- truthound_dashboard/core/notifications/escalation/engine.py +429 -0
- truthound_dashboard/core/notifications/escalation/models.py +336 -0
- truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
- truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
- truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
- truthound_dashboard/core/notifications/events.py +49 -0
- truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
- truthound_dashboard/core/notifications/metrics/base.py +528 -0
- truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
- truthound_dashboard/core/notifications/routing/__init__.py +169 -0
- truthound_dashboard/core/notifications/routing/combinators.py +184 -0
- truthound_dashboard/core/notifications/routing/config.py +375 -0
- truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
- truthound_dashboard/core/notifications/routing/engine.py +382 -0
- truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
- truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
- truthound_dashboard/core/notifications/routing/rules.py +625 -0
- truthound_dashboard/core/notifications/routing/validator.py +678 -0
- truthound_dashboard/core/notifications/service.py +2 -0
- truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
- truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
- truthound_dashboard/core/notifications/throttling/builder.py +311 -0
- truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
- truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
- truthound_dashboard/core/openlineage.py +1028 -0
- truthound_dashboard/core/plugins/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/extractor.py +703 -0
- truthound_dashboard/core/plugins/docs/renderers.py +804 -0
- truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
- truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
- truthound_dashboard/core/plugins/hooks/manager.py +403 -0
- truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
- truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
- truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
- truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
- truthound_dashboard/core/plugins/loader.py +504 -0
- truthound_dashboard/core/plugins/registry.py +810 -0
- truthound_dashboard/core/plugins/reporter_executor.py +588 -0
- truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
- truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
- truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
- truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
- truthound_dashboard/core/plugins/sandbox.py +617 -0
- truthound_dashboard/core/plugins/security/__init__.py +68 -0
- truthound_dashboard/core/plugins/security/analyzer.py +535 -0
- truthound_dashboard/core/plugins/security/policies.py +311 -0
- truthound_dashboard/core/plugins/security/protocols.py +296 -0
- truthound_dashboard/core/plugins/security/signing.py +842 -0
- truthound_dashboard/core/plugins/security.py +446 -0
- truthound_dashboard/core/plugins/validator_executor.py +401 -0
- truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
- truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
- truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
- truthound_dashboard/core/plugins/versioning/semver.py +266 -0
- truthound_dashboard/core/profile_comparison.py +601 -0
- truthound_dashboard/core/report_history.py +570 -0
- truthound_dashboard/core/reporters/__init__.py +57 -0
- truthound_dashboard/core/reporters/base.py +296 -0
- truthound_dashboard/core/reporters/csv_reporter.py +155 -0
- truthound_dashboard/core/reporters/html_reporter.py +598 -0
- truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
- truthound_dashboard/core/reporters/i18n/base.py +494 -0
- truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
- truthound_dashboard/core/reporters/json_reporter.py +160 -0
- truthound_dashboard/core/reporters/junit_reporter.py +233 -0
- truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
- truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
- truthound_dashboard/core/reporters/registry.py +272 -0
- truthound_dashboard/core/rule_generator.py +2088 -0
- truthound_dashboard/core/scheduler.py +822 -12
- truthound_dashboard/core/schema_evolution.py +858 -0
- truthound_dashboard/core/services.py +152 -9
- truthound_dashboard/core/statistics.py +718 -0
- truthound_dashboard/core/streaming_anomaly.py +883 -0
- truthound_dashboard/core/triggers/__init__.py +45 -0
- truthound_dashboard/core/triggers/base.py +226 -0
- truthound_dashboard/core/triggers/evaluators.py +609 -0
- truthound_dashboard/core/triggers/factory.py +363 -0
- truthound_dashboard/core/unified_alerts.py +870 -0
- truthound_dashboard/core/validation_limits.py +509 -0
- truthound_dashboard/core/versioning.py +709 -0
- truthound_dashboard/core/websocket/__init__.py +59 -0
- truthound_dashboard/core/websocket/manager.py +512 -0
- truthound_dashboard/core/websocket/messages.py +130 -0
- truthound_dashboard/db/__init__.py +30 -0
- truthound_dashboard/db/models.py +3375 -3
- truthound_dashboard/main.py +22 -0
- truthound_dashboard/schemas/__init__.py +396 -1
- truthound_dashboard/schemas/anomaly.py +1258 -0
- truthound_dashboard/schemas/base.py +4 -0
- truthound_dashboard/schemas/cross_alerts.py +334 -0
- truthound_dashboard/schemas/drift_monitor.py +890 -0
- truthound_dashboard/schemas/lineage.py +428 -0
- truthound_dashboard/schemas/maintenance.py +154 -0
- truthound_dashboard/schemas/model_monitoring.py +374 -0
- truthound_dashboard/schemas/notifications_advanced.py +1363 -0
- truthound_dashboard/schemas/openlineage.py +704 -0
- truthound_dashboard/schemas/plugins.py +1293 -0
- truthound_dashboard/schemas/profile.py +420 -34
- truthound_dashboard/schemas/profile_comparison.py +242 -0
- truthound_dashboard/schemas/reports.py +285 -0
- truthound_dashboard/schemas/rule_suggestion.py +434 -0
- truthound_dashboard/schemas/schema_evolution.py +164 -0
- truthound_dashboard/schemas/source.py +117 -2
- truthound_dashboard/schemas/triggers.py +511 -0
- truthound_dashboard/schemas/unified_alerts.py +223 -0
- truthound_dashboard/schemas/validation.py +25 -1
- truthound_dashboard/schemas/validators/__init__.py +11 -0
- truthound_dashboard/schemas/validators/base.py +151 -0
- truthound_dashboard/schemas/versioning.py +152 -0
- truthound_dashboard/static/index.html +2 -2
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/METADATA +142 -18
- truthound_dashboard-1.4.0.dist-info/RECORD +239 -0
- truthound_dashboard/static/assets/index-BCA8H1hO.js +0 -574
- truthound_dashboard/static/assets/index-BNsSQ2fN.css +0 -1
- truthound_dashboard/static/assets/unmerged_dictionaries-CsJWCRx9.js +0 -1
- truthound_dashboard-1.3.0.dist-info/RECORD +0 -110
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.3.0.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)
|