vnai 2.0.2__py3-none-any.whl → 2.0.3__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/flow/relay.py CHANGED
@@ -1,152 +1,479 @@
1
- _T='execution_time'
2
- _S='manual'
3
- _R='success'
4
- _Q='is_exceeded'
5
- _P='source'
6
- _O='function'
7
- _N='last_sync_time'
8
- _M='sync_interval'
9
- _L='buffer_size'
10
- _K='webhook_url'
11
- _J='value'
12
- _I='sync_count'
13
- _H='machine_id'
14
- _G=False
15
- _F=None
16
- _E='timestamp'
17
- _D='api_requests'
18
- _C='rate_limits'
19
- _B='function_calls'
20
- _A=True
21
- import time,threading,json,random,requests
1
+ ##
2
+
3
+ ##
4
+
5
+
6
+ import time
7
+ import threading
8
+ import json
9
+ import random
10
+ import requests
22
11
  from datetime import datetime
23
12
  from pathlib import Path
24
- from typing import Dict,List,Any,Optional
13
+ from typing import Dict, List, Any, Optional
14
+
25
15
  class Conduit:
26
- _instance=_F;_lock=threading.Lock()
27
- def __new__(A,webhook_url=_F,buffer_size=50,sync_interval=300):
28
- with A._lock:
29
- if A._instance is _F:A._instance=super(Conduit,A).__new__(A);A._instance._initialize(webhook_url,buffer_size,sync_interval)
30
- return A._instance
31
- def _initialize(A,webhook_url,buffer_size,sync_interval):
32
- A.webhook_url=webhook_url;A.buffer_size=buffer_size;A.sync_interval=sync_interval;A.buffer={_B:[],_D:[],_C:[]};A.lock=threading.Lock();A.last_sync_time=time.time();A.sync_count=0;A.failed_queue=[];A.home_dir=Path.home();A.project_dir=A.home_dir/'.vnstock';A.project_dir.mkdir(exist_ok=_A);A.data_dir=A.project_dir/'data';A.data_dir.mkdir(exist_ok=_A);A.config_path=A.data_dir/'relay_config.json'
33
- try:from vnai.scope.profile import inspector as B;A.machine_id=B.fingerprint()
34
- except:A.machine_id=A._generate_fallback_id()
35
- A._load_config();A._start_periodic_sync()
36
- def _generate_fallback_id(D)->str:
37
- try:import platform as A,hashlib as B,uuid;C=A.node()+A.platform()+A.processor();return B.md5(C.encode()).hexdigest()
38
- except:import uuid;return str(uuid.uuid4())
39
- def _load_config(B):
40
- if B.config_path.exists():
41
- try:
42
- with open(B.config_path,'r')as C:A=json.load(C)
43
- if not B.webhook_url and _K in A:B.webhook_url=A[_K]
44
- if _L in A:B.buffer_size=A[_L]
45
- if _M in A:B.sync_interval=A[_M]
46
- if _N in A:B.last_sync_time=A[_N]
47
- if _I in A:B.sync_count=A[_I]
48
- except:pass
49
- def _save_config(A):
50
- B={_K:A.webhook_url,_L:A.buffer_size,_M:A.sync_interval,_N:A.last_sync_time,_I:A.sync_count}
51
- try:
52
- with open(A.config_path,'w')as C:json.dump(B,C)
53
- except:pass
54
- def _start_periodic_sync(A):
55
- def B():
56
- while _A:time.sleep(A.sync_interval);A.dispatch('periodic')
57
- C=threading.Thread(target=B,daemon=_A);C.start()
58
- def add_function_call(B,record):
59
- A=record
60
- if not isinstance(A,dict):A={_J:str(A)}
61
- with B.lock:B.buffer[_B].append(A);B._check_triggers(_B)
62
- def add_api_request(B,record):
63
- A=record
64
- if not isinstance(A,dict):A={_J:str(A)}
65
- with B.lock:B.buffer[_D].append(A);B._check_triggers(_D)
66
- def add_rate_limit(B,record):
67
- A=record
68
- if not isinstance(A,dict):A={_J:str(A)}
69
- with B.lock:B.buffer[_C].append(A);B._check_triggers(_C)
70
- def _check_triggers(A,record_type:str):
71
- D=record_type;E=time.time();B=_G;C=_F;F=sum(len(A)for A in A.buffer.values())
72
- if F>=A.buffer_size:B=_A;C='buffer_full'
73
- elif D==_C and A.buffer[_C]and any(A.get(_Q)for A in A.buffer[_C]if isinstance(A,dict)):B=_A;C='rate_limit_exceeded'
74
- elif D==_B and A.buffer[_B]and any(not A.get(_R)for A in A.buffer[_B]if isinstance(A,dict)):B=_A;C='function_error'
75
- else:
76
- G=min(1.,(E-A.last_sync_time)/(A.sync_interval/2))
77
- if random.random()<.05*G:B=_A;C='random_time_weighted'
78
- if B:threading.Thread(target=A.dispatch,args=(C,),daemon=_A).start()
79
- def queue(B,package,priority=_F):
80
- N='packages';M='commercial';L='system_info';K='rate_limit';I='system';H='type';C=package
81
- if not C:return _G
82
- if not isinstance(C,dict):B.add_function_call({'message':str(C)});return _A
83
- if _E not in C:C[_E]=datetime.now().isoformat()
84
- if H in C:
85
- D=C[H];A=C.get('data',{})
86
- if isinstance(A,dict)and I in A:
87
- J=A[I].get(_H);A.pop(I)
88
- if J:A[_H]=J
89
- if D==_O:B.add_function_call(A)
90
- elif D=='api_request':B.add_api_request(A)
91
- elif D==K:B.add_rate_limit(A)
92
- elif D==L:B.add_function_call({H:L,M:A.get(M),N:A.get(N),_E:C.get(_E)})
93
- elif D=='metrics':
94
- O=A
95
- for(G,F)in O.items():
96
- if isinstance(F,list):
97
- if G==_O:
98
- for E in F:B.add_function_call(E)
99
- elif G==K:
100
- for E in F:B.add_rate_limit(E)
101
- elif G=='request':
102
- for E in F:B.add_api_request(E)
103
- else:B.add_function_call(A)
104
- else:B.add_function_call(C)
105
- if priority=='high':B.dispatch('high_priority')
106
- return _A
107
- def dispatch(A,reason=_S):
108
- if not A.webhook_url:return _G
109
- with A.lock:
110
- if all(len(A)==0 for A in A.buffer.values()):return _G
111
- B={_B:A.buffer[_B].copy(),_D:A.buffer[_D].copy(),_C:A.buffer[_C].copy()};A.buffer={_B:[],_D:[],_C:[]};A.last_sync_time=time.time();A.sync_count+=1;A._save_config()
112
- try:from vnai.scope.profile import inspector as G;C=G.examine();D=C.get(_H,A.machine_id)
113
- except:C={_H:A.machine_id};D=A.machine_id
114
- E={'analytics_data':B,'metadata':{_E:datetime.now().isoformat(),_H:D,_I:A.sync_count,'trigger_reason':reason,'environment':C,'data_counts':{_B:len(B[_B]),_D:len(B[_D]),_C:len(B[_C])}}};F=A._send_data(E)
115
- if not F:
116
- with A.lock:
117
- A.failed_queue.append(E)
118
- if len(A.failed_queue)>10:A.failed_queue=A.failed_queue[-10:]
119
- return F
120
- def _send_data(A,payload):
121
- if not A.webhook_url:return _G
122
- try:B=requests.post(A.webhook_url,json=payload,timeout=5);return B.status_code==200
123
- except:return _G
124
- def retry_failed(A):
125
- if not A.failed_queue:return 0
126
- with A.lock:D=A.failed_queue.copy();A.failed_queue=[]
127
- B=0
128
- for C in D:
129
- if A._send_data(C):B+=1
130
- else:
131
- with A.lock:A.failed_queue.append(C)
132
- return B
133
- def configure(A,webhook_url):
134
- with A.lock:A.webhook_url=webhook_url;A._save_config();return _A
135
- conduit=Conduit()
136
- def track_function_call(function_name,source,execution_time,success=_A,error=_F,args=_F):
137
- E=error;A=args;C={_O:function_name,_P:source,_T:execution_time,_E:datetime.now().isoformat(),_R:success}
138
- if E:C['error']=E
139
- if A:
140
- B={}
141
- if isinstance(A,dict):
142
- for(F,D)in A.items():
143
- if isinstance(D,(str,int,float,bool)):B[F]=D
144
- else:B[F]=str(type(D))
145
- else:B={_J:str(A)}
146
- C['args']=B
147
- conduit.add_function_call(C)
148
- def track_rate_limit(source,limit_type,limit_value,current_usage,is_exceeded):B=current_usage;A=limit_value;C={_P:source,'limit_type':limit_type,'limit_value':A,'current_usage':B,_Q:is_exceeded,_E:datetime.now().isoformat(),'usage_percentage':B/A*100 if A>0 else 0};conduit.add_rate_limit(C)
149
- def track_api_request(endpoint,source,method,status_code,execution_time,request_size=0,response_size=0):A={'endpoint':endpoint,_P:source,'method':method,'status_code':status_code,_T:execution_time,_E:datetime.now().isoformat(),'request_size':request_size,'response_size':response_size};conduit.add_api_request(A)
150
- def configure(webhook_url):return conduit.configure(webhook_url)
151
- def sync_now():return conduit.dispatch(_S)
152
- def retry_failed():return conduit.retry_failed()
16
+ #--
17
+
18
+ _instance = None
19
+ _lock = threading.Lock()
20
+
21
+ def __new__(cls, webhook_url=None, buffer_size=50, sync_interval=300):
22
+ with cls._lock:
23
+ if cls._instance is None:
24
+ cls._instance = super(Conduit, cls).__new__(cls)
25
+ cls._instance._initialize(webhook_url, buffer_size, sync_interval)
26
+ return cls._instance
27
+
28
+ def _initialize(self, webhook_url, buffer_size, sync_interval):
29
+ #--
30
+ self.webhook_url = webhook_url
31
+ self.buffer_size = buffer_size
32
+ self.sync_interval = sync_interval
33
+
34
+ ##
35
+
36
+ self.buffer = {
37
+ "function_calls": [],
38
+ "api_requests": [],
39
+ "rate_limits": []
40
+ }
41
+
42
+ self.lock = threading.Lock()
43
+ self.last_sync_time = time.time()
44
+ self.sync_count = 0
45
+ self.failed_queue = []
46
+
47
+ ##
48
+
49
+ self.home_dir = Path.home()
50
+ self.project_dir = self.home_dir / ".vnstock"
51
+ self.project_dir.mkdir(exist_ok=True)
52
+ self.data_dir = self.project_dir / 'data'
53
+ self.data_dir.mkdir(exist_ok=True)
54
+ self.config_path = self.data_dir / "relay_config.json"
55
+
56
+ ##
57
+
58
+ try:
59
+ from vnai.scope.profile import inspector
60
+ self.machine_id = inspector.fingerprint()
61
+ except:
62
+ self.machine_id = self._generate_fallback_id()
63
+
64
+ ##
65
+
66
+ self._load_config()
67
+
68
+ ##
69
+
70
+ self._start_periodic_sync()
71
+
72
+ def _generate_fallback_id(self) -> str:
73
+ #--
74
+ try:
75
+ import platform
76
+ import hashlib
77
+ import uuid
78
+
79
+ ##
80
+
81
+ system_info = platform.node() + platform.platform() + platform.processor()
82
+ return hashlib.md5(system_info.encode()).hexdigest()
83
+ except:
84
+ import uuid
85
+ return str(uuid.uuid4())
86
+
87
+ def _load_config(self):
88
+ #--
89
+ if self.config_path.exists():
90
+ try:
91
+ with open(self.config_path, 'r') as f:
92
+ config = json.load(f)
93
+
94
+ if not self.webhook_url and 'webhook_url' in config:
95
+ self.webhook_url = config['webhook_url']
96
+ if 'buffer_size' in config:
97
+ self.buffer_size = config['buffer_size']
98
+ if 'sync_interval' in config:
99
+ self.sync_interval = config['sync_interval']
100
+ if 'last_sync_time' in config:
101
+ self.last_sync_time = config['last_sync_time']
102
+ if 'sync_count' in config:
103
+ self.sync_count = config['sync_count']
104
+ except:
105
+ pass
106
+
107
+ def _save_config(self):
108
+ #--
109
+ config = {
110
+ 'webhook_url': self.webhook_url,
111
+ 'buffer_size': self.buffer_size,
112
+ 'sync_interval': self.sync_interval,
113
+ 'last_sync_time': self.last_sync_time,
114
+ 'sync_count': self.sync_count
115
+ }
116
+
117
+ try:
118
+ with open(self.config_path, 'w') as f:
119
+ json.dump(config, f)
120
+ except:
121
+ pass
122
+
123
+ def _start_periodic_sync(self):
124
+ #--
125
+ def periodic_sync():
126
+ while True:
127
+ time.sleep(self.sync_interval)
128
+ self.dispatch("periodic")
129
+
130
+ sync_thread = threading.Thread(target=periodic_sync, daemon=True)
131
+ sync_thread.start()
132
+
133
+ def add_function_call(self, record):
134
+ #--
135
+ ##
136
+
137
+ if not isinstance(record, dict):
138
+ record = {"value": str(record)}
139
+
140
+ with self.lock:
141
+ self.buffer["function_calls"].append(record)
142
+ self._check_triggers("function_calls")
143
+
144
+ def add_api_request(self, record):
145
+ #--
146
+ ##
147
+
148
+ if not isinstance(record, dict):
149
+ record = {"value": str(record)}
150
+
151
+ with self.lock:
152
+ self.buffer["api_requests"].append(record)
153
+ self._check_triggers("api_requests")
154
+
155
+ def add_rate_limit(self, record):
156
+ #--
157
+ ##
158
+
159
+ if not isinstance(record, dict):
160
+ record = {"value": str(record)}
161
+
162
+ with self.lock:
163
+ self.buffer["rate_limits"].append(record)
164
+ self._check_triggers("rate_limits")
165
+
166
+ def _check_triggers(self, record_type: str):
167
+ #--
168
+ current_time = time.time()
169
+ should_trigger = False
170
+ trigger_reason = None
171
+
172
+ ##
173
+
174
+ total_records = sum(len(buffer) for buffer in self.buffer.values())
175
+
176
+ ##
177
+
178
+ if total_records >= self.buffer_size:
179
+ should_trigger = True
180
+ trigger_reason = "buffer_full"
181
+
182
+ ##
183
+
184
+ elif record_type == "rate_limits" and self.buffer["rate_limits"] and any(item.get("is_exceeded") for item in self.buffer["rate_limits"] if isinstance(item, dict)):
185
+ should_trigger = True
186
+ trigger_reason = "rate_limit_exceeded"
187
+ elif record_type == "function_calls" and self.buffer["function_calls"] and any(not item.get("success") for item in self.buffer["function_calls"] if isinstance(item, dict)):
188
+ should_trigger = True
189
+ trigger_reason = "function_error"
190
+
191
+ ##
192
+
193
+ else:
194
+ time_factor = min(1.0, (current_time - self.last_sync_time) / (self.sync_interval / 2))
195
+ if random.random() < 0.05 * time_factor: ##
196
+
197
+ should_trigger = True
198
+ trigger_reason = "random_time_weighted"
199
+
200
+ if should_trigger:
201
+ threading.Thread(
202
+ target=self.dispatch,
203
+ args=(trigger_reason,),
204
+ daemon=True
205
+ ).start()
206
+
207
+ def queue(self, package, priority=None):
208
+ #--
209
+ if not package:
210
+ return False
211
+
212
+ ##
213
+
214
+ if not isinstance(package, dict):
215
+ self.add_function_call({"message": str(package)})
216
+ return True
217
+
218
+ ##
219
+
220
+ if "timestamp" not in package:
221
+ package["timestamp"] = datetime.now().isoformat()
222
+
223
+ ##
224
+
225
+ if "type" in package:
226
+ package_type = package["type"]
227
+ data = package.get("data", {})
228
+
229
+ ##
230
+
231
+ if isinstance(data, dict) and "system" in data:
232
+ ##
233
+
234
+ machine_id = data["system"].get("machine_id")
235
+ data.pop("system")
236
+ if machine_id:
237
+ data["machine_id"] = machine_id
238
+
239
+ if package_type == "function":
240
+ self.add_function_call(data)
241
+ elif package_type == "api_request":
242
+ self.add_api_request(data)
243
+ elif package_type == "rate_limit":
244
+ self.add_rate_limit(data)
245
+ elif package_type == "system_info":
246
+ ##
247
+
248
+ ##
249
+
250
+ self.add_function_call({
251
+ "type": "system_info",
252
+ "commercial": data.get("commercial"),
253
+ "packages": data.get("packages"),
254
+ "timestamp": package.get("timestamp")
255
+ })
256
+ elif package_type == "metrics":
257
+ ##
258
+
259
+ metrics_data = data
260
+ for metric_type, metrics_list in metrics_data.items():
261
+ if isinstance(metrics_list, list):
262
+ if metric_type == "function":
263
+ for item in metrics_list:
264
+ self.add_function_call(item)
265
+ elif metric_type == "rate_limit":
266
+ for item in metrics_list:
267
+ self.add_rate_limit(item)
268
+ elif metric_type == "request":
269
+ for item in metrics_list:
270
+ self.add_api_request(item)
271
+ else:
272
+ ##
273
+
274
+ self.add_function_call(data)
275
+ else:
276
+ ##
277
+
278
+ self.add_function_call(package)
279
+
280
+ ##
281
+
282
+ if priority == "high":
283
+ self.dispatch("high_priority")
284
+
285
+ return True
286
+
287
+ def dispatch(self, reason="manual"):
288
+ #--
289
+ if not self.webhook_url:
290
+ return False
291
+
292
+ with self.lock:
293
+ ##
294
+
295
+ if all(len(records) == 0 for records in self.buffer.values()):
296
+ return False
297
+
298
+ ##
299
+
300
+ data_to_send = {
301
+ "function_calls": self.buffer["function_calls"].copy(),
302
+ "api_requests": self.buffer["api_requests"].copy(),
303
+ "rate_limits": self.buffer["rate_limits"].copy()
304
+ }
305
+
306
+ ##
307
+
308
+ self.buffer = {
309
+ "function_calls": [],
310
+ "api_requests": [],
311
+ "rate_limits": []
312
+ }
313
+
314
+ ##
315
+
316
+ self.last_sync_time = time.time()
317
+ self.sync_count += 1
318
+ self._save_config()
319
+
320
+ ##
321
+
322
+ try:
323
+ from vnai.scope.profile import inspector
324
+ environment_info = inspector.examine()
325
+ machine_id = environment_info.get("machine_id", self.machine_id)
326
+ except:
327
+ ##
328
+
329
+ environment_info = {"machine_id": self.machine_id}
330
+ machine_id = self.machine_id
331
+
332
+ ##
333
+
334
+ payload = {
335
+ "analytics_data": data_to_send,
336
+ "metadata": {
337
+ "timestamp": datetime.now().isoformat(),
338
+ "machine_id": machine_id,
339
+ "sync_count": self.sync_count,
340
+ "trigger_reason": reason,
341
+ "environment": environment_info,
342
+ "data_counts": {
343
+ "function_calls": len(data_to_send["function_calls"]),
344
+ "api_requests": len(data_to_send["api_requests"]),
345
+ "rate_limits": len(data_to_send["rate_limits"])
346
+ }
347
+ }
348
+ }
349
+
350
+ ##
351
+
352
+ success = self._send_data(payload)
353
+
354
+ if not success:
355
+ with self.lock:
356
+ self.failed_queue.append(payload)
357
+ if len(self.failed_queue) > 10:
358
+ self.failed_queue = self.failed_queue[-10:]
359
+
360
+ return success
361
+
362
+ def _send_data(self, payload):
363
+ #--
364
+ if not self.webhook_url:
365
+ return False
366
+
367
+ try:
368
+ response = requests.post(
369
+ self.webhook_url,
370
+ json=payload,
371
+ timeout=5 ##
372
+
373
+ )
374
+
375
+ return response.status_code == 200
376
+ except:
377
+ return False
378
+
379
+ def retry_failed(self):
380
+ #--
381
+ if not self.failed_queue:
382
+ return 0
383
+
384
+ with self.lock:
385
+ to_retry = self.failed_queue.copy()
386
+ self.failed_queue = []
387
+
388
+ success_count = 0
389
+ for payload in to_retry:
390
+ if self._send_data(payload):
391
+ success_count += 1
392
+ else:
393
+ with self.lock:
394
+ self.failed_queue.append(payload)
395
+
396
+ return success_count
397
+
398
+ def configure(self, webhook_url):
399
+ #--
400
+ with self.lock:
401
+ self.webhook_url = webhook_url
402
+ self._save_config()
403
+ return True
404
+
405
+ ##
406
+
407
+ conduit = Conduit()
408
+
409
+ ##
410
+
411
+ def track_function_call(function_name, source, execution_time, success=True, error=None, args=None):
412
+ #--
413
+ record = {
414
+ "function": function_name,
415
+ "source": source,
416
+ "execution_time": execution_time,
417
+ "timestamp": datetime.now().isoformat(),
418
+ "success": success
419
+ }
420
+
421
+ if error:
422
+ record["error"] = error
423
+
424
+ if args:
425
+ ##
426
+
427
+ sanitized_args = {}
428
+ if isinstance(args, dict):
429
+ for key, value in args.items():
430
+ if isinstance(value, (str, int, float, bool)):
431
+ sanitized_args[key] = value
432
+ else:
433
+ sanitized_args[key] = str(type(value))
434
+ else:
435
+ sanitized_args = {"value": str(args)}
436
+ record["args"] = sanitized_args
437
+
438
+ conduit.add_function_call(record)
439
+
440
+ def track_rate_limit(source, limit_type, limit_value, current_usage, is_exceeded):
441
+ #--
442
+ record = {
443
+ "source": source,
444
+ "limit_type": limit_type,
445
+ "limit_value": limit_value,
446
+ "current_usage": current_usage,
447
+ "is_exceeded": is_exceeded,
448
+ "timestamp": datetime.now().isoformat(),
449
+ "usage_percentage": (current_usage / limit_value) * 100 if limit_value > 0 else 0
450
+ }
451
+
452
+ conduit.add_rate_limit(record)
453
+
454
+ def track_api_request(endpoint, source, method, status_code, execution_time, request_size=0, response_size=0):
455
+ #--
456
+ record = {
457
+ "endpoint": endpoint,
458
+ "source": source,
459
+ "method": method,
460
+ "status_code": status_code,
461
+ "execution_time": execution_time,
462
+ "timestamp": datetime.now().isoformat(),
463
+ "request_size": request_size,
464
+ "response_size": response_size
465
+ }
466
+
467
+ conduit.add_api_request(record)
468
+
469
+ def configure(webhook_url):
470
+ #--
471
+ return conduit.configure(webhook_url)
472
+
473
+ def sync_now():
474
+ #--
475
+ return conduit.dispatch("manual")
476
+
477
+ def retry_failed():
478
+ #--
479
+ return conduit.retry_failed()
vnai/scope/__init__.py CHANGED
@@ -1,4 +1,9 @@
1
+ ##
2
+
3
+ ##
4
+
5
+
1
6
  from vnai.scope.profile import inspector
2
- from vnai.scope.state import tracker,record
7
+ from vnai.scope.state import tracker, record
3
8
  from vnai.scope.promo import manager as content_manager
4
- from vnai.scope.promo import present as present_content
9
+ from vnai.scope.promo import present as present_content