vnai 2.0.3__py3-none-any.whl → 2.0.5__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.
vnai/beam/quota.py CHANGED
@@ -1,7 +1,5 @@
1
- ##
2
-
3
- ##
4
-
1
+ # vnai/beam/quota.py
2
+ # Resource allocation and management (formerly rate_limiter)
5
3
 
6
4
  import time
7
5
  import functools
@@ -10,7 +8,7 @@ from collections import defaultdict
10
8
  from datetime import datetime
11
9
 
12
10
  class RateLimitExceeded(Exception):
13
- #--
11
+ """Custom exception for rate limit violations."""
14
12
  def __init__(self, resource_type, limit_type="min", current_usage=None, limit_value=None, retry_after=None):
15
13
  self.resource_type = resource_type
16
14
  self.limit_type = limit_type
@@ -18,8 +16,7 @@ class RateLimitExceeded(Exception):
18
16
  self.limit_value = limit_value
19
17
  self.retry_after = retry_after
20
18
 
21
- ##
22
-
19
+ # Create a user-friendly message
23
20
  message = f"Bạn đã gửi quá nhiều request tới {resource_type}. "
24
21
  if retry_after:
25
22
  message += f"Vui lòng thử lại sau {round(retry_after)} giây."
@@ -29,7 +26,7 @@ class RateLimitExceeded(Exception):
29
26
  super().__init__(message)
30
27
 
31
28
  class Guardian:
32
- #--
29
+ """Ensures optimal resource allocation"""
33
30
 
34
31
  _instance = None
35
32
  _lock = threading.Lock()
@@ -42,16 +39,18 @@ class Guardian:
42
39
  return cls._instance
43
40
 
44
41
  def _initialize(self):
45
- #--
42
+ """Initialize guardian"""
46
43
  self.resource_limits = defaultdict(lambda: defaultdict(int))
47
44
  self.usage_counters = defaultdict(lambda: defaultdict(list))
48
45
 
49
- ##
50
-
46
+ # Define resource limits
51
47
  self.resource_limits["default"] = {"min": 60, "hour": 3000}
52
48
  self.resource_limits["TCBS"] = {"min": 60, "hour": 3000}
53
49
  self.resource_limits["VCI"] = {"min": 60, "hour": 3000}
50
+ self.resource_limits["MBK"] = {"min": 600, "hour": 36000}
51
+ self.resource_limits["MAS.ext"] = {"min": 600, "hour": 36000}
54
52
  self.resource_limits["VCI.ext"] = {"min": 600, "hour": 36000}
53
+ self.resource_limits["FMK.ext"] = {"min": 600, "hour": 36000}
55
54
  self.resource_limits["VND.ext"] = {"min": 600, "hour": 36000}
56
55
  self.resource_limits["CAF.ext"] = {"min": 600, "hour": 36000}
57
56
  self.resource_limits["SPL.ext"] = {"min": 600, "hour": 36000}
@@ -59,15 +58,13 @@ class Guardian:
59
58
  self.resource_limits["FAD.ext"] = {"min": 600, "hour": 36000}
60
59
 
61
60
  def verify(self, operation_id, resource_type="default"):
62
- #--
61
+ """Verify resource availability before operation"""
63
62
  current_time = time.time()
64
63
 
65
- ##
66
-
64
+ # Get limits for this resource type (or use default)
67
65
  limits = self.resource_limits.get(resource_type, self.resource_limits["default"])
68
66
 
69
- ##
70
-
67
+ # Check minute limit
71
68
  minute_cutoff = current_time - 60
72
69
  self.usage_counters[resource_type]["min"] = [
73
70
  t for t in self.usage_counters[resource_type]["min"]
@@ -78,8 +75,7 @@ class Guardian:
78
75
  minute_exceeded = minute_usage >= limits["min"]
79
76
 
80
77
  if minute_exceeded:
81
- ##
82
-
78
+ # Track limit check through metrics module
83
79
  from vnai.beam.metrics import collector
84
80
  collector.record(
85
81
  "rate_limit",
@@ -92,19 +88,16 @@ class Guardian:
92
88
  },
93
89
  priority="high"
94
90
  )
