vnai 2.1.3__py3-none-any.whl → 2.1.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/scope/promo.py CHANGED
@@ -1,91 +1,191 @@
1
- _G='init'
2
- _F='terminal'
3
- _E='markdown'
4
- _D='html'
5
- _C=True
6
- _B=False
7
- _A=None
8
- import logging,requests
1
+ import logging
2
+ import requests
9
3
  from datetime import datetime
10
- import random,threading,time,urllib.parse
11
- _vnii_check_attempted=_B
12
- class AdCategory:FREE=0;MANDATORY=1;ANNOUNCEMENT=2;REFERRAL=3;FEATURE=4;GUIDE=5;SURVEY=6;PROMOTION=7;SECURITY=8;MAINTENANCE=9;WARNING=10
13
- try:from vnii import lc_init
14
- except ImportError:lc_init=_A
15
- logger=logging.getLogger(__name__)
16
- if not logger.hasHandlers():handler=logging.StreamHandler();handler.setFormatter(logging.Formatter('%(message)s'));logger.addHandler(handler);logger.setLevel(logging.ERROR)
4
+ import random
5
+ import threading
6
+ import time
7
+ import urllib.parse
8
+ _vnii_check_attempted = False
9
+
10
+ class AdCategory:
11
+ FREE = 0
12
+ MANDATORY = 1
13
+ ANNOUNCEMENT = 2
14
+ REFERRAL = 3
15
+ FEATURE = 4
16
+ GUIDE = 5
17
+ SURVEY = 6
18
+ PROMOTION = 7
19
+ SECURITY = 8
20
+ MAINTENANCE = 9
21
+ WARNING = 10
22
+ try:
23
+ from vnii import lc_init
24
+ except ImportError:
25
+ lc_init = None
26
+ logger = logging.getLogger(__name__)
27
+ if not logger.hasHandlers():
28
+ handler = logging.StreamHandler()
29
+ handler.setFormatter(logging.Formatter('%(message)s'))
30
+ logger.addHandler(handler)
31
+ logger.setLevel(logging.ERROR)
32
+
17
33
  class ContentManager:
