cgse-common 0.17.2__py3-none-any.whl → 0.17.4__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.
- {cgse_common-0.17.2.dist-info → cgse_common-0.17.4.dist-info}/METADATA +1 -1
- {cgse_common-0.17.2.dist-info → cgse_common-0.17.4.dist-info}/RECORD +8 -11
- egse/hk.py +2 -2
- egse/log.py +8 -0
- egse/socketdevice.py +5 -4
- egse/system.py +5 -0
- egse/plugins/metrics/duckdb.py +0 -442
- egse/plugins/metrics/timescaledb.py +0 -596
- egse/ratelimit.py +0 -275
- {cgse_common-0.17.2.dist-info → cgse_common-0.17.4.dist-info}/WHEEL +0 -0
- {cgse_common-0.17.2.dist-info → cgse_common-0.17.4.dist-info}/entry_points.txt +0 -0
egse/ratelimit.py
DELETED
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Rate limiting utilities for controlling execution frequency of functions and code blocks.
|
|
3
|
-
|
|
4
|
-
This module provides decorators, context managers, and utility functions to limit how
|
|
5
|
-
often code executes, which is particularly useful for:
|
|
6
|
-
|
|
7
|
-
- Reducing noisy output in loops (print statements, logging)
|
|
8
|
-
- Throttling expensive operations
|
|
9
|
-
- Controlling debug message frequency
|
|
10
|
-
- Implementing backpressure in data processing pipelines
|
|
11
|
-
|
|
12
|
-
The rate limiting tracks execution counts per unique key and shows suppressed message
|
|
13
|
-
counts similar to system loggers (e.g., "... 5 more similar messages suppressed").
|
|
14
|
-
|
|
15
|
-
Basic Usage:
|
|
16
|
-
# Decorator - limit function execution
|
|
17
|
-
@rate_limit(every_n=10)
|
|
18
|
-
def debug_function():
|
|
19
|
-
print("Debug info")
|
|
20
|
-
|
|
21
|
-
# Context manager - limit code block execution
|
|
22
|
-
for i in range(100):
|
|
23
|
-
with rate_limited(every_n=10, key="progress") as should_execute:
|
|
24
|
-
if should_execute:
|
|
25
|
-
print(f"Progress: {i}")
|
|
26
|
-
|
|
27
|
-
# Convenience functions
|
|
28
|
-
for i in range(100):
|
|
29
|
-
rate_limited_print(f"Processing item {i}", every_n=10)
|
|
30
|
-
|
|
31
|
-
Advanced Usage:
|
|
32
|
-
# Custom rate limiter instances for isolation
|
|
33
|
-
debug_limiter = RateLimiter()
|
|
34
|
-
|
|
35
|
-
@rate_limit(every_n=5, limiter=debug_limiter)
|
|
36
|
-
def isolated_debug():
|
|
37
|
-
print("This uses a separate counter")
|
|
38
|
-
|
|
39
|
-
# Rate-limited logging with suppression tracking
|
|
40
|
-
import logging
|
|
41
|
-
logger = logging.getLogger(__name__)
|
|
42
|
-
|
|
43
|
-
for i in range(50):
|
|
44
|
-
rate_limited_log(logger, logging.WARNING,
|
|
45
|
-
"High CPU usage", every_n=10)
|
|
46
|
-
|
|
47
|
-
Classes:
|
|
48
|
-
RateLimiter: Core rate limiting logic with per-key counters
|
|
49
|
-
|
|
50
|
-
Functions:
|
|
51
|
-
rate_limit: Decorator for rate-limiting function calls
|
|
52
|
-
rate_limited: Context manager for rate-limiting code blocks
|
|
53
|
-
rate_limited_print: Convenience function for rate-limited printing
|
|
54
|
-
rate_limited_log: Convenience function for rate-limited logging
|
|
55
|
-
|
|
56
|
-
Author: Generated by Claude
|
|
57
|
-
License: MIT
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
import time
|
|
61
|
-
import logging
|
|
62
|
-
from functools import wraps
|
|
63
|
-
from collections import defaultdict
|
|
64
|
-
from contextlib import contextmanager
|
|
65
|
-
from typing import Dict, Any, Optional, Callable
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
class RateLimiter:
|
|
69
|
-
"""A rate limiter that tracks execution counts and provides rate limiting functionality."""
|
|
70
|
-
|
|
71
|
-
def __init__(self):
|
|
72
|
-
self.counters: Dict[str, int] = defaultdict(int)
|
|
73
|
-
self.last_executed: Dict[str, int] = defaultdict(int)
|
|
74
|
-
self.suppressed_count: Dict[str, int] = defaultdict(int)
|
|
75
|
-
|
|
76
|
-
def should_execute(self, key: str, every_n: int) -> tuple[bool, int]:
|
|
77
|
-
"""
|
|
78
|
-
Check if an operation should be executed based on rate limiting.
|
|
79
|
-
|
|
80
|
-
Returns:
|
|
81
|
-
tuple: (should_execute: bool, suppressed_count: int)
|
|
82
|
-
"""
|
|
83
|
-
self.counters[key] += 1
|
|
84
|
-
|
|
85
|
-
# Execute on 1st call, then every every_n calls after that
|
|
86
|
-
if self.counters[key] == 1 or (self.counters[key] - 1) % every_n == 0:
|
|
87
|
-
# For the first call, no suppressed count to return
|
|
88
|
-
if self.counters[key] == 1:
|
|
89
|
-
return True, 0
|
|
90
|
-
else:
|
|
91
|
-
# For subsequent executions, return the suppressed count and reset it
|
|
92
|
-
suppressed = self.suppressed_count[key]
|
|
93
|
-
self.suppressed_count[key] = 0
|
|
94
|
-
return True, suppressed
|
|
95
|
-
else:
|
|
96
|
-
self.suppressed_count[key] += 1
|
|
97
|
-
return False, 0
|
|
98
|
-
|
|
99
|
-
def reset(self, key: Optional[str] = None):
|
|
100
|
-
"""Reset counters for a specific key or all keys."""
|
|
101
|
-
if key:
|
|
102
|
-
self.counters[key] = 0
|
|
103
|
-
self.last_executed[key] = 0
|
|
104
|
-
self.suppressed_count[key] = 0
|
|
105
|
-
else:
|
|
106
|
-
self.counters.clear()
|
|
107
|
-
self.last_executed.clear()
|
|
108
|
-
self.suppressed_count.clear()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# Global rate limiter instance
|
|
112
|
-
_global_limiter = RateLimiter()
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def rate_limit(
|
|
116
|
-
every_n: int = 10, key: Optional[str] = None, show_suppressed: bool = True, limiter: Optional[RateLimiter] = None
|
|
117
|
-
):
|
|
118
|
-
"""
|
|
119
|
-
Decorator that limits function execution to every N calls.
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
every_n: Execute only every N calls
|
|
123
|
-
key: Unique identifier for this rate limiter (auto-generated if None)
|
|
124
|
-
show_suppressed: Whether to show "... N more suppressed" message
|
|
125
|
-
limiter: Custom RateLimiter instance (uses global one if None)
|
|
126
|
-
"""
|
|
127
|
-
|
|
128
|
-
def decorator(func: Callable) -> Callable:
|
|
129
|
-
nonlocal key
|
|
130
|
-
if key is None:
|
|
131
|
-
key = f"{func.__module__}.{func.__qualname__}"
|
|
132
|
-
|
|
133
|
-
rate_limiter = limiter or _global_limiter
|
|
134
|
-
|
|
135
|
-
@wraps(func)
|
|
136
|
-
def wrapper(*args, **kwargs):
|
|
137
|
-
should_execute, suppressed = rate_limiter.should_execute(key, every_n)
|
|
138
|
-
|
|
139
|
-
if should_execute:
|
|
140
|
-
# Show suppressed count BEFORE executing (except for first execution)
|
|
141
|
-
if show_suppressed and suppressed > 0:
|
|
142
|
-
print(f"... {suppressed} more similar messages suppressed")
|
|
143
|
-
|
|
144
|
-
result = func(*args, **kwargs)
|
|
145
|
-
return result
|
|
146
|
-
|
|
147
|
-
return None # Suppressed execution
|
|
148
|
-
|
|
149
|
-
# Add utility methods to the wrapper
|
|
150
|
-
wrapper.reset = lambda: rate_limiter.reset(key)
|
|
151
|
-
wrapper.get_count = lambda: rate_limiter.counters[key]
|
|
152
|
-
|
|
153
|
-
return wrapper
|
|
154
|
-
|
|
155
|
-
return decorator
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
@contextmanager
|
|
159
|
-
def rate_limited(
|
|
160
|
-
every_n: int = 10, key: Optional[str] = None, show_suppressed: bool = True, limiter: Optional[RateLimiter] = None
|
|
161
|
-
):
|
|
162
|
-
"""
|
|
163
|
-
Context manager that limits execution of code block to every N calls.
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
every_n: Execute only every N calls
|
|
167
|
-
key: Unique identifier for this rate limiter (required for context manager)
|
|
168
|
-
show_suppressed: Whether to show "... N more suppressed" message
|
|
169
|
-
limiter: Custom RateLimiter instance (uses global one if None)
|
|
170
|
-
|
|
171
|
-
Yields:
|
|
172
|
-
bool: True if code should execute, False if suppressed
|
|
173
|
-
"""
|
|
174
|
-
if key is None:
|
|
175
|
-
raise ValueError("Context manager requires a unique 'key' parameter")
|
|
176
|
-
|
|
177
|
-
rate_limiter = limiter or _global_limiter
|
|
178
|
-
should_execute, suppressed = rate_limiter.should_execute(key, every_n)
|
|
179
|
-
|
|
180
|
-
if should_execute:
|
|
181
|
-
# Show suppressed count BEFORE yielding (except for first execution)
|
|
182
|
-
if show_suppressed and suppressed > 0:
|
|
183
|
-
print(f"... {suppressed} more similar operations suppressed")
|
|
184
|
-
|
|
185
|
-
try:
|
|
186
|
-
yield should_execute
|
|
187
|
-
finally:
|
|
188
|
-
pass # Nothing to do in finally
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
# Convenience functions for common use cases
|
|
192
|
-
def rate_limited_print(message: str, every_n: int = 10, key: Optional[str] = None):
|
|
193
|
-
"""Print a message only every N calls."""
|
|
194
|
-
if key is None:
|
|
195
|
-
key = f"print_{hash(message) % 10000}"
|
|
196
|
-
|
|
197
|
-
with rate_limited(every_n, key=key) as should_execute:
|
|
198
|
-
if should_execute:
|
|
199
|
-
print(message)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def rate_limited_log(
|
|
203
|
-
logger: logging.Logger, level: int, message: str, every_n: int = 10, key: Optional[str] = None, *args
|
|
204
|
-
):
|
|
205
|
-
"""Log a message only every N calls."""
|
|
206
|
-
if key is None:
|
|
207
|
-
key = f"log_{hash(message) % 10000}"
|
|
208
|
-
|
|
209
|
-
with rate_limited(every_n, key=key, show_suppressed=False) as should_execute:
|
|
210
|
-
if should_execute:
|
|
211
|
-
suppressed = _global_limiter.suppressed_count[key]
|
|
212
|
-
if suppressed > 0:
|
|
213
|
-
message += f" (... {suppressed} more similar messages suppressed)"
|
|
214
|
-
logger.log(level, message, *args)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
# Example usage and demonstrations
|
|
218
|
-
if __name__ == "__main__":
|
|
219
|
-
print("=== Decorator Example ===")
|
|
220
|
-
|
|
221
|
-
@rate_limit(every_n=3, show_suppressed=True)
|
|
222
|
-
def noisy_function(i):
|
|
223
|
-
print(f"Processing item {i}")
|
|
224
|
-
return f"result_{i}"
|
|
225
|
-
|
|
226
|
-
# This will only print every 3rd call
|
|
227
|
-
for i in range(10):
|
|
228
|
-
noisy_function(i)
|
|
229
|
-
|
|
230
|
-
print(f"\nTotal calls to noisy_function: {noisy_function.get_count()}")
|
|
231
|
-
|
|
232
|
-
print("\n=== Context Manager Example ===")
|
|
233
|
-
|
|
234
|
-
# Using context manager in a loop
|
|
235
|
-
for i in range(15):
|
|
236
|
-
with rate_limited(every_n=5, key="loop_progress") as should_execute:
|
|
237
|
-
if should_execute:
|
|
238
|
-
print(f"Progress update: processed {i + 1} items")
|
|
239
|
-
|
|
240
|
-
print("\n=== Convenience Functions Example ===")
|
|
241
|
-
|
|
242
|
-
# Using convenience functions
|
|
243
|
-
for i in range(8):
|
|
244
|
-
rate_limited_print(f"Item {i} processed", every_n=3, key="item_processing")
|
|
245
|
-
|
|
246
|
-
print("\n=== Logging Example ===")
|
|
247
|
-
|
|
248
|
-
# Setup logging
|
|
249
|
-
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
|
250
|
-
logger = logging.getLogger(__name__)
|
|
251
|
-
|
|
252
|
-
# Rate-limited logging
|
|
253
|
-
for i in range(12):
|
|
254
|
-
rate_limited_log(
|
|
255
|
-
logger, logging.WARNING, f"High memory usage detected: {i * 10}%", every_n=4, key="memory_warning"
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
print("\n=== Multiple Rate Limiters Example ===")
|
|
259
|
-
|
|
260
|
-
# Using separate rate limiter instances for different purposes
|
|
261
|
-
debug_limiter = RateLimiter()
|
|
262
|
-
error_limiter = RateLimiter()
|
|
263
|
-
|
|
264
|
-
@rate_limit(every_n=2, key="debug_messages", limiter=debug_limiter)
|
|
265
|
-
def debug_message(msg):
|
|
266
|
-
print(f"DEBUG: {msg}")
|
|
267
|
-
|
|
268
|
-
@rate_limit(every_n=4, key="error_messages", limiter=error_limiter)
|
|
269
|
-
def error_message(msg):
|
|
270
|
-
print(f"ERROR: {msg}")
|
|
271
|
-
|
|
272
|
-
for i in range(10):
|
|
273
|
-
debug_message(f"Debug info {i}")
|
|
274
|
-
if i % 2 == 0:
|
|
275
|
-
error_message(f"Error condition {i}")
|
|
File without changes
|
|
File without changes
|