95
- ##
96
-
91
+ # Raise custom exception with retry information
97
92
  raise RateLimitExceeded(
98
93
  resource_type=resource_type,
99
94
  limit_type="min",
100
95
  current_usage=minute_usage,
101
96
  limit_value=limits["min"],
102
- retry_after=60 - (current_time % 60) ##
103
-
97
+ retry_after=60 - (current_time % 60) # Seconds until the minute rolls over
104
98
  )
105
99
 
106
- ##
107
-
100
+ # Check hour limit
108
101
  hour_cutoff = current_time - 3600
109
102
  self.usage_counters[resource_type]["hour"] = [
110
103
  t for t in self.usage_counters[resource_type]["hour"]
@@ -114,8 +107,7 @@ class Guardian:
114
107
  hour_usage = len(self.usage_counters[resource_type]["hour"])
115
108
  hour_exceeded = hour_usage >= limits["hour"]
116
109
 
117
- ##
118
-
110
+ # Track rate limit check
119
111
  from vnai.beam.metrics import collector
120
112
  collector.record(
121
113
  "rate_limit",
@@ -129,30 +121,26 @@ class Guardian:
129
121
  )
130
122
 
131
123
  if hour_exceeded:
132
- ##
133
-
124
+ # Raise custom exception with retry information
134
125
  raise RateLimitExceeded(
135
126
  resource_type=resource_type,
136
127
  limit_type="hour",
137
128
  current_usage=hour_usage,
138
129
  limit_value=limits["hour"],
139
- retry_after=3600 - (current_time % 3600) ##
140
-
130
+ retry_after=3600 - (current_time % 3600) # Seconds until the hour rolls over
141
131
  )
142
132
 
143
- ##
144
-
133
+ # Record this request
145
134
  self.usage_counters[resource_type]["min"].append(current_time)
146
135
  self.usage_counters[resource_type]["hour"].append(current_time)
147
136
  return True
148
137
 
149
138
  def usage(self, resource_type="default"):
150
- #--
139
+ """Get current usage percentage for resource limits"""
151
140
  current_time = time.time()
152
141
  limits = self.resource_limits.get(resource_type, self.resource_limits["default"])
153
142
 
154
- ##
155
-
143
+ # Clean old timestamps
156
144
  minute_cutoff = current_time - 60
157
145
  hour_cutoff = current_time - 3600
158
146
 
@@ -166,25 +154,22 @@ class Guardian:
166
154
  if t > hour_cutoff
167
155
  ]
168
156
 
169
- ##
170
-
157
+ # Calculate percentages
171
158
  minute_usage = len(self.usage_counters[resource_type]["min"])
172
159
  hour_usage = len(self.usage_counters[resource_type]["hour"])
173
160
 
174
161
  minute_percentage = (minute_usage / limits["min"]) * 100 if limits["min"] > 0 else 0
175
162
  hour_percentage = (hour_usage / limits["hour"]) * 100 if limits["hour"] > 0 else 0
176
163
 
177
- ##
178
-
164
+ # Return the higher percentage
179
165
  return max(minute_percentage, hour_percentage)
180
166
 
181
167
  def get_limit_status(self, resource_type="default"):
182
- #--
168
+ """Get detailed information about current limit status"""
183
169
  current_time = time.time()
184
170
  limits = self.resource_limits.get(resource_type, self.resource_limits["default"])
185
171
 
186
- ##
187
-
172
+ # Clean old timestamps
188
173
  minute_cutoff = current_time - 60
189
174
  hour_cutoff = current_time - 3600
190
175
 
@@ -209,17 +194,14 @@ class Guardian:
209
194
  }
210
195
  }
211
196
 
212
- ##
213
-
197
+ # Create singleton instance
214
198
  guardian = Guardian()
215
199
 
216
200
  class CleanErrorContext:
217
- #--
218
- ##
219
-
201
+ """Context manager to clean up tracebacks for rate limits"""
202
+ # Class variable to track if a message has been displayed recently
220
203
  _last_message_time = 0
221
- _message_cooldown = 5 ##
222
-
204
+ _message_cooldown = 5 # Only show a message every 5 seconds
223
205
 
224
206
  def __enter__(self):
225
207
  return self
@@ -228,37 +210,64 @@ class CleanErrorContext:
228
210
  if exc_type is RateLimitExceeded:
