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