vnai 0.1.3__py3-none-any.whl → 2.3.7__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 ADDED
@@ -0,0 +1,389 @@
1
+ import logging
2
+ import requests
3
+ import json
4
+ from datetime import datetime, timedelta
5
+ import random
6
+ import threading
7
+ import time
8
+ import urllib.parse
9
+ from pathlib import Path
10
+ _vnii_check_attempted = False
11
+
12
+ class AdCategory:
13
+ FREE = 0
14
+ MANDATORY = 1
15
+ ANNOUNCEMENT = 2
16
+ REFERRAL = 3
17
+ FEATURE = 4
18
+ GUIDE = 5
19
+ SURVEY = 6
20
+ PROMOTION = 7
21
+ SECURITY = 8
22
+ MAINTENANCE = 9
23
+ WARNING = 10
24
+ API_KEY_CHECKER_AVAILABLE = None
25
+ api_key_checker = 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
+
33
+ class ContentManager:
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, debug=False):
45
+ self.content_base_url ="https://hq.vnstocks.com/content-delivery"
46
+ self.is_paid_user = None
47
+ self.license_checked = False
48
+ self._debug = debug
49
+ global API_KEY_CHECKER_AVAILABLE, api_key_checker
50
+ if API_KEY_CHECKER_AVAILABLE is None:
51
+ try:
52
+ from vnai.scope.lc_integration import api_key_checker as _checker
53
+ api_key_checker = _checker
54
+ API_KEY_CHECKER_AVAILABLE = True
55
+ except ImportError:
56
+ API_KEY_CHECKER_AVAILABLE = False
57
+ api_key_checker = None
58
+ self.last_display = 0
59
+ self.display_interval = 24 * 3600
60
+ self.content_base_url ="https://hq.vnstocks.com/content-delivery"
61
+ self.target_url ="https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
62
+ self.image_url = (
63
+ "https://vnstocks.com/img/trang-chu-vnstock-python-api-phan-tich-giao-dich-chung-khoan.jpg"
64
+ )
65
+ self.vnstock_dir = Path.home() /".vnstock"
66
+ self.message_cache_file = self.vnstock_dir /"message_cache.json"
67
+ self.message_cache = {}
68
+ self._load_message_cache()
69
+ self._start_periodic_display()
70
+
71
+ def _load_message_cache(self) -> None:
72
+ try:
73
+ if self.message_cache_file.exists():
74
+ with open(self.message_cache_file,'r') as f:
75
+ self.message_cache = json.load(f)
76
+ else:
77
+ self.message_cache = {}
78
+ except Exception as e:
79
+ logger.debug(f"Failed to load message cache: {e}")
80
+ self.message_cache = {}
81
+
82
+ def _save_message_cache(self) -> None:
83
+ try:
84
+ self.vnstock_dir.mkdir(exist_ok=True)
85
+ with open(self.message_cache_file,'w') as f:
86
+ json.dump(self.message_cache, f, indent=2)
87
+ except Exception as e:
88
+ logger.debug(f"Failed to save message cache: {e}")
89
+
90
+ def should_show_promotional_for_rate_limit(self, tier: str) -> bool:
91
+ if tier !="free":
92
+ return False
93
+ last_promo = self.message_cache.get("last_promotional_message")
94
+ if last_promo:
95
+ try:
96
+ last_time = datetime.fromisoformat(last_promo)
97
+ now = datetime.now()
98
+ if (now - last_time).total_seconds() < 86400:
99
+ return False
100
+ except Exception as e:
101
+ logger.debug(f"Error parsing last promotional time: {e}")
102
+ session_start = self.message_cache.get("session_start")
103
+ if session_start:
104
+ try:
105
+ start_time = datetime.fromisoformat(session_start)
106
+ now = datetime.now()
107
+ if (now - start_time).total_seconds() < 300:
108
+ return False
109
+ except Exception as e:
110
+ logger.debug(f"Error parsing session start time: {e}")
111
+ else:
112
+ self.message_cache["session_start"] = datetime.now().isoformat()
113
+ self._save_message_cache()
114
+ return False
115
+ return True
116
+
117
+ def mark_promotional_shown(self) -> None:
118
+ self.message_cache["last_promotional_message"] = datetime.now().isoformat()
119
+ self._save_message_cache()
120
+
121
+ def _check_license_status(self):
122
+ if self.license_checked:
123
+ return
124
+ if API_KEY_CHECKER_AVAILABLE and api_key_checker:
125
+ try:
126
+ result = api_key_checker.check_license_with_vnii()
127
+ if self._debug:
128
+ logger.info(f"API key check result: {result}")
129
+ valid_statuses = [
130
+ 'verified',
131
+ 'cached',
132
+ 'device_limit_exceeded'
133
+ ]
134
+ if result.get('status') in valid_statuses:
135
+ self.is_paid_user = result.get('is_paid', False)
136
+ self.license_checked = True
137
+ if self._debug:
138
+ status_msg = (
139
+ "Detected paid user"
140
+ if self.is_paid_user
141
+ else"Detected free user"
142
+ )
143
+ logger.info(f"{status_msg} via API key")
144
+ else:
145
+ self.is_paid_user = False
146
+ self.license_checked = True
147
+ if self._debug:
148
+ logger.info(
149
+ f"No valid license: {result.get('status')}"
150
+ )
151
+ except Exception as e:
152
+ if self._debug:
153
+ logger.error(f"Error checking license via API key: {e}")
154
+ self.is_paid_user = False
155
+ self.license_checked = True
156
+ else:
157
+ if self._debug:
158
+ logger.warning(
159
+ "API key checker not available. "
160
+ "Cannot determine paid user status."
161
+ )
162
+ self.is_paid_user = False
163
+ self.license_checked = True
164
+
165
+ def _start_periodic_display(self):
166
+ def periodic_display():
167
+ while True:
168
+ if not self.license_checked:
169
+ self._check_license_status()
170
+ if self.is_paid_user:
171
+ break
172
+ sleep_time = random.randint(2 * 3600, 6 * 3600)
173
+ time.sleep(sleep_time)
174
+ current_time = time.time()
175
+ if current_time - self.last_display >= self.display_interval:
176
+ self.present_content(context="periodic")
177
+ else:
178
+ pass
179
+ thread = threading.Thread(target=periodic_display, daemon=True)
180
+ thread.start()
181
+
182
+ def fetch_remote_content(self, context: str ="init", html: bool = True) -> str:
183
+ if not self.license_checked:
184
+ self._check_license_status()
185
+ if self.is_paid_user:
186
+ return""
187
+ try:
188
+ params = {"context": context,"html":"true" if html else"false"}
189
+ url =f"{self.content_base_url}?{urllib.parse.urlencode(params)}"
190
+ response = requests.get(url, timeout=3)
191
+ if response.status_code == 200:
192
+ return response.text
193
+ logger.error(f"Non-200 response fetching content: {response.status_code}")
194
+ return None
195
+ except Exception as e:
196
+ logger.error(f"Failed to fetch remote content: {e}")
197
+ return None
198
+
199
+ def present_content(self, context: str ="init", ad_category: int = AdCategory.FREE) -> None:
200
+ if not self.license_checked:
201
+ self._check_license_status()
202
+ if self.is_paid_user and ad_category == AdCategory.FREE:
203
+ return
204
+ self.last_display = time.time()
205
+ environment = None
206
+ try:
207
+ from vnai.scope.profile import inspector
208
+ environment = inspector.examine().get("environment","unknown")
209
+ except Exception as e:
210
+ logger.error(f"Không detect được environment: {e}")
211
+ environment ="unknown"
212
+ remote_content = self.fetch_remote_content(
213
+ context=context, html=(environment =="jupyter")
214
+ )
215
+ fallback = self._generate_fallback_content(context)
216
+ if environment =="jupyter":
217
+ try:
218
+ from IPython.display import display, HTML, Markdown
219
+ if remote_content:
220
+ display(HTML(remote_content))
221
+ else:
222
+ try:
223
+ display(Markdown(fallback["markdown"]))
224
+ except Exception as e:
225
+ display(HTML(fallback["html"]))
226
+ except Exception as e:
227
+ pass
228
+ elif environment =="terminal":
229
+ if remote_content:
230
+ print(remote_content)
231
+ else:
232
+ print(fallback["terminal"])
233
+ else:
234
+ print(fallback["simple"])
235
+
236
+ def _generate_fallback_content(self, context):
237
+ fallback = {"html":"","markdown":"","terminal":"","simple":""}
238
+ if context =="loop":
239
+ fallback["html"] = (
240
+ f"""
241
+ <div style="border: 1px solid #e74c3c; padding: 15px; border-radius: 5px; margin: 10px 0;">
242
+ <h3 style="color: #e74c3c;">⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests</h3>
243
+ <p>Để tránh bị giới hạn tốc độ và tối ưu hiệu suất:</p>
244
+ <ul>
245
+ <li>Thêm thời gian chờ giữa các lần gọi API</li>
246
+ <li>Sử dụng xử lý theo batch thay vì lặp liên tục</li>
247
+ <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>
248
+ </ul>
249
+ </div>
250
+ """
251
+ )
252
+ fallback["markdown"] = (
253
+ """
254
+ ## ⚠️ Bạn đang sử dụng vòng lặp với quá nhiều requests
255
+ Để tránh bị giới hạn tốc độ và tối ưu hiệu suất:
256
+ * Thêm thời gian chờ giữa các lần gọi API
257
+ * Sử dụng xử lý theo batch thay vì lặp liên tục
258
+ * Tham gia gói tài trợ [Vnstock Insider](https://vnstocks.com/insiders-program) để tăng 5X giới hạn API
259
+ """
260
+ )
261
+ fallback["terminal"] = get_promotional_message("terminal")
262
+ fallback["simple"] = get_promotional_message("simple")
263
+ else:
264
+ from vnai.beam.auth import authenticator
265
+ tier = authenticator.get_tier()
266
+ period_limit_info =""
267
+ if tier =='guest':
268
+ period_limit_info = (
269
+ "<p style='color: #e67e22; margin-top: 10px;'>"
270
+ "<strong>📊 Phiên bản cộng đồng:</strong> Báo cáo tài chính được giới hạn tối đa <strong>4 kỳ</strong> để minh hoạ thuật toán. "
271
+ "Để truy cập đầy đủ, vui lòng <a href='https://vnstocks.com/insiders-program' style='color: #e67e22;'>tham gia gói thành viên tài trợ dự án</a>."
272
+ "</p>"
273
+ )
274
+ elif tier =='free':
275
+ period_limit_info = (
276
+ "<p style='color: #e67e22; margin-top: 10px;'>"
277
+ "<strong>📊 Phiên bản cộng đồng:</strong> Báo cáo tài chính được giới hạn tối đa <strong>8 kỳ</strong> để minh hoạ thuật toán. "
278
+ "Để truy cập đầy đủ, vui lòng <a href='https://vnstocks.com/insiders-program' style='color: #e67e22;'>tham gia gói thành viên tài trợ dự án</a>."
279
+ "</p>"
280
+ )
281
+ fallback["html"] = (
282
+ f"""
283
+ <div style="border: 1px solid #3498db; padding: 15px; border-radius: 5px; margin: 10px 0;">
284
+ <h3 style="color: #3498db;">👋 Chào mừng bạn đến với Vnstock!</h3>
285
+ <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>
286
+ <ul>
287
+ <li>Tài liệu: <a href="https://vnstocks.com/docs/category/s%E1%BB%95-tay-h%C6%B0%E1%BB%9Bng-d%E1%BA%ABn" style="color: #3498db;">vnstocks.com/docs</a></li>
288
+ <li>Cộng đồng: <a href="https://www.facebook.com/groups/vnstock.official" style="color: #3498db;">vnstocks.com/community</a></li>
289
+ </ul>
290
+ <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>
291
+ {period_limit_info}
292
+ </div>
293
+ """
294
+ )
295
+ period_limit_markdown =""
296
+ if tier =='guest':
297
+ period_limit_markdown ="\n**📊 Phiên bản cộng đồng:** Báo cáo tài chính được giới hạn tối đa **4 kỳ** để minh hoạ thuật toán. Để truy cập đầy đủ, vui lòng [tham gia gói thành viên tài trợ dự án](https://vnstocks.com/insiders-program)."
298
+ elif tier =='free':
299
+ period_limit_markdown ="\n**📊 Phiên bản cộng đồng:** Báo cáo tài chính được giới hạn tối đa **8 kỳ** để minh hoạ thuật toán. Để truy cập đầy đủ, vui lòng [tham gia gói thành viên tài trợ dự án](https://vnstocks.com/insiders-program)."
300
+ fallback["markdown"] = (
301
+ f"""
302
+ ## 👋 Chào mừng bạn đến với Vnstock!
303
+ Cảm ơn bạn đã sử dụng package phân tích chứng khoán #1 tại Việt Nam
304
+ * Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs)
305
+ * Cộng đồng: [Nhóm Facebook](https://facebook.com/groups/vnstock.official)
306
+ Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.{period_limit_markdown}
307
+ """
308
+ )
309
+ period_limit_terminal =""
310
+ if tier =='guest':
311
+ period_limit_terminal ="\n║ 📊 Báo cáo tài chính: Giới hạn 4 kỳ (phiên bản cộng đồng) ║\n║ Nâng cấp: https://vnstocks.com/insiders-program ║"
312
+ elif tier =='free':
313
+ period_limit_terminal ="\n║ 📊 Báo cáo tài chính: Giới hạn 8 kỳ (phiên bản cộng đồng) ║\n║ Nâng cấp: https://vnstocks.com/insiders-program ║"
314
+ fallback["terminal"] = (
315
+ f"""
316
+ ╔════════════════════════════════════════════════════════════╗
317
+ ║ ║
318
+ ║ 👋 Chào mừng bạn đến với Vnstock! ║
319
+ ║ ║
320
+ ║ Cảm ơn bạn đã sử dụng package phân tích ║
321
+ ║ chứng khoán #1 tại Việt Nam ║
322
+ ║ ║
323
+ ║ ✓ Tài liệu: https://vnstocks.com/docs ║
324
+ ║ ✓ Cộng đồng: https://facebook.com/groups/vnstock.official ║{period_limit_terminal}
325
+ ║ ║
326
+ ║ Khám phá các tính năng mới nhất và tham gia ║
327
+ ║ cộng đồng để nhận hỗ trợ. ║
328
+ ║ ║
329
+ ╚════════════════════════════════════════════════════════════╝
330
+ """
331
+ )
332
+ period_limit_simple =""
333
+ if tier =='guest':
334
+ period_limit_simple =" | 📊 Báo cáo tài chính: Giới hạn 4 kỳ"
335
+ elif tier =='free':
336
+ period_limit_simple =" | 📊 Báo cáo tài chính: Giới hạn 8 kỳ"
337
+ fallback["simple"] = (
338
+ "👋 Chào mừng bạn đến với Vnstock! "
339
+ "Tài liệu: https://vnstocks.com/onboard | "
340
+ "Cộng đồng: https://facebook.com/groups/vnstock.official"
341
+ f"{period_limit_simple}"
342
+ )
343
+ return fallback
344
+ manager = ContentManager()
345
+
346
+ def present(context: str ="init", ad_category: int = AdCategory.FREE) -> None:
347
+ manager.present_content(context=context, ad_category=ad_category)
348
+
349
+ def get_promotional_message(format_type: str ="terminal") -> str:
350
+ if format_type =="terminal":
351
+ return"""
352
+ ╔═════════════════════════════════════════════════════════════════╗
353
+ ║ ║
354
+ ║ 🚫 ĐANG BỊ CHẶN BỞI GIỚI HẠN API? GIẢI PHÁP Ở ĐÂY! ║
355
+ ║ ║
356
+ ║ ✓ Tăng ngay 10X tốc độ gọi API - Không còn lỗi RateLimit ║
357
+ ║ ✓ Tiết kiệm 85% thời gian chờ đợi giữa các request ║
358
+ ║ ║
359
+ ║ ➤ NÂNG CẤP NGAY VỚI GÓI TÀI TRỢ VNSTOCK: ║
360
+ ║ https://vnstocks.com/insiders-program ║
361
+ ║ ║
362
+ ╚═════════════════════════════════════════════════════════════════╝
363
+ """
364
+ elif format_type =="simple":
365
+ return (
366
+ "🚫 Đang bị giới hạn API? Tăng tốc độ gọi API lên 10X với gói "
367
+ "Vnstock Insider: https://vnstocks.com/insiders-program"
368
+ )
369
+ elif format_type =="html":
370
+ return"""
371
+ <div style="border: 2px solid #e74c3c; padding: 15px; border-radius: 5px; margin: 10px 0; background-color: #fadbd8;">
372
+ <h3 style="color: #c0392b; margin-top: 0;">🚫 ĐANG BỊ CHẶN BỞI GIỚI HẠN API?</h3>
373
+ <p><strong>✓ Tăng ngay 10X tốc độ gọi API - Không còn lỗi RateLimit</strong></p>
374
+ <p><strong>✓ Tiết kiệm 85% thời gian chờ đợi giữa các request</strong></p>
375
+ <p style="margin-bottom: 0;">
376
+ <a href="https://vnstocks.com/insiders-program" style="color: #c0392b; font-weight: bold;">
377
+ ➤ NÂNG CẤP NGAY VỚI GÓI TÀI TRỢ VNSTOCK
378
+ </a>
379
+ </p>
380
+ </div>
381
+ """
382
+ else:
383
+ return get_promotional_message("terminal")
384
+
385
+ def should_show_promotional_for_rate_limit(tier: str) -> bool:
386
+ return manager.should_show_promotional_for_rate_limit(tier)
387
+
388
+ def mark_promotional_shown() -> None:
389
+ manager.mark_promotional_shown()
vnai/scope/state.py ADDED
@@ -0,0 +1,155 @@
1
+ import time
2
+ import threading
3
+ import json
4
+ import os
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+
8
+ class Tracker:
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)
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: vnai
3
+ Version: 2.3.7
4
+ Summary: Vnstock Analytics Interface
5
+ Author-email: Vnstock Analytics Team <support@vnstocks.com>
6
+ License: proprietary
7
+ Project-URL: Homepage, https://vnstocks.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: requests>=2.25.0
16
+ Requires-Dist: psutil>=5.8.0
17
+ Requires-Dist: cffi>=1.17.1
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=6.0.0; extra == "dev"
20
+
21
+ # VnAI
@@ -0,0 +1,23 @@
1
+ vnai/__init__.py,sha256=LjSVIZnlr88sAYTKLTaycLGyPrAwXDtYIBxRPhziWQA,11389
2
+ vnai/beam/__init__.py,sha256=P9URuUI3H8PAKWo0OSAbwT5Tv3r1ANoGhv3ZQCFopv4,949
3
+ vnai/beam/auth.py,sha256=o22q4mHngFtt4_BLn1ytkhIeX-CcqslXqk0AZAr-ygU,11958
4
+ vnai/beam/fundamental.py,sha256=sqKutOsnDCRL_h7fAMZw3uAFIFEL5jDq-BN9u1OgQkg,7674
5
+ vnai/beam/metrics.py,sha256=2BmdJl7gB4rHwCN7G7WUMg_6rnlY3xfulBzU7usqNfE,6275
6
+ vnai/beam/patching.py,sha256=Kd-Q-73C97xEUUa9bnZcmCr2rWCpnof6lX5Fg_hd8jM,10322
7
+ vnai/beam/pulse.py,sha256=xu7cStqdkCw8be_yVwfjGtqdy-stYC511oZOkT9j6hs,2485
8
+ vnai/beam/quota.py,sha256=_dmsDhB0PGCA0W044sWvLGK9p9r60KePdxRvVZv6mjE,17035
9
+ vnai/beam/sync.py,sha256=Pfr4CziIoWwebPt0p6nTvsLlIqZuXebMcuIyTNzTk_I,3497
10
+ vnai/flow/__init__.py,sha256=9JepoOHS1xn0_ZrDyfft42g2cjhaka7xdG0velqmk-I,70
11
+ vnai/flow/queue.py,sha256=9dyMwxen1vCiFJ7wNZn-kzMY8DnB4xD575J1pPSWZLw,3223
12
+ vnai/flow/relay.py,sha256=I76uSJXnMOZs77x-pdnsuDy7vSdGh2lp4QBSGJ1_9gY,12791
13
+ vnai/scope/__init__.py,sha256=mOPU5J0y3PgmTT9IVG9geYNQASADpu8Lc-GPvA5mDQ4,376
14
+ vnai/scope/device.py,sha256=qnWTQ0MvUBsZDo0lYEx7lbyRmQX0XuTNAYd30AbS5J4,10825
15
+ vnai/scope/lc_integration.py,sha256=M2XfNIHeMT5maLACPmQFv601AF1Kx-sYw6FiOKGL9oU,12101
16
+ vnai/scope/license.py,sha256=Su10AeGAn6n-XYVTZoW3f2jOqp0tNAoR4Ixqr8Q12NU,6852
17
+ vnai/scope/profile.py,sha256=b4rruVF3m2D_vzimmgVmjNkdjTF_2ci1xeEh40WHWlk,22439
18
+ vnai/scope/promo.py,sha256=k7jaGLXl1U5nW3Ut-tg3qEAVJ0k_zMZ1qY4kzqPRB-0,18517
19
+ vnai/scope/state.py,sha256=ApzP6U0Hzp047-k3czL933soUXiZjpZSxXk7DKwf7yM,5389
20
+ vnai-2.3.7.dist-info/METADATA,sha256=1xTTK6p2emi3NBCLR0Jywo5KXr51RVQNWKG4_xkriDQ,691
21
+ vnai-2.3.7.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
22
+ vnai-2.3.7.dist-info/top_level.txt,sha256=4zI0qZHePCwvgSqXl4420sBcd0VzZn4MEcRsAIFae3k,5
23
+ vnai-2.3.7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.2.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,20 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: vnai
3
- Version: 0.1.3
4
- Summary: :))
5
- Author: Vnstock HQ
6
- Author-email: support@vnstock.site
7
- Classifier: Programming Language :: Python :: 3
8
- Classifier: Programming Language :: Python :: 3.10
9
- Classifier: Programming Language :: Python :: 3.11
10
- Classifier: Programming Language :: Python :: 3.12
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Operating System :: OS Independent
13
- Requires-Python: >=3.10
14
- Requires-Dist: requests
15
- Requires-Dist: cryptography
16
- Requires-Dist: psutil
17
- Provides-Extra: dev
18
- Requires-Dist: pytest ; extra == 'dev'
19
- Requires-Dist: pytest-cov ; extra == 'dev'
20
-
@@ -1,5 +0,0 @@
1
- vnai/__init__.py,sha256=4fZ4M7tusRFxxQdWHkLZyJj7XSFTpEtFNwVCLZ7296o,11782
2
- vnai-0.1.3.dist-info/METADATA,sha256=4hCuxIWE-1XgNPVjg-zksv8cNlhQYSlTVswyZfWtIBg,615
3
- vnai-0.1.3.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
4
- vnai-0.1.3.dist-info/top_level.txt,sha256=4zI0qZHePCwvgSqXl4420sBcd0VzZn4MEcRsAIFae3k,5
5
- vnai-0.1.3.dist-info/RECORD,,