229
211
  current_time = time.time()
230
212
 
231
- ##
232
-
213
+ # Only print the message if enough time has passed since the last one
233
214
  if current_time - CleanErrorContext._last_message_time >= CleanErrorContext._message_cooldown:
234
215
  print(f"\n⚠️ {str(exc_val)}\n")
235
216
  CleanErrorContext._last_message_time = current_time
236
217
 
237
- ##
238
-
239
- ##
240
-
218
+ # Re-raise the exception more forcefully to ensure it propagates
219
+ # This will bypass any try/except blocks that might be catching RateLimitExceeded
241
220
  import sys
242
221
  sys.exit(f"Rate limit exceeded. {str(exc_val)} Process terminated.")
243
222
 
244
- ##
245
-
223
+ # The line below won't be reached, but we keep it for clarity
246
224
  return False
247
225
  return False
248
226
 
249
227
 
250
228
  def optimize(resource_type='default', loop_threshold=10, time_window=5, ad_cooldown=150, content_trigger_threshold=3,
251
229
  max_retries=2, backoff_factor=2, debug=False):
252
- #--
253
- ##
254
-
230
+ """
231
+ Decorator that optimizes function execution, tracks metrics, and detects loop patterns for ad opportunities.
232
+
233
+ Features:
234
+ - Resource verification
235
+ - Performance metrics collection
236
+ - Loop detection for ad/content opportunities
237
+ - Automatic retry with exponential backoff for rate limit errors
238
+
239
+ Args:
240
+ resource_type: Type of resource used by function ("network", "database", "cpu", "memory", "io", "default")
241
+ loop_threshold: Number of calls within time_window to consider as a loop (min: 2)
242
+ time_window: Time period in seconds to consider for loop detection
243
+ ad_cooldown: Minimum seconds between showing ads for the same function
244
+ content_trigger_threshold: Number of consecutive loop detections before triggering content (min: 1)
245
+ max_retries: Maximum number of times to retry when rate limits are hit
246
+ backoff_factor: Base factor for exponential backoff (wait time = backoff_factor^retry_count)
247
+ debug: When True, prints diagnostic information about loop detection
248
+
249
+ Examples:
250
+ @optimize
251
+ def simple_function():
252
+ return "result"
253
+
254
+ @optimize("network")
255
+ def fetch_stock_data(symbol):
256
+ # Makes network calls
257
+ return data
258
+
259
+ @optimize("database", loop_threshold=4, time_window=10)
260
+ def query_financial_data(params):
261
+ # Database queries
262
+ return results
263
+ """
264
+ # Handle case where decorator is used without arguments: @optimize
255
265
  if callable(resource_type):
256
266
  func = resource_type
257
267
  return _create_wrapper(func, 'default', loop_threshold, time_window, ad_cooldown, content_trigger_threshold,
258
268
  max_retries, backoff_factor, debug)
259
269
 
260
- ##
261
-
270
+ # Basic validation
262
271
  if loop_threshold < 2:
263
272
  raise ValueError(f"loop_threshold must be at least 2, got {loop_threshold}")
264
273
  if time_window <= 0:
@@ -270,8 +279,7 @@ def optimize(resource_type='default', loop_threshold=10, time_window=5, ad_coold
270
279
  if backoff_factor <= 0:
271
280
  raise ValueError(f"backoff_factor must be positive, got {backoff_factor}")
272
281
 
273
- ##
274
-
282
+ # Return the actual decorator
275
283
  def decorator(func):
276
284
  return _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldown, content_trigger_threshold,
277
285
  max_retries, backoff_factor, debug)
@@ -279,17 +287,14 @@ def optimize(resource_type='default', loop_threshold=10, time_window=5, ad_coold
279
287
 
280
288
  def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldown, content_trigger_threshold,
281
289
  max_retries, backoff_factor, debug):
282
- #--
283
- ##
284
-
290
+ """Creates the function wrapper with call tracking for loop detection"""
291
+ # Static storage for each decorated function instance
285
292
  call_history = []
286
293
  last_ad_time = 0
287
294
  consecutive_loop_detections = 0
288
- session_displayed = False ##
289
-
295
+ session_displayed = False # Track if we've displayed an ad in this session
290
296
  session_start_time = time.time()
