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.
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}")