18
- _instance=_A;_lock=threading.Lock()
19
- def __new__(cls):
20
- with cls._lock:
21
- if cls._instance is _A:cls._instance=super(ContentManager,cls).__new__(cls);cls._instance._initialize()
22
- return cls._instance
23
- def _initialize(self):
24
- B='https://hq.vnstocks.com/content-delivery';A='vnii';self.content_base_url=B;global _vnii_check_attempted
25
- if _vnii_check_attempted:return
26
- _vnii_check_attempted=_C;import sys,importlib
27
- try:
28
- import importlib.metadata
29
- try:
30
- old_version=importlib.metadata.version(A);VNII_LATEST_VERSION='0.0.9';VNII_URL=f"https://github.com/vnstock-hq/licensing/releases/download/vnii-{VNII_LATEST_VERSION}/vnii-{VNII_LATEST_VERSION}.tar.gz";import subprocess
31
- try:
32
- subprocess.check_call([sys.executable,'-m','pip','install',f"vnii@{VNII_URL}"]);importlib.invalidate_caches()
33
- if A in sys.modules:importlib.reload(sys.modules[A])
34
- else:import vnii
35
- new_version=importlib.metadata.version(A)
36
- except Exception as e:logger.error(f"Lỗi khi cài đặt vnii: {e}");pass
37
- except importlib.metadata.PackageNotFoundError:pass;self.is_paid_user=_B;return
38
- except Exception as e:
39
- logger.error(f"Lỗi khi kiểm tra/cài đặt vnii: {e}");user_msg=f"Không thể tự động cài đặt/cập nhật vnii. Vui lòng liên hệ admin hoặc hỗ trợ kỹ thuật của Vnstock để được trợ giúp. Chi tiết lỗi: {e}";logger.error(user_msg)
40
- try:print(user_msg)
41
- except Exception:pass
42
- self.is_paid_user=_B;return
43
- self.is_paid_user=_B
44
- if lc_init is not _A:
45
- try:
46
- license_info=lc_init(repo_name='vnstock');status=license_info.get('status','').lower()
47
- if'recognized and verified'in status:self.is_paid_user=_C
48
- except Exception as e:pass
49
- else:0
50
- self.last_display=0;self.display_interval=86400;self.content_base_url=B;self.target_url='https://vnstocks.com/lp-khoa-hoc-python-chung-khoan';self.image_url='https://vnstocks.com/img/trang-chu-vnstock-python-api-phan-tich-giao-dich-chung-khoan.jpg';self._start_periodic_display()
51
- def _start_periodic_display(self):
52
- def periodic_display():
53
- while _C:
54
- if self.is_paid_user:break
55
- sleep_time=random.randint(7200,21600);time.sleep(sleep_time);current_time=time.time()
56
- if current_time-self.last_display>=self.display_interval:self.present_content(context='periodic')
57
- else:0
58
- thread=threading.Thread(target=periodic_display,daemon=_C);thread.start()
59
- def fetch_remote_content(self,context:str=_G,html:bool=_C)->str:
60
- if self.is_paid_user:return''
61
- try:
62
- params={'context':context,_D:'true'if html else'false'};url=f"{self.content_base_url}?{urllib.parse.urlencode(params)}";response=requests.get(url,timeout=3)
63
- if response.status_code==200:return response.text
64
- logger.error(f"Non-200 response fetching content: {response.status_code}");return
65
- except Exception as e:logger.error(f"Failed to fetch remote content: {e}");return
66
- def present_content(self,context:str=_G,ad_category:int=AdCategory.FREE)->_A:
67
- C='jupyter';B='unknown';A='Hiển thị quảng cáo';environment=_A
68
- if getattr(self,'is_paid_user',_B)and ad_category==AdCategory.FREE:return
69
- self.last_display=time.time()
70
- if environment is _A:
71
- try:from vnai.scope.profile import inspector;environment=inspector.examine().get('environment',B)
72
- except Exception as e:logger.error(f"Không detect được environment: {e}");environment=B
73
- remote_content=self.fetch_remote_content(context=context,html=environment==C);fallback=self._generate_fallback_content(context)
74
- if environment==C:
75
- try:
76
- from IPython.display import display,HTML,Markdown
77
- if remote_content:display(HTML(remote_content))
78
- else:
79
- try:display(Markdown(fallback[_E]))
80
- except Exception as e:display(HTML(fallback[_D]))
81
- except Exception as e:pass
82
- elif environment==_F:
83
- if remote_content:logger.error(A)
84
- else:logger.error(A)
85
- else:logger.error(A)
86
- def _generate_fallback_content(self,context):
87
- A='simple';fallback={_D:'',_E:'',_F:'',A:''}
88
- if context=='loop':fallback[_D]=f'''
34
+ _instance = None
35
+ _lock = threading.Lock()
36
+
37
+ def __new__(cls):
38
+ with cls._lock:
39
+ if cls._instance is None:
40
+ cls._instance = super(ContentManager, cls).__new__(cls)
41
+ cls._instance._initialize()
42
+ return cls._instance
43
+
44
+ def _initialize(self):
45
+ self.content_base_url ="https://hq.vnstocks.com/content-delivery"
46
+ global _vnii_check_attempted
47
+ if _vnii_check_attempted:
48
+ return
49
+ _vnii_check_attempted = True
50
+ import sys
51
+ import importlib
52
+ try:
53
+ import importlib.metadata
54
+ VNII_LATEST_VERSION ="0.0.9"
55
+ VNII_URL =f"https://github.com/vnstock-hq/licensing/releases/download/vnii-{VNII_LATEST_VERSION}/vnii-{VNII_LATEST_VERSION}.tar.gz"
56
+ import subprocess
57
+ try:
58
+ old_version = importlib.metadata.version("vnii")
59
+ if old_version != VNII_LATEST_VERSION:
60
+ try:
61
+ subprocess.check_call([
62
+ sys.executable,"-m","pip","install",f"vnii@{VNII_URL}","--quiet"
63
+ ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
64
+ importlib.invalidate_caches()
65
+ if"vnii" in sys.modules:
66
+ importlib.reload(sys.modules["vnii"])
67
+ else:
68
+ import vnii
69
+ new_version = importlib.metadata.version("vnii")
70
+ except Exception as e:
71
+ logger.error(f"Lỗi khi cài đặt vnii: {e}")
72
+ pass
73
+ except importlib.metadata.PackageNotFoundError:
74
+ self.is_paid_user = False
75
+ return
76
+ except Exception as e:
77
+ logger.error(f"Lỗi khi kiểm tra/cài đặt vnii: {e}")
78
+ user_msg = (
79
+ "Không thể tự động cài đặt/cập nhật vnii. "
80
+ "Vui lòng liên hệ admin hoặc hỗ trợ kỹ thuật của Vnstock để được trợ giúp. "
81
+ f"Chi tiết lỗi: {e}"
82
+ )
83
+ logger.error(user_msg)
84
+ try:
85
+ print(user_msg)
86
+ except Exception:
87
+ pass
88
+ self.is_paid_user = False
89
+ return
90
+ self.is_paid_user = False
91
+ if lc_init is not None:
92
+ try:
93
+ license_info = lc_init(repo_name='vnstock')
94
+ status = license_info.get('status','').lower()
95
+ if'recognized and verified' in status:
96
+ self.is_paid_user = True
97
+ except Exception as e:
98
+ pass
99
+ else:
100
+ pass
101
+ self.last_display = 0
102
+ self.display_interval = 24 * 3600
103
+ self.content_base_url ="https://hq.vnstocks.com/content-delivery"
104
+ self.target_url ="https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
105
+ self.image_url = (
106
+ "https://vnstocks.com/img/trang-chu-vnstock-python-api-phan-tich-giao-dich-chung-khoan.jpg"
107
+ )
108
+ self._start_periodic_display()
109
+
110
+ def _start_periodic_display(self):
111
+ def periodic_display():
112
+ while True:
113
+ if self.is_paid_user:
114
+ break
115
+ sleep_time = random.randint(2 * 3600, 6 * 3600)
116
+ time.sleep(sleep_time)
117
+ current_time = time.time()
118
+ if current_time - self.last_display >= self.display_interval:
119
+ self.present_content(context="periodic")
120
+ else:
121
+ pass
122
+ thread = threading.Thread(target=periodic_display, daemon=True)
123
+ thread.start()
124
+
125
+ def fetch_remote_content(self, context: str ="init", html: bool = True) -> str:
126
+ if self.is_paid_user:
127
+ return""
128
+ """
129
+ Fetch promotional content from remote service with context and format flag.
130
+ Args:
131
+ context: usage context (e.g., "init", "periodic", "loop").
132
+ html: if True, request HTML; otherwise plain text.
133
+ Returns:
134
+ The content string on HTTP 200, or None on failure.
135
+ """
136
+ try:
137
+ params = {"context": context,"html":"true" if html else"false"}
138
+ url =f"{self.content_base_url}?{urllib.parse.urlencode(params)}"
139
+ response = requests.get(url, timeout=3)
140
+ if response.status_code == 200:
141
+ return response.text
142
+ logger.error(f"Non-200 response fetching content: {response.status_code}")
143
+ return None
144
+ except Exception as e:
145
+ logger.error(f"Failed to fetch remote content: {e}")
146
+ return None
147
+
148
+ def present_content(self, context: str ="init", ad_category: int = AdCategory.FREE) -> None:
149
+ environment = None
150
+ if getattr(self,'is_paid_user', False) and ad_category == AdCategory.FREE:
151
+ return
152
+ self.last_display = time.time()
153
+ if environment is None:
154
+ try:
155
+ from vnai.scope.profile import inspector
156
+ environment = inspector.examine().get("environment","unknown")
157
+ except Exception as e:
158
+ logger.error(f"Không detect được environment: {e}")
159
+ environment ="unknown"
160
+ remote_content = self.fetch_remote_content(
161
+ context=context, html=(environment =="jupyter")
162
+ )
163
+ fallback = self._generate_fallback_content(context)
164
+ if environment =="jupyter":
165
+ try:
166
+ from IPython.display import display, HTML, Markdown
167
+ if remote_content:
168
+ display(HTML(remote_content))
169
+ else:
170
+ try:
171
+ display(Markdown(fallback["markdown"]))
172
+ except Exception as e:
173
+ display(HTML(fallback["html"]))
174
+ except Exception as e:
175
+ pass
176
+ elif environment =="terminal":
177
+ if remote_content:
178
+ print(remote_content)
179
+ else:
180
+ print(fallback["terminal"])
181
+ else:
182
+ print(fallback["simple"])
183
+
184
+ def _generate_fallback_content(self, context):
185
+ fallback = {"html":"","markdown":"","terminal":"","simple":""}
186
+ if context =="loop":
187
+ fallback["html"] = (
188
+ f"""
89
189
  <div style="border: 1px solid #e74c3c; padding: 15px; border-radius: 5px; margin: 10px 0;">
90
190
  <h3 style="color: #e74c3c;">⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests</h3>
91
191
  <p>Để tránh bị giới hạn tốc độ và tối ưu hiệu suất:</p>
@@ -95,8 +195,39 @@ class ContentManager:
95
195
  <li>Tham gia gói tài trợ <a href="https://vnstocks.com/insiders-program" style="color: #3498db;">Vnstock Insider</a> để tăng 5X giới hạn API</li>
96
196
  </ul>
97
197
  </div>
98
- ''';fallback[_E]='\n## ⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests\n\nĐể tránh bị giới hạn tốc độ và tối ưu hiệu suất:\n* Thêm thời gian chờ giữa các lần gọi API\n* Sử dụng xử lý theo batch thay vì lặp liên tục\n* Tham gia gói tài trợ [Vnstock Insider](https://vnstocks.com/insiders-program) để tăng 5X giới hạn API\n ';fallback[_F]='\n╔═════════════════════════════════════════════════════════════════╗\n║ ║\n║ 🚫 ĐANG BỊ CHẶN BỞI GIỚI HẠN API? GIẢI PHÁP Ở ĐÂY! ║\n║ ║\n║ ✓ Tăng ngay 500% tốc độ gọi API - Không còn lỗi RateLimit ║\n║ ✓ Tiết kiệm 85% thời gian chờ đợi giữa các request ║\n║ ║\n║ ➤ NÂNG CẤP NGAY VỚI GÓI TÀI TRỢ VNSTOCK: ║\n║ https://vnstocks.com/insiders-program ║\n║ ║\n╚═════════════════════════════════════════════════════════════════╝\n ';fallback[A]='🚫 Đang bị giới hạn API? Tăng tốc độ gọi API lên 500% với gói Vnstock Insider: https://vnstocks.com/insiders-program'
99
- else:fallback[_D]=f'''
198
+ """
199
+ )
200
+ fallback["markdown"] = (
201
+ """
202
+ ## ⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests
203
+ Để tránh bị giới hạn tốc độ và tối ưu hiệu suất:
204
+ * Thêm thời gian chờ giữa các lần gọi API
205
+ * Sử dụng xử lý theo batch thay vì lặp liên tục
206
+ * Tham gia gói tài trợ [Vnstock Insider](https://vnstocks.com/insiders-program) để tăng 5X giới hạn API
207
+ """
208
+ )
209
+ fallback["terminal"] = (
210
+ """
211
+ ╔═════════════════════════════════════════════════════════════════╗
212
+ ║ ║
213
+ ║ 🚫 ĐANG BỊ CHẶN BỞI GIỚI HẠN API? GIẢI PHÁP Ở ĐÂY! ║
214
+ ║ ║
215
+ ║ ✓ Tăng ngay 500% tốc độ gọi API - Không còn lỗi RateLimit ║
216
+ ║ ✓ Tiết kiệm 85% thời gian chờ đợi giữa các request ║
217
+ ║ ║
218
+ ║ ➤ NÂNG CẤP NGAY VỚI GÓI TÀI TRỢ VNSTOCK: ║
219
+ ║ https://vnstocks.com/insiders-program ║
220
+ ║ ║
221
+ ╚═════════════════════════════════════════════════════════════════╝
222
+ """
223
+ )
224
+ fallback["simple"] = (
225
+ "🚫 Đang bị giới hạn API? Tăng tốc độ gọi API lên 500% với gói "
226
+ "Vnstock Insider: https://vnstocks.com/insiders-program"
227
+ )
228
+ else:
229
+ fallback["html"] = (
230
+ f"""
100
231
  <div style="border: 1px solid #3498db; padding: 15px; border-radius: 5px; margin: 10px 0;">
101
232
  <h3 style="color: #3498db;">👋 Chào mừng bạn đến với Vnstock!</h3>
102
233
  <p>Cảm ơn bạn đã sử dụng thư viện phân tích chứng khoán #1 tại Việt Nam cho Python</p>
@@ -106,7 +237,42 @@ class ContentManager:
106
237
  </ul>
107
238
  <p>Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.</p>
108
239
  </div>
109
- ''';fallback[_E]='\n## 👋 Chào mừng bạn đến với Vnstock!\n\nCảm ơn bạn đã sử dụng package phân tích chứng khoán #1 tại Việt Nam\n\n* Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs)\n* Cộng đồng: [Nhóm Facebook](https://facebook.com/groups/vnstock.official)\n\nKhám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.\n ';fallback[_F]='\n╔════════════════════════════════════════════════════════════╗\n║ ║\n║ 👋 Chào mừng bạn đến với Vnstock! ║\n║ ║\n║ Cảm ơn bạn đã sử dụng package phân tích ║\n║ chứng khoán #1 tại Việt Nam ║\n║ ║\n║ ✓ Tài liệu: https://vnstocks.com/docs ║\n║ ✓ Cộng đồng: https://facebook.com/groups/vnstock.official ║\n║ ║\n║ Khám phá các tính năng mới nhất và tham gia ║\n║ cộng đồng để nhận hỗ trợ. ║\n║ ║\n╚════════════════════════════════════════════════════════════╝\n ';fallback[A]='👋 Chào mừng bạn đến với Vnstock! Tài liệu: https://vnstocks.com/onboard | Cộng đồng: https://facebook.com/groups/vnstock.official'
110
- return fallback
111
- manager=ContentManager()
112
- def present(context:str=_G,ad_category:int=AdCategory.FREE)->_A:manager.present_content(context=context,ad_category=ad_category)
240
+ """
241
+ )
242
+ fallback["markdown"] = (
243
+ """
244
+ ## 👋 Chào mừng bạn đến với Vnstock!
245
+ Cảm ơn bạn đã sử dụng package phân tích chứng khoán #1 tại Việt Nam
246
+ * Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs)
247
+ * Cộng đồng: [Nhóm Facebook](https://facebook.com/groups/vnstock.official)
248
+ Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.
249
+ """
250
+ )
251
+ fallback["terminal"] = (
252
+ """
253
+ ╔════════════════════════════════════════════════════════════╗
254
+ ║ ║
255
+ ║ 👋 Chào mừng bạn đến với Vnstock! ║
256
+ ║ ║
257
+ ║ Cảm ơn bạn đã sử dụng package phân tích ║
258
+ ║ chứng khoán #1 tại Việt Nam ║
259
+ ║ ║
260
+ ║ ✓ Tài liệu: https://vnstocks.com/docs ║
261
+ ║ ✓ Cộng đồng: https://facebook.com/groups/vnstock.official ║
262
+ ║ ║
263
+ ║ Khám phá các tính năng mới nhất và tham gia ║
264
+ ║ cộng đồng để nhận hỗ trợ. ║
265
+ ║ ║
266
+ ╚════════════════════════════════════════════════════════════╝
267
+ """
268
+ )
269
+ fallback["simple"] = (
270
+ "👋 Chào mừng bạn đến với Vnstock! "
271
+ "Tài liệu: https://vnstocks.com/onboard | "
272
+ "Cộng đồng: https://facebook.com/groups/vnstock.official"
273
+ )
274
+ return fallback
275
+ manager = ContentManager()
276
+
277
+ def present(context: str ="init", ad_category: int = AdCategory.FREE) -> None:
278
+ manager.present_content(context=context, ad_category=ad_category)
vnai/scope/state.py CHANGED
@@ -1,74 +1,155 @@
1
- _L='minimal'
2
- _K='warnings'
3
- _J='api_requests'
4
- _I='last_error_time'
5
- _H='startup_time'
6
- _G='standard'
7
- _F='function_calls'
8
- _E='peak_memory'
9
- _D='errors'
10
- _C=True
11
- _B=None
12
- _A='execution_times'
13
- import time,threading,json,os
1
+ import time
2
+ import threading
3
+ import json
4
+ import os
14
5
  from datetime import datetime
15
6
  from pathlib import Path
7
+
16
8
  class Tracker:
17
- _instance=_B;_lock=threading.Lock()
18
- def __new__(cls):
19
- with cls._lock:
20
- if cls._instance is _B:cls._instance=super(Tracker,cls).__new__(cls);cls._instance._initialize()
21
- return cls._instance
22
- def _initialize(self):self.metrics={_H:datetime.now().isoformat(),_F:0,_J:0,_D:0,_K:0};self.performance_metrics={_A:[],_I:_B,_E:0};self.privacy_level=_G;self.home_dir=Path.home();self.project_dir=self.home_dir/'.vnstock';self.project_dir.mkdir(exist_ok=_C);self.data_dir=self.project_dir/'data';self.data_dir.mkdir(exist_ok=_C);self.metrics_path=self.data_dir/'usage_metrics.json';self.privacy_config_path=self.project_dir/'config'/'privacy.json';os.makedirs(os.path.dirname(self.privacy_config_path),exist_ok=_C);self._load_metrics();self._load_privacy_settings();self._start_background_collector()
23
- def _load_metrics(self):
24
- if self.metrics_path.exists():
25
- try:
26
- with open(self.metrics_path,'r')as f:stored_metrics=json.load(f)
27
- for(key,value)in stored_metrics.items():
28
- if key in self.metrics:self.metrics[key]=value
29
- except:pass
30
- def _save_metrics(self):
31
- try:
32
- with open(self.metrics_path,'w')as f:json.dump(self.metrics,f)
33
- except:pass
34
- def _load_privacy_settings(self):
35
- if self.privacy_config_path.exists():
36
- try:
37
- with open(self.privacy_config_path,'r')as f:settings=json.load(f);self.privacy_level=settings.get('level',_G)
38
- except:pass
39
- def setup_privacy(self,level=_B):
40
- privacy_levels={_L:'Essential system data only',_G:'Performance metrics and errors','enhanced':'Detailed operation analytics'}
41
- if level is _B:level=_G
42
- if level not in privacy_levels:raise ValueError(f"Invalid privacy level: {level}. Choose from {', '.join(privacy_levels.keys())}")
43
- self.privacy_level=level
44
- with open(self.privacy_config_path,'w')as f:json.dump({'level':level},f)
45
- return level
46
- def get_privacy_level(self):return self.privacy_level
47
- def _start_background_collector(self):
48
- def collect_metrics():
49
- while _C:
50
- try:
51
- import psutil;current_process=psutil.Process();memory_info=current_process.memory_info();memory_usage=memory_info.rss/1048576
52
- if memory_usage>self.performance_metrics[_E]:self.performance_metrics[_E]=memory_usage
53
- self._save_metrics()
54
- except:pass
55
- time.sleep(300)
56
- thread=threading.Thread(target=collect_metrics,daemon=_C);thread.start()
57
- def record(self,event_type,data=_B):
58
- A='execution_time'
59
- if self.privacy_level==_L and event_type!=_D:return _C
60
- if event_type in self.metrics:self.metrics[event_type]+=1
61
- else:self.metrics[event_type]=1
62
- if event_type==_D:self.performance_metrics[_I]=datetime.now().isoformat()
63
- if event_type==_F and data and A in data:
64
- self.performance_metrics[_A].append(data[A])
65
- if len(self.performance_metrics[_A])>100:self.performance_metrics[_A]=self.performance_metrics[_A][-100:]
66
- if self.metrics[_F]%100==0 or event_type==_D:self._save_metrics()
67
- return _C
68
- def get_metrics(self):
69
- avg_execution_time=0
70
- if self.performance_metrics[_A]:avg_execution_time=sum(self.performance_metrics[_A])/len(self.performance_metrics[_A])
71
- output=self.metrics.copy();output.update({'avg_execution_time':avg_execution_time,'peak_memory_mb':self.performance_metrics[_E],'uptime':(datetime.now()-datetime.fromisoformat(self.metrics[_H])).total_seconds(),'privacy_level':self.privacy_level});return output
72
- def reset(self):self.metrics={_H:datetime.now().isoformat(),_F:0,_J:0,_D:0,_K:0};self.performance_metrics={_A:[],_I:_B,_E:0};self._save_metrics();return _C
73
- tracker=Tracker()
74
- def record(event_type,data=_B):return tracker.record(event_type,data)
9
+ _instance = None
10
+ _lock = threading.Lock()
11
+
12
+ def __new__(cls):
13
+ with cls._lock:
14
+ if cls._instance is None:
15
+ cls._instance = super(Tracker, cls).__new__(cls)
16
+ cls._instance._initialize()
17
+ return cls._instance
18
+
19
+ def _initialize(self):
20
+ self.metrics = {
21
+ "startup_time": datetime.now().isoformat(),
22
+ "function_calls": 0,
23
+ "api_requests": 0,
24
+ "errors": 0,
25
+ "warnings": 0
26
+ }
27
+ self.performance_metrics = {
28
+ "execution_times": [],
29
+ "last_error_time": None,
30
+ "peak_memory": 0
31
+ }
32
+ self.privacy_level ="standard"
33
+ self.home_dir = Path.home()
34
+ self.project_dir = self.home_dir /".vnstock"
35
+ self.project_dir.mkdir(exist_ok=True)
36
+ self.data_dir = self.project_dir /'data'
37
+ self.data_dir.mkdir(exist_ok=True)
38
+ self.metrics_path = self.data_dir /"usage_metrics.json"
39
+ self.privacy_config_path = self.project_dir /'config' /"privacy.json"
40
+ os.makedirs(os.path.dirname(self.privacy_config_path), exist_ok=True)
41
+ self._load_metrics()
42
+ self._load_privacy_settings()
43
+ self._start_background_collector()
44
+
45
+ def _load_metrics(self):
46
+ if self.metrics_path.exists():
47
+ try:
48
+ with open(self.metrics_path,'r') as f:
49
+ stored_metrics = json.load(f)
50
+ for key, value in stored_metrics.items():
51
+ if key in self.metrics:
52
+ self.metrics[key] = value
53
+ except:
54
+ pass
55
+
56
+ def _save_metrics(self):
57
+ try:
58
+ with open(self.metrics_path,'w') as f:
59
+ json.dump(self.metrics, f)
60
+ except:
61
+ pass
62
+
63
+ def _load_privacy_settings(self):
64
+ if self.privacy_config_path.exists():
65
+ try:
66
+ with open(self.privacy_config_path,'r') as f:
67
+ settings = json.load(f)
68
+ self.privacy_level = settings.get("level","standard")
69
+ except:
70
+ pass
71
+
72
+ def setup_privacy(self, level=None):
73
+ privacy_levels = {
74
+ "minimal":"Essential system data only",
75
+ "standard":"Performance metrics and errors",
76
+ "enhanced":"Detailed operation analytics"
77
+ }
78
+ if level is None:
79
+ level ="standard"
80
+ if level not in privacy_levels:
81
+ raise ValueError(f"Invalid privacy level: {level}. Choose from {', '.join(privacy_levels.keys())}")
82
+ self.privacy_level = level
83
+ with open(self.privacy_config_path,"w") as f:
84
+ json.dump({"level": level}, f)
85
+ return level
86
+
87
+ def get_privacy_level(self):
88
+ return self.privacy_level
89
+
90
+ def _start_background_collector(self):
91
+ def collect_metrics():
92
+ while True:
93
+ try:
94
+ import psutil
95
+ current_process = psutil.Process()
96
+ memory_info = current_process.memory_info()
97
+ memory_usage = memory_info.rss / (1024 * 1024)
98
+ if memory_usage > self.performance_metrics["peak_memory"]:
99
+ self.performance_metrics["peak_memory"] = memory_usage
100
+ self._save_metrics()
101
+ except:
102
+ pass
103
+ time.sleep(300)
104
+ thread = threading.Thread(target=collect_metrics, daemon=True)
105
+ thread.start()
106
+
107
+ def record(self, event_type, data=None):
108
+ if self.privacy_level =="minimal" and event_type !="errors":
109
+ return True
110
+ if event_type in self.metrics:
111
+ self.metrics[event_type] += 1
112
+ else:
113
+ self.metrics[event_type] = 1
114
+ if event_type =="errors":
115
+ self.performance_metrics["last_error_time"] = datetime.now().isoformat()
116
+ if event_type =="function_calls" and data and"execution_time" in data:
117
+ self.performance_metrics["execution_times"].append(data["execution_time"])
118
+ if len(self.performance_metrics["execution_times"]) > 100:
119
+ self.performance_metrics["execution_times"] = self.performance_metrics["execution_times"][-100:]
120
+ if self.metrics["function_calls"] % 100 == 0 or event_type =="errors":
121
+ self._save_metrics()
122
+ return True
123
+
124
+ def get_metrics(self):
125
+ avg_execution_time = 0
126
+ if self.performance_metrics["execution_times"]:
127
+ avg_execution_time = sum(self.performance_metrics["execution_times"]) / len(self.performance_metrics["execution_times"])
128
+ output = self.metrics.copy()
129
+ output.update({
130
+ "avg_execution_time": avg_execution_time,
131
+ "peak_memory_mb": self.performance_metrics["peak_memory"],
132
+ "uptime": (datetime.now() - datetime.fromisoformat(self.metrics["startup_time"])).total_seconds(),
133
+ "privacy_level": self.privacy_level
134
+ })
135
+ return output
136
+
137
+ def reset(self):
138
+ self.metrics = {
139
+ "startup_time": datetime.now().isoformat(),
140
+ "function_calls": 0,
141
+ "api_requests": 0,
142
+ "errors": 0,
143
+ "warnings": 0
144
+ }
145
+ self.performance_metrics = {
146
+ "execution_times": [],
147
+ "last_error_time": None,
148
+ "peak_memory": 0
149
+ }
150
+ self._save_metrics()
151
+ return True
152
+ tracker = Tracker()
153
+
154
+ def record(event_type, data=None):
155
+ return tracker.record(event_type, data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vnai
3
- Version: 2.1.3
3
+ Version: 2.1.4
4
4
  Summary: Vnstock Analytics Interface
5
5
  Author-email: Vnstock Analytics Team <support@vnstocks.com>
6
6
  License: proprietary
@@ -14,6 +14,7 @@ Requires-Python: >=3.7
14
14
  Description-Content-Type: text/markdown
15
15
  Requires-Dist: requests>=2.25.0
16
16
  Requires-Dist: psutil>=5.8.0
17
+ Requires-Dist: vnii>=0.0.9
17
18
  Provides-Extra: dev
18
19
  Requires-Dist: pytest>=6.0.0; extra == "dev"
19
20
 
@@ -0,0 +1,16 @@
1
+ vnai/__init__.py,sha256=81Kie-ZYqe1HYZmT46qR_yGdEyN28kOQvFrFNUd6iBg,5748
2
+ vnai/beam/__init__.py,sha256=Of7ydx35u4gTOBCYXxDwTfDAZVyIpbds09dXX0hJh-w,133
3
+ vnai/beam/metrics.py,sha256=p0WzO1a5MvXm98KyyshLzB4FCk-5ERh7JN65IxVB0I0,6441
4
+ vnai/beam/pulse.py,sha256=WTdjJ8mr2fvtPEZcoyzVO9Nv8jt2QPCjOZjCh7CB4Ww,2563
5
+ vnai/beam/quota.py,sha256=6DQdA7qwft1VWsscjpxUGyA59PEGmUY2mnGomj2aDRE,14748
6
+ vnai/flow/__init__.py,sha256=d96OtNAJeYEYYW_3ZZXbGh8NWbVK3cd78lsub9m9--Q,82
7
+ vnai/flow/queue.py,sha256=LBuaiAdfaOfbOL_1oxapEKlNav7qGdUk8BQdfJTxMyA,3322
8
+ vnai/flow/relay.py,sha256=e7H_MSNm0AYwDeY0AeHCptl2vuD1NCRUvmsGWNmQK2c,13515
9
+ vnai/scope/__init__.py,sha256=VVFWdhAVGYJY-4CykgyhjsqoL17WlbRjVk3CZYAN7S4,200
10
+ vnai/scope/profile.py,sha256=R_xM5DyDEZ6ju9CwTAv_iXDHx4Dhqd7b1lCeAmlj-lc,22145
11
+ vnai/scope/promo.py,sha256=lnoZJL5smBc1SF49bkCMMCrgzv5NnrIIAMbSqyTGmYI,13003
12
+ vnai/scope/state.py,sha256=Y8eZNVtG31E7Jv-RtHdDLkRRKbfCK8czbbyKp65065k,5543
13
+ vnai-2.1.4.dist-info/METADATA,sha256=LQXxQuOnwMnjoc1rMzuOOZjPAlc8QYwNr_jprLr6w6I,710
14
+ vnai-2.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ vnai-2.1.4.dist-info/top_level.txt,sha256=4zI0qZHePCwvgSqXl4420sBcd0VzZn4MEcRsAIFae3k,5
16
+ vnai-2.1.4.dist-info/RECORD,,