291
- session_timeout = 1800 ##
292
-
297
+ session_timeout = 1800 # 30 minutes for session expiration
293
298
 
294
299
  @functools.wraps(func)
295
300
  def wrapper(*args, **kwargs):
@@ -297,36 +302,29 @@ def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldow
297
302
  current_time = time.time()
298
303
  content_triggered = False
299
304
 
300
- ##
301
-
305
+ # Reset session if it has expired
302
306
  if current_time - session_start_time > session_timeout:
303
307
  session_displayed = False
304
308
  session_start_time = current_time
305
309
 
306
- ##
307
-
310
+ # For automatic retries with rate limits
308
311
  retries = 0
309
312
  while True:
310
- ##
311
-
312
- ##
313
-
313
+ # ===== LOOP DETECTION LOGIC =====
314
+ # Add current call to history
314
315
  call_history.append(current_time)
315
316
 
316
- ##
317
-
317
+ # Prune old calls outside the time window
318
318
  while call_history and current_time - call_history[0] > time_window:
319
319
  call_history.pop(0)
320
320
 
321
- ##
322
-
321
+ # Check if we're in a loop pattern
323
322
  loop_detected = len(call_history) >= loop_threshold
324
323
 
325
324
  if debug and loop_detected:
326
325
  print(f"[OPTIMIZE] Đã phát hiện vòng lặp cho {func.__name__}: {len(call_history)} lần gọi trong {time_window}s")
327
326
 
328
- ##
329
-
327
+ # Handle loop detection
330
328
  if loop_detected:
331
329
  consecutive_loop_detections += 1
332
330
  if debug:
@@ -334,29 +332,26 @@ def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldow
334
332
  else:
335
333
  consecutive_loop_detections = 0
336
334
 
337
- ##
338
-
339
- should_show_content = (consecutive_loop_detections >= content_trigger_threshold) and (current_time - last_ad_time >= ad_cooldown) and not session_displayed
335
+ # Determine if we should show content - add session_displayed check
336
+ should_show_content = (consecutive_loop_detections >= content_trigger_threshold) and \
337
+ (current_time - last_ad_time >= ad_cooldown) and \
338
+ not session_displayed
340
339
 
341
- ##
342
-
340
+ # Handle content opportunity
343
341
  if should_show_content:
344
342
  last_ad_time = current_time
345
343
  consecutive_loop_detections = 0
346
344
  content_triggered = True
347
- session_displayed = True ##
348
-
345
+ session_displayed = True # Mark that we've displayed in this session
349
346
 
350
347
  if debug:
351
348
  print(f"[OPTIMIZE] Đã kích hoạt nội dung cho {func.__name__}")
352
349
 
353
- ##
354
-
350
+ # Trigger content display using promo manager with "loop" context
355
351
  try:
356
352
  from vnai.scope.promo import manager
357
353
 
358
- ##
359
-
354
+ # Get environment if available
360
355
  try:
361
356
  from vnai.scope.profile import inspector
362
357
  environment = inspector.examine().get("environment", None)
@@ -365,26 +360,21 @@ def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldow
365
360
  manager.present_content(context="loop")
366
361
 
367
362
  except ImportError:
368
- ##
369
-
363
+ # Fallback if content manager is not available
370
364
  print(f"Phát hiện vòng lặp: Hàm '{func.__name__}' đang được gọi trong một vòng lặp")
371
365
  except Exception as e:
372
- ##
373
-
366
+ # Don't let content errors affect the main function
374
367
  if debug:
375
368
  print(f"[OPTIMIZE] Lỗi khi hiển thị nội dung: {str(e)}")
376
369
 
377
- ##
378
-
370
+ # ===== RESOURCE VERIFICATION =====
379
371
  try:
380
- ##
381
-
372
+ # Use a context manager to clean up the traceback
382
373
  with CleanErrorContext():
383
374
  guardian.verify(func.__name__, resource_type)
384
375
 
385
376
  except RateLimitExceeded as e:
386
- ##
387
-
377
+ # Record the rate limit error
388
378
  from vnai.beam.metrics import collector
389
379
  collector.record(
390
380
  "error",
@@ -398,8 +388,7 @@ def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldow
398
388
  priority="high"
399
389
  )
