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.
- 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.1.dist-info → truthound_dashboard-1.4.0.dist-info}/METADATA +142 -22
- truthound_dashboard-1.4.0.dist-info/RECORD +239 -0
- truthound_dashboard/static/assets/index-BZG20KuF.js +0 -586
- truthound_dashboard/static/assets/index-D_HyZ3pb.css +0 -1
- truthound_dashboard/static/assets/unmerged_dictionaries-CtpqQBm0.js +0 -1
- truthound_dashboard-1.3.1.dist-info/RECORD +0 -110
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Notification throttling system.
|
|
2
|
+
|
|
3
|
+
This module provides rate limiting for notifications to prevent
|
|
4
|
+
overwhelming recipients during incidents or high-frequency events.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- 5 throttler types (TokenBucket, FixedWindow, SlidingWindow, Composite, NoOp)
|
|
8
|
+
- Fluent builder API for configuration
|
|
9
|
+
- Per-channel and global throttling
|
|
10
|
+
- Burst allowance support
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
from truthound_dashboard.core.notifications.throttling import (
|
|
14
|
+
ThrottlerBuilder,
|
|
15
|
+
TokenBucketThrottler,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Using builder
|
|
19
|
+
throttler = (
|
|
20
|
+
ThrottlerBuilder()
|
|
21
|
+
.with_per_minute_limit(10)
|
|
22
|
+
.with_per_hour_limit(100)
|
|
23
|
+
.with_burst_allowance(1.5)
|
|
24
|
+
.build()
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Check if allowed
|
|
28
|
+
result = throttler.allow("slack-channel")
|
|
29
|
+
if result.allowed:
|
|
30
|
+
send_notification()
|
|
31
|
+
else:
|
|
32
|
+
print(f"Retry after {result.retry_after} seconds")
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from .builder import ThrottlerBuilder
|
|
36
|
+
from .stores import (
|
|
37
|
+
REDIS_AVAILABLE,
|
|
38
|
+
BaseThrottlingStore,
|
|
39
|
+
InMemoryThrottlingStore,
|
|
40
|
+
RedisThrottlingStore,
|
|
41
|
+
SQLiteThrottlingStore,
|
|
42
|
+
ThrottlingEntry,
|
|
43
|
+
ThrottlingMetrics,
|
|
44
|
+
ThrottlingStoreType,
|
|
45
|
+
create_throttling_store,
|
|
46
|
+
)
|
|
47
|
+
from .throttlers import (
|
|
48
|
+
BaseThrottler,
|
|
49
|
+
CompositeThrottler,
|
|
50
|
+
FixedWindowThrottler,
|
|
51
|
+
NoOpThrottler,
|
|
52
|
+
NotificationThrottler,
|
|
53
|
+
SlidingWindowThrottler,
|
|
54
|
+
ThrottleResult,
|
|
55
|
+
ThrottlerRegistry,
|
|
56
|
+
TokenBucketThrottler,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
# Throttlers
|
|
61
|
+
"BaseThrottler",
|
|
62
|
+
"TokenBucketThrottler",
|
|
63
|
+
"FixedWindowThrottler",
|
|
64
|
+
"SlidingWindowThrottler",
|
|
65
|
+
"CompositeThrottler",
|
|
66
|
+
"NoOpThrottler",
|
|
67
|
+
"ThrottlerRegistry",
|
|
68
|
+
"ThrottleResult",
|
|
69
|
+
# Service
|
|
70
|
+
"NotificationThrottler",
|
|
71
|
+
# Builder
|
|
72
|
+
"ThrottlerBuilder",
|
|
73
|
+
# Stores
|
|
74
|
+
"BaseThrottlingStore",
|
|
75
|
+
"InMemoryThrottlingStore",
|
|
76
|
+
"SQLiteThrottlingStore",
|
|
77
|
+
"RedisThrottlingStore",
|
|
78
|
+
"ThrottlingEntry",
|
|
79
|
+
"ThrottlingMetrics",
|
|
80
|
+
"ThrottlingStoreType",
|
|
81
|
+
"create_throttling_store",
|
|
82
|
+
"REDIS_AVAILABLE",
|
|
83
|
+
]
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""Fluent builder for throttler configuration.
|
|
2
|
+
|
|
3
|
+
This module provides a builder pattern for easily configuring
|
|
4
|
+
multi-level rate limiting with a fluent API.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
throttler = (
|
|
8
|
+
ThrottlerBuilder()
|
|
9
|
+
.with_per_minute_limit(10)
|
|
10
|
+
.with_per_hour_limit(100)
|
|
11
|
+
.with_per_day_limit(500)
|
|
12
|
+
.with_burst_allowance(1.5)
|
|
13
|
+
.build()
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
result = throttler.allow("channel-1")
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
from .stores import BaseThrottlingStore, InMemoryThrottlingStore
|
|
24
|
+
from .throttlers import (
|
|
25
|
+
BaseThrottler,
|
|
26
|
+
CompositeThrottler,
|
|
27
|
+
FixedWindowThrottler,
|
|
28
|
+
NotificationThrottler,
|
|
29
|
+
TokenBucketThrottler,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from typing import Self
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ThrottlerBuilder:
|
|
37
|
+
"""Fluent builder for creating throttlers.
|
|
38
|
+
|
|
39
|
+
Provides a convenient API for configuring multi-level
|
|
40
|
+
rate limiting with various algorithms.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
# Simple per-minute limit
|
|
44
|
+
throttler = (
|
|
45
|
+
ThrottlerBuilder()
|
|
46
|
+
.with_per_minute_limit(10)
|
|
47
|
+
.build()
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Multi-level limits with burst
|
|
51
|
+
throttler = (
|
|
52
|
+
ThrottlerBuilder()
|
|
53
|
+
.with_per_minute_limit(10)
|
|
54
|
+
.with_per_hour_limit(100)
|
|
55
|
+
.with_burst_allowance(1.5)
|
|
56
|
+
.build()
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Token bucket for smooth limiting
|
|
60
|
+
throttler = (
|
|
61
|
+
ThrottlerBuilder()
|
|
62
|
+
.with_token_bucket(capacity=10, refill_rate=1)
|
|
63
|
+
.build()
|
|
64
|
+
)
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self) -> None:
|
|
68
|
+
"""Initialize builder."""
|
|
69
|
+
self._per_second: int | None = None
|
|
70
|
+
self._per_minute: int | None = None
|
|
71
|
+
self._per_hour: int | None = None
|
|
72
|
+
self._per_day: int | None = None
|
|
73
|
+
self._burst_allowance: float = 1.0
|
|
74
|
+
self._token_bucket_capacity: float | None = None
|
|
75
|
+
self._token_bucket_rate: float | None = None
|
|
76
|
+
self._store: BaseThrottlingStore | None = None
|
|
77
|
+
self._throttlers: list[BaseThrottler] = []
|
|
78
|
+
|
|
79
|
+
def with_per_second_limit(self, limit: int) -> "Self":
|
|
80
|
+
"""Set per-second limit.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
limit: Maximum requests per second.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Self for chaining.
|
|
87
|
+
"""
|
|
88
|
+
self._per_second = limit
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
def with_per_minute_limit(self, limit: int) -> "Self":
|
|
92
|
+
"""Set per-minute limit.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
limit: Maximum requests per minute.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Self for chaining.
|
|
99
|
+
"""
|
|
100
|
+
self._per_minute = limit
|
|
101
|
+
return self
|
|
102
|
+
|
|
103
|
+
def with_per_hour_limit(self, limit: int) -> "Self":
|
|
104
|
+
"""Set per-hour limit.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
limit: Maximum requests per hour.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Self for chaining.
|
|
111
|
+
"""
|
|
112
|
+
self._per_hour = limit
|
|
113
|
+
return self
|
|
114
|
+
|
|
115
|
+
def with_per_day_limit(self, limit: int) -> "Self":
|
|
116
|
+
"""Set per-day limit.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
limit: Maximum requests per day.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Self for chaining.
|
|
123
|
+
"""
|
|
124
|
+
self._per_day = limit
|
|
125
|
+
return self
|
|
126
|
+
|
|
127
|
+
def with_burst_allowance(self, factor: float) -> "Self":
|
|
128
|
+
"""Set burst allowance factor.
|
|
129
|
+
|
|
130
|
+
Multiplies the limit to allow temporary bursts.
|
|
131
|
+
For example, 1.5 allows 150% of normal rate for short periods.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
factor: Burst multiplier (1.0 = no burst allowed).
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Self for chaining.
|
|
138
|
+
"""
|
|
139
|
+
self._burst_allowance = max(1.0, factor)
|
|
140
|
+
return self
|
|
141
|
+
|
|
142
|
+
def with_token_bucket(
|
|
143
|
+
self,
|
|
144
|
+
capacity: float,
|
|
145
|
+
refill_rate: float,
|
|
146
|
+
) -> "Self":
|
|
147
|
+
"""Configure token bucket algorithm.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
capacity: Maximum tokens (burst capacity).
|
|
151
|
+
refill_rate: Tokens added per second.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Self for chaining.
|
|
155
|
+
"""
|
|
156
|
+
self._token_bucket_capacity = capacity
|
|
157
|
+
self._token_bucket_rate = refill_rate
|
|
158
|
+
return self
|
|
159
|
+
|
|
160
|
+
def with_store(self, store: BaseThrottlingStore) -> "Self":
|
|
161
|
+
"""Set storage backend.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
store: Storage backend to use.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Self for chaining.
|
|
168
|
+
"""
|
|
169
|
+
self._store = store
|
|
170
|
+
return self
|
|
171
|
+
|
|
172
|
+
def add_throttler(self, throttler: BaseThrottler) -> "Self":
|
|
173
|
+
"""Add a custom throttler to the composite.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
throttler: Custom throttler to add.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Self for chaining.
|
|
180
|
+
"""
|
|
181
|
+
self._throttlers.append(throttler)
|
|
182
|
+
return self
|
|
183
|
+
|
|
184
|
+
def build(self) -> BaseThrottler:
|
|
185
|
+
"""Build the throttler.
|
|
186
|
+
|
|
187
|
+
Creates a CompositeThrottler if multiple limits are
|
|
188
|
+
configured, or a single throttler for simple configs.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Configured throttler.
|
|
192
|
+
"""
|
|
193
|
+
store = self._store or InMemoryThrottlingStore()
|
|
194
|
+
throttlers: list[BaseThrottler] = list(self._throttlers)
|
|
195
|
+
|
|
196
|
+
# Add token bucket if configured
|
|
197
|
+
if self._token_bucket_capacity and self._token_bucket_rate:
|
|
198
|
+
throttlers.append(
|
|
199
|
+
TokenBucketThrottler(
|
|
200
|
+
capacity=self._token_bucket_capacity * self._burst_allowance,
|
|
201
|
+
refill_rate=self._token_bucket_rate,
|
|
202
|
+
store=store,
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Add window-based throttlers
|
|
207
|
+
if self._per_second:
|
|
208
|
+
throttlers.append(
|
|
209
|
+
FixedWindowThrottler(
|
|
210
|
+
limit=int(self._per_second * self._burst_allowance),
|
|
211
|
+
window_seconds=1,
|
|
212
|
+
store=store,
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
if self._per_minute:
|
|
217
|
+
throttlers.append(
|
|
218
|
+
FixedWindowThrottler(
|
|
219
|
+
limit=int(self._per_minute * self._burst_allowance),
|
|
220
|
+
window_seconds=60,
|
|
221
|
+
store=store,
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if self._per_hour:
|
|
226
|
+
throttlers.append(
|
|
227
|
+
FixedWindowThrottler(
|
|
228
|
+
limit=int(self._per_hour * self._burst_allowance),
|
|
229
|
+
window_seconds=3600,
|
|
230
|
+
store=store,
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if self._per_day:
|
|
235
|
+
throttlers.append(
|
|
236
|
+
FixedWindowThrottler(
|
|
237
|
+
limit=int(self._per_day * self._burst_allowance),
|
|
238
|
+
window_seconds=86400,
|
|
239
|
+
store=store,
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Return appropriate throttler
|
|
244
|
+
if not throttlers:
|
|
245
|
+
# No limits configured - use simple token bucket default
|
|
246
|
+
return TokenBucketThrottler(
|
|
247
|
+
capacity=100,
|
|
248
|
+
refill_rate=10,
|
|
249
|
+
store=store,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
if len(throttlers) == 1:
|
|
253
|
+
return throttlers[0]
|
|
254
|
+
|
|
255
|
+
return CompositeThrottler(throttlers=throttlers)
|
|
256
|
+
|
|
257
|
+
def build_notification_throttler(
|
|
258
|
+
self,
|
|
259
|
+
global_limits: bool = True,
|
|
260
|
+
) -> NotificationThrottler:
|
|
261
|
+
"""Build a NotificationThrottler service.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
global_limits: Apply limits globally (vs per-channel).
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Configured NotificationThrottler.
|
|
268
|
+
"""
|
|
269
|
+
throttler = self.build()
|
|
270
|
+
|
|
271
|
+
if global_limits:
|
|
272
|
+
return NotificationThrottler(
|
|
273
|
+
global_throttler=throttler,
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
return NotificationThrottler(
|
|
277
|
+
default_throttler=throttler,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def configure_global_throttling(
|
|
282
|
+
per_minute: int | None = None,
|
|
283
|
+
per_hour: int | None = None,
|
|
284
|
+
per_day: int | None = None,
|
|
285
|
+
burst_allowance: float = 1.5,
|
|
286
|
+
) -> NotificationThrottler:
|
|
287
|
+
"""Configure global notification throttling.
|
|
288
|
+
|
|
289
|
+
Convenience function for common throttling setup.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
per_minute: Max notifications per minute.
|
|
293
|
+
per_hour: Max notifications per hour.
|
|
294
|
+
per_day: Max notifications per day.
|
|
295
|
+
burst_allowance: Burst factor.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Configured NotificationThrottler.
|
|
299
|
+
"""
|
|
300
|
+
builder = ThrottlerBuilder()
|
|
301
|
+
|
|
302
|
+
if per_minute:
|
|
303
|
+
builder.with_per_minute_limit(per_minute)
|
|
304
|
+
if per_hour:
|
|
305
|
+
builder.with_per_hour_limit(per_hour)
|
|
306
|
+
if per_day:
|
|
307
|
+
builder.with_per_day_limit(per_day)
|
|
308
|
+
|
|
309
|
+
builder.with_burst_allowance(burst_allowance)
|
|
310
|
+
|
|
311
|
+
return builder.build_notification_throttler(global_limits=True)
|