400
390
 
401
- ##
402
-
391
+ # Display rate limit content ONLY if we haven't shown any content this session
403
392
  if not session_displayed:
404
393
  try:
405
394
  from vnai.scope.promo import manager
@@ -407,25 +396,21 @@ def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldow
407
396
  from vnai.scope.profile import inspector
408
397
  environment = inspector.examine().get("environment", None)
409
398
  manager.present_content(environment=environment, context="loop")
410
- session_displayed = True ##
411
-
399
+ session_displayed = True # Mark that we've displayed
412
400
  last_ad_time = current_time
413
401
  except ImportError:
414
402
  manager.present_content(context="loop")
415
403
  session_displayed = True
416
404
  last_ad_time = current_time
417
405
  except Exception:
418
- pass ##
419
-
406
+ pass # Don't let content errors affect the retry logic
420
407
 
421
- ##
422
-
408
+ # Continue with retry logic
423
409
  if retries < max_retries:
424
410
  wait_time = backoff_factor ** retries
425
411
  retries += 1
426
412
 
427
- ##
428
-
413
+ # If the exception has a retry_after value, use that instead
429
414
  if hasattr(e, "retry_after") and e.retry_after:
430
415
  wait_time = min(wait_time, e.retry_after)
431
416
 
@@ -433,22 +418,18 @@ def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldow
433
418
  print(f"[OPTIMIZE] Đã đạt giới hạn tốc độ cho {func.__name__}, thử lại sau {wait_time} giây (lần thử {retries}/{max_retries})")
434
419
 
435
420
  time.sleep(wait_time)
436
- continue ##
437
-
421
+ continue # Retry the call
438
422
  else:
439
- ##
440
-
423
+ # No more retries, re-raise the exception
441
424
  raise
442
425
 
443
- ##
444
-
426
+ # ===== FUNCTION EXECUTION & METRICS =====
445
427
  start_time = time.time()
446
428
  success = False
447
429
  error = None
448
430
 
449
431
  try:
450
- ##
451
-
432
+ # Execute the original function
452
433
  result = func(*args, **kwargs)
453
434
  success = True
454
435
  return result
@@ -456,12 +437,10 @@ def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldow
456
437
  error = str(e)
457
438
  raise
458
439
  finally:
459
- ##
460
-
440
+ # Calculate execution metrics
461
441
  execution_time = time.time() - start_time
462
442
 
463
- ##
464
-
443
+ # Record metrics
465
444
  try:
466
445
  from vnai.beam.metrics import collector
467
446
  collector.record(
@@ -480,8 +459,7 @@ def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldow
480
459
  }
481
460
  )
482
461
 
483
- ##
484
-
462
+ # Record content opportunity metrics if detected
485
463
  if content_triggered:
486
464
  collector.record(
487
465
  "ad_opportunity",
@@ -494,19 +472,16 @@ def _create_wrapper(func, resource_type, loop_threshold, time_window, ad_cooldow
494
472
  }
495
473
  )
496
474
  except ImportError:
497
- ##
498
-
475
+ # Metrics module not available, just continue
499
476
  pass
500
477
 
501
- ##
502
-
478
+ # If we got here, the function executed successfully, so break the retry loop
503
479
  break
504
480
 
505
481
  return wrapper
506
482
 
507
483
 
508
- ##
509
-
484
+ # Helper function for getting the current rate limit status
510
485
  def rate_limit_status(resource_type="default"):
511
- #--
486
+ """Get the current rate limit status for a resource type"""
512
487
  return guardian.get_limit_status(resource_type)
vnai/flow/__init__.py CHANGED
@@ -1,7 +1,5 @@
1
- ##
2
-
3
- ##
4
-
1
+ # vnai/flow/__init__.py
2
+ # Data flow and transmission management
5
3
 
6
4
  from vnai.flow.relay import conduit, configure
7
5
  from vnai.flow.queue import buffer
vnai/flow/queue.py CHANGED
@@ -1,7 +1,5 @@
1
- ##
2
-
3
- ##
4
-
1
+ # vnai/flow/queue.py
2
+ # Data buffering system
5
3
 
6
4
  import time
7
5
  import threading
@@ -10,7 +8,7 @@ from datetime import datetime
10
8
  from pathlib import Path
11
9
 
12
10
  class Buffer:
13
- #--
11
+ """Manages data buffering with persistence"""
14
12
 
15
13
  _instance = None
16
14
  _lock = threading.Lock()
@@ -23,15 +21,13 @@ class Buffer:
23
21
  return cls._instance
24
22
 
25
23
  def _initialize(self):
26
- #--
24
+ """Initialize buffer"""
27
25
  self.data = []
28
26
  self.lock = threading.Lock()
29
27
  self.max_size = 1000
30
- self.backup_interval = 300 ##
31
-
28
+ self.backup_interval = 300 # 5 minutes
32
29
 
33
- ##
34
-
30
+ # Setup data directory
35
31
  self.home_dir = Path.home()
36
32
  self.project_dir = self.home_dir / ".vnstock"
37
33
  self.project_dir.mkdir(exist_ok=True)
@@ -39,16 +35,14 @@ class Buffer:
39
35
  self.data_dir.mkdir(exist_ok=True)
40
36
  self.backup_path = self.data_dir / "buffer_backup.json"
41
37
 
42
- ##
43
-
38
+ # Load from backup if exists
44
39
  self._load_from_backup()
45
40
 
46
- ##
47
-
41
+ # Start backup thread
48
42
  self._start_backup_thread()
49
43
 
50
44
  def _load_from_backup(self):
51
- #--
45
+ """Load data from backup file"""
52
46
  if self.backup_path.exists():
53
47
  try:
54
48
  with open(self.backup_path, 'r') as f:
@@ -60,7 +54,7 @@ class Buffer:
60
54
  pass
61
55
 
62
56
  def _save_to_backup(self):
63
- #--
57
+ """Save data to backup file"""
64
58
  with self.lock:
65
59
  if not self.data:
66
60
  return
@@ -72,7 +66,7 @@ class Buffer:
72
66
  pass
73
67
 
74
68
  def _start_backup_thread(self):
75
- #--
69
+ """Start background backup thread"""
76
70
  def backup_task():
77
71
  while True:
78
72
  time.sleep(self.backup_interval)
@@ -82,34 +76,30 @@ class Buffer:
82
76
  backup_thread.start()
83
77
 
84
78
  def add(self, item, category=None):
85
- #--
79
+ """Add item to buffer"""
86
80
  with self.lock:
87
- ##
88
-
81
+ # Add metadata
89
82
  if isinstance(item, dict):
90
83
  if "timestamp" not in item:
91
84
  item["timestamp"] = datetime.now().isoformat()
92
85
  if category:
93
86
  item["category"] = category
94
87
 
95
- ##
96
-
88
+ # Add to buffer
97
89
  self.data.append(item)
98
90
 
99
- ##
100
-
91
+ # Trim if exceeds max size
101
92
  if len(self.data) > self.max_size:
102
93
  self.data = self.data[-self.max_size:]
103
94
 
104
- ##
105
-
95
+ # Save to backup if buffer gets large
106
96
  if len(self.data) % 100 == 0:
107
97
  self._save_to_backup()
108
98
 
109
99
  return len(self.data)
110
100
 
111
101
  def get(self, count=None, category=None):
112
- #--
102
+ """Get items from buffer with optional filtering"""
113
103
  with self.lock:
114
104
  if category:
115
105
  filtered_data = [item for item in self.data if item.get("category") == category]
@@ -122,7 +112,7 @@ class Buffer:
122
112
  return filtered_data
123
113
 
124
114
  def clear(self, category=None):
125
- #--
115
+ """Clear buffer, optionally by category"""
126
116
  with self.lock:
127
117
  if category:
128
118
  self.data = [item for item in self.data if item.get("category") != category]
@@ -133,13 +123,12 @@ class Buffer:
133
123
  return len(self.data)
134
124
 
135
125
  def size(self, category=None):
136
- #--
126
+ """Get buffer size, optionally by category"""
137
127
  with self.lock:
138
128
  if category:
139
129
  return len([item for item in self.data if item.get("category") == category])
140
130
  else:
141
131
  return len(self.data)
142
132
 
143
- ##
144
-
133
+ # Create singleton instance
145
134
  buffer = Buffer()