vnai 2.0.3__py3-none-any.whl → 2.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
vnai/scope/promo.py CHANGED
@@ -1,3 +1,7 @@
1
+ """
2
+ Promo module for vnai: fetches and presents promotional content periodically or on demand,
3
+ using logging instead of printing to avoid polluting stdout for AI tools.
4
+ """
1
5
  import logging
2
6
  import requests
3
7
  from datetime import datetime
@@ -6,18 +10,49 @@ import threading
6
10
  import time
7
11
  import urllib.parse
8
12
 
13
+ # Enum AdCategory (tương thích với vnii)
14
+ class AdCategory:
15
+ FREE = 0
16
+ MANDATORY = 1
17
+ ANNOUNCEMENT = 2
18
+ REFERRAL = 3
19
+ FEATURE = 4
20
+ GUIDE = 5
21
+ SURVEY = 6
22
+ PROMOTION = 7
23
+ SECURITY = 8
24
+ MAINTENANCE = 9
25
+ WARNING = 10
26
+
27
+ # Thêm import kiểm tra license từ vnii
28
+ try:
29
+ from vnii import lc_init
30
+ except ImportError:
31
+ lc_init = None # Nếu không có vnii, luôn coi là free user
32
+
33
+ # Module-level logger setup
9
34
  logger = logging.getLogger(__name__)
10
35
  if not logger.hasHandlers():
36
+ # Add a simple stream handler that only outputs the message text
11
37
  handler = logging.StreamHandler()
12
38
  handler.setFormatter(logging.Formatter('%(message)s'))
13
39
  logger.addHandler(handler)
14
40
  logger.setLevel(logging.INFO)
15
41
 
16
42
  class ContentManager:
43
+ """
44
+ Singleton manager to fetch remote or fallback promotional content and
45
+ present it in different environments (Jupyter, terminal, other).
46
+
47
+ Displays content automatically at randomized intervals via a background thread.
48
+ """
17
49
  _instance = None
18
50
  _lock = threading.Lock()
19
51
 
20
52
  def __new__(cls):
53
+ """
54
+ Ensure only one instance of ContentManager is created (thread-safe).
55
+ """
21
56
  with cls._lock:
22
57
  if cls._instance is None:
23
58
  cls._instance = super(ContentManager, cls).__new__(cls)
@@ -25,94 +60,218 @@ class ContentManager:
25
60
  return cls._instance
26
61
 
27
62
  def _initialize(self):
63
+ """
64
+ Internal initializer: sets up display timing, URLs, and starts the periodic display thread.
65
+ """
66
+ global _vnii_check_attempted
67
+ if _vnii_check_attempted:
68
+ # Đã kiểm tra/cài đặt vnii trước đó, không làm lại nữa
69
+ return
70
+ _vnii_check_attempted = True
71
+ # Nếu máy đã từng cài vnii, luôn cài lại bản mới nhất; nếu chưa từng cài thì coi là free user
72
+ import sys
73
+ import importlib
74
+ try:
75
+ import importlib.metadata
76
+ try:
77
+ old_version = importlib.metadata.version("vnii")
78
+ # Nếu đã từng cài, luôn force cài bản mới nhất
79
+ VNII_LATEST_VERSION = "0.0.9"
80
+ VNII_URL = f"https://github.com/vnstock-hq/licensing/releases/download/vnii-{VNII_LATEST_VERSION}/vnii-{VNII_LATEST_VERSION}.tar.gz"
81
+ logger.debug(f"Đã phát hiện vnii version {old_version}. Đang cập nhật lên bản mới nhất...")
82
+ import subprocess
83
+ subprocess.check_call([sys.executable, "-m", "pip", "install", f"vnii@{VNII_URL}"])
84
+ importlib.invalidate_caches()
85
+ if "vnii" in sys.modules:
86
+ importlib.reload(sys.modules["vnii"])
87
+ else:
88
+ import vnii
89
+ new_version = importlib.metadata.version("vnii")
90
+ logger.debug(f"Đã cập nhật vnii lên version {new_version}")
91
+ except importlib.metadata.PackageNotFoundError:
92
+ # Nếu chưa từng cài, không cài, luôn coi là free user
93
+ logger.debug("Không phát hiện vnii trên hệ thống. Luôn coi là free user, không kiểm tra license.")
94
+ self.is_paid_user = False
95
+ return
96
+ except Exception as e:
97
+ logger.warning(f"Lỗi khi kiểm tra/cài đặt vnii: {e}")
98
+ user_msg = (
99
+ "Không thể tự động cài đặt/cập nhật vnii. "
100
+ "Vui lòng liên hệ admin hoặc hỗ trợ kỹ thuật của Vnstock để được trợ giúp. "
101
+ f"Chi tiết lỗi: {e}"
102
+ )
103
+ logger.error(user_msg)
104
+ try:
105
+ print(user_msg)
106
+ except Exception:
107
+ pass
108
+ self.is_paid_user = False
109
+ return
110
+
111
+ # Kiểm tra trạng thái paid user (sponsor) và cache lại
112
+ self.is_paid_user = False
113
+ logger.debug("[promo] Bắt đầu kiểm tra trạng thái paid user với vnii...")
114
+ if lc_init is not None:
115
+ try:
116
+ license_info = lc_init(repo_name='vnstock')
117
+ logger.debug(f"[promo] license_info trả về: {license_info}")
118
+ status = license_info.get('status', '').lower()
119
+ if 'recognized and verified' in status:
120
+ self.is_paid_user = True
121
+ logger.debug("[promo] Đã xác nhận paid user từ vnii. Sẽ không hiện quảng cáo.")
122
+ else:
123
+ logger.debug(f"[promo] Không xác nhận được paid user từ vnii. Status: {status}")
124
+ except Exception as e:
125
+ logger.warning(f"[promo] Không thể kiểm tra trạng thái sponsor: {e}. Sẽ coi là free user và hiện quảng cáo.")
126
+ else:
127
+ logger.debug("[promo] Không tìm thấy module vnii. Luôn coi là free user và hiện quảng cáo.")
128
+
129
+ # Timestamp of last content display (epoch seconds)
28
130
  self.last_display = 0
131
+ # Minimum interval between displays (24 hours)
29
132
  self.display_interval = 24 * 3600
30
133
 
31
- self.content_base_url = (
32
- "https://vnstock-beam.hf.space/content-delivery"
33
- )
34
- self.target_url = (
35
- "https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
36
- )
134
+ # Base endpoints for fetching remote content and linking
135
+ self.content_base_url = "https://hq.vnstocks.com/static"
136
+ self.target_url = "https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
37
137
  self.image_url = (
38
138
  "https://vnstocks.com/img/trang-chu-vnstock-python-api-phan-tich-giao-dich-chung-khoan.jpg"
39
139
  )
40
140
 
141
+ # Launch the background thread to periodically present content
142
+ logger.debug(f"[promo] is_paid_user = {self.is_paid_user}")
41
143
  self._start_periodic_display()
42
144
 
43
145
  def _start_periodic_display(self):
146
+ """
147
+ Launch a daemon thread that sleeps a random duration between 2–6 hours,
148
+ then checks if the display interval has elapsed and calls present_content.
149
+ """
150
+ logger.debug("[promo] Khởi tạo thread hiển thị quảng cáo định kỳ...")
44
151
  def periodic_display():
152
+ logger.debug("[promo] Thread quảng cáo bắt đầu chạy.")
45
153
  while True:
154
+ # Nếu là paid user thì không bao giờ hiện ads
155
+ if self.is_paid_user:
156
+ logger.debug("[promo] Đang là paid user trong thread. Không hiện quảng cáo, dừng thread.")
157
+ break
158
+ # Randomize sleep to avoid synchronized requests across instances
46
159
  sleep_time = random.randint(2 * 3600, 6 * 3600)
160
+ logger.debug(f"[promo] Thread quảng cáo sẽ ngủ {sleep_time//3600} giờ...")
47
161
  time.sleep(sleep_time)
48
162
 
163
+ # Present content if enough time has passed since last_display
49
164
  current_time = time.time()
165
+ logger.debug(f"[promo] Kiểm tra điều kiện hiện quảng cáo: time since last_display = {current_time - self.last_display}s")
50
166
  if current_time - self.last_display >= self.display_interval:
167
+ logger.debug("[promo] Đã đủ thời gian, sẽ gọi present_content(context='periodic')")
51
168
  self.present_content(context="periodic")
169
+ else:
170
+ logger.debug("[promo] Chưa đủ thời gian, chưa hiện quảng cáo.")
52
171
 
53
172
  thread = threading.Thread(target=periodic_display, daemon=True)
54
173
  thread.start()
55
174
 
56
- def fetch_remote_content(self, context="init", html=True):
175
+ def fetch_remote_content(self, context: str = "init", html: bool = True) -> str:
176
+ if self.is_paid_user:
177
+ logger.debug("Paid user detected. Skip fetching remote content (ads).")
178
+ return ""
179
+
180
+ """
181
+ Fetch promotional content from remote service with context and format flag.
182
+
183
+ Args:
184
+ context: usage context (e.g., "init", "periodic", "loop").
185
+ html: if True, request HTML; otherwise plain text.
186
+
187
+ Returns:
188
+ The content string on HTTP 200, or None on failure.
189
+ """
57
190
  try:
191
+ # Build query params and URL
58
192
  params = {"context": context, "html": "true" if html else "false"}
59
193
  url = f"{self.content_base_url}?{urllib.parse.urlencode(params)}"
60
194
 
61
195
  response = requests.get(url, timeout=3)
62
196
  if response.status_code == 200:
63
197
  return response.text
198
+ # Log non-200 responses at debug level
64
199
  logger.debug(f"Non-200 response fetching content: {response.status_code}")
65
200
  return None
66
201
  except Exception as e:
202
+ # Log exceptions without interrupting user code
67
203
  logger.debug(f"Failed to fetch remote content: {e}")
68
204
  return None
69
205
 
70
- def present_content(self, environment=None, context="init"):
206
+ def present_content(self, context: str = "init", ad_category: int = AdCategory.FREE) -> None:
207
+ environment = None
208
+ logger.debug(f"[promo] Gọi present_content(context={context}, ad_category={ad_category}). is_paid_user = {getattr(self, 'is_paid_user', None)}")
209
+ """
210
+ Display promotional content in the appropriate environment.
211
+ ad_category: Loại quảng cáo (FREE, ANNOUNCEMENT, ...)
212
+ """
213
+ # Nếu là paid user và ad_category là FREE thì skip, còn lại vẫn hiện
214
+ if getattr(self, 'is_paid_user', False) and ad_category == AdCategory.FREE:
215
+ logger.debug("[promo] Đang là paid user và ad_category là FREE. Không hiện quảng cáo.")
216
+ return
217
+
218
+ # Chỉ hiện log này nếu debug mode
219
+ if logger.level <= logging.DEBUG:
220
+ logger.debug(f"[promo] Sẽ hiển thị quảng cáo với context={context}, ad_category={ad_category}")
221
+ # Update last display timestamp
71
222
  self.last_display = time.time()
72
223
 
224
+ # Auto-detect environment if not provided
73
225
  if environment is None:
74
226
  try:
75
227
  from vnai.scope.profile import inspector
76
-
77
- environment = (
78
- inspector.examine().get("environment", "unknown")
79
- )
80
- except Exception:
228
+ environment = inspector.examine().get("environment", "unknown")
229
+ logger.debug(f"[promo] Đã detect environment: {environment}")
230
+ except Exception as e:
231
+ logger.debug(f"[promo] Không detect được environment: {e}")
81
232
  environment = "unknown"
82
233
 
83
- if environment == "jupyter":
84
- remote_content = self.fetch_remote_content(
85
- context=context, html=True
86
- )
87
- else:
88
- remote_content = self.fetch_remote_content(
89
- context=context, html=False
90
- )
91
-
92
- fallback_content = self._generate_fallback_content(context)
234
+ # Retrieve remote or HTML/text content based on environment
235
+ remote_content = self.fetch_remote_content(
236
+ context=context, html=(environment == "jupyter")
237
+ )
238
+ logger.debug(f"[promo] remote_content = {bool(remote_content)} (None -> False, có nội dung -> True)")
239
+ # Generate fallback messages if remote fetch fails
240
+ fallback = self._generate_fallback_content(context)
241
+ logger.debug(f"[promo] fallback keys: {list(fallback.keys())}")
93
242
 
94
243
  if environment == "jupyter":
244
+ logger.debug("[promo] Đang ở môi trường Jupyter, sẽ thử display HTML/Markdown.")
95
245
  try:
96
246
  from IPython.display import display, HTML, Markdown
97
247
 
98
248
  if remote_content:
249
+ logger.debug("[promo] Hiển thị quảng cáo bằng HTML từ remote_content.")
99
250
  display(HTML(remote_content))
100
251
  else:
252
+ logger.debug("[promo] Không có remote_content, thử display fallback Markdown/HTML.")
101
253
  try:
102
- display(Markdown(fallback_content["markdown"]))
103
- except Exception:
104
- display(HTML(fallback_content["html"]))
254
+ display(Markdown(fallback["markdown"]))
255
+ except Exception as e:
256
+ logger.debug(f"[promo] Lỗi khi display Markdown: {e}, fallback HTML.")
257
+ display(HTML(fallback["html"]))
105
258
  except Exception as e:
106
- logger.debug(f"Jupyter display failed: {e}")
259
+ logger.debug(f"[promo] Jupyter display failed: {e}")
107
260
 
108
261
  elif environment == "terminal":
262
+ logger.debug("[promo] Đang ở môi trường terminal, sẽ log quảng cáo ra logger.")
263
+ # Log terminal-friendly or raw content via logger
109
264
  if remote_content:
110
- logger.info(remote_content)
265
+ logger.debug("[promo] Hiển thị quảng cáo bằng remote_content cho terminal.")
266
+ logger.debug(remote_content)
111
267
  else:
112
- logger.info(fallback_content["terminal"])
268
+ logger.debug("[promo] Không có remote_content, hiển thị fallback terminal.")
269
+ logger.debug(fallback["terminal"])
113
270
 
114
271
  else:
115
- logger.info(fallback_content["simple"])
272
+ logger.debug(f"[promo] Môi trường khác ({environment}), hiển thị fallback simple.")
273
+ # Generic simple message for other environments
274
+ logger.debug(fallback["simple"])
116
275
 
117
276
  def _generate_fallback_content(self, context):
118
277
  fallback = {"html": "", "markdown": "", "terminal": "", "simple": ""}
@@ -180,39 +339,51 @@ class ContentManager:
180
339
 
181
340
  Cảm ơn bạn đã sử dụng package phân tích chứng khoán #1 tại Việt Nam
182
341
 
183
- * Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs/category/s%E1%BB%95-tay-h%C6%B0%E1%BB%9Bng-d%E1%BA%ABn)
184
- * Cộng đồng: [Nhóm Facebook](https://www.facebook.com/groups/vnstock.official)
342
+ * Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs)
343
+ * Cộng đồng: [Nhóm Facebook](https://facebook.com/groups/vnstock.official)
185
344
 
186
345
  Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.
187
346
  """
188
347
  )
189
348
  fallback["terminal"] = (
190
349
  """
191
- ╔══════════════════════════════════════════════════════════╗
192
-
193
- ║ 👋 Chào mừng bạn đến với Vnstock!
194
-
195
- ║ Cảm ơn bạn đã sử dụng package phân tích
196
- ║ chứng khoán #1 tại Việt Nam
197
-
198
- ║ ✓ Tài liệu: https://vnstocks.com/docs/category/s%E1%BB%95-tay-h%C6%B0%E1%BB%9Bng-d%E1%BA%ABn
199
- ║ ✓ Cộng đồng: https://www.facebook.com/groups/vnstock.official
200
-
201
- ║ Khám phá các tính năng mới nhất và tham gia
202
- ║ cộng đồng để nhận hỗ trợ.
203
-
204
- ╚══════════════════════════════════════════════════════════╝
350
+ ╔════════════════════════════════════════════════════════════╗
351
+
352
+ ║ 👋 Chào mừng bạn đến với Vnstock!
353
+
354
+ ║ Cảm ơn bạn đã sử dụng package phân tích
355
+ ║ chứng khoán #1 tại Việt Nam
356
+
357
+ ║ ✓ Tài liệu: https://vnstocks.com/docs
358
+ ║ ✓ Cộng đồng: https://facebook.com/groups/vnstock.official
359
+
360
+ ║ Khám phá các tính năng mới nhất và tham gia
361
+ ║ cộng đồng để nhận hỗ trợ.
362
+
363
+ ╚════════════════════════════════════════════════════════════╝
205
364
  """
206
365
  )
207
366
  fallback["simple"] = (
208
367
  "👋 Chào mừng bạn đến với Vnstock! "
209
- "Tài liệu: https://vnstocks.com/docs/tai-lieu/huong-dan-nhanh | "
210
- "Cộng đồng: https://www.facebook.com/groups/vnstock.official"
368
+ "Tài liệu: https://vnstocks.com/onboard | "
369
+ "Cộng đồng: https://facebook.com/groups/vnstock.official"
211
370
  )
212
371
  return fallback
213
372
 
214
- # Singleton instance
373
+ # Singleton instance for module-level use
215
374
  manager = ContentManager()
216
375
 
217
- def present(context="init"): # module-level shortcut
218
- return manager.present_content(context=context)
376
+ # Ép buộc hiện quảng cáo ngay khi import nếu là free user
377
+ if not getattr(manager, 'is_paid_user', False):
378
+ manager.present_content(context="auto_import")
379
+
380
+
381
+ def present(context: str = "init", ad_category: int = AdCategory.FREE) -> None:
382
+ """
383
+ Shortcut to ContentManager.present_content for external callers.
384
+
385
+ Args:
386
+ context: propagate context string to ContentManager.
387
+ ad_category: loại quảng cáo (FREE, ANNOUNCEMENT, ...)
388
+ """
389
+ manager.present_content(context=context, ad_category=ad_category)
vnai/scope/state.py CHANGED
@@ -1,7 +1,5 @@
1
- ##
2
-
3
- ##
4
-
1
+ # vnai/scope/state.py
2
+ # System state tracking
5
3
 
6
4
  import time
7
5
  import threading
@@ -11,7 +9,7 @@ from datetime import datetime
11
9
  from pathlib import Path
12
10
 
13
11
  class Tracker:
14
- #--
12
+ """Tracks system state and performance metrics"""
15
13
 
16
14
  _instance = None
17
15
  _lock = threading.Lock()
@@ -24,7 +22,7 @@ class Tracker:
24
22
  return cls._instance
25
23
 
26
24
  def _initialize(self):
27
- #--
25
+ """Initialize tracker"""
28
26
  self.metrics = {
29
27
  "startup_time": datetime.now().isoformat(),
30
28
  "function_calls": 0,
@@ -41,8 +39,7 @@ class Tracker:
41
39
 
42
40
  self.privacy_level = "standard"
43
41
 
44
- ##
45
-
42
+ # Setup data directory
46
43
  self.home_dir = Path.home()
47
44
  self.project_dir = self.home_dir / ".vnstock"
48
45
  self.project_dir.mkdir(exist_ok=True)
@@ -51,31 +48,26 @@ class Tracker:
51
48
  self.metrics_path = self.data_dir / "usage_metrics.json"
52
49
  self.privacy_config_path = self.project_dir / 'config' / "privacy.json"
53
50
 
54
- ##
55
-
51
+ # Create config directory if it doesn't exist
56
52
  os.makedirs(os.path.dirname(self.privacy_config_path), exist_ok=True)
57
53
 
58
- ##
59
-
54
+ # Load existing metrics
60
55
  self._load_metrics()
61
56
 
62
- ##
63
-
57
+ # Load privacy settings
64
58
  self._load_privacy_settings()
65
59
 
66
- ##
67
-
60
+ # Start background metrics collector
68
61
  self._start_background_collector()
69
62
 
70
63
  def _load_metrics(self):
71
- #--
64
+ """Load metrics from file"""
72
65
  if self.metrics_path.exists():
73
66
  try:
74
67
  with open(self.metrics_path, 'r') as f:
75
68
  stored_metrics = json.load(f)
76
69
 
77
- ##
78
-
70
+ # Update metrics with stored values
79
71
  for key, value in stored_metrics.items():
80
72
  if key in self.metrics:
81
73
  self.metrics[key] = value
@@ -83,7 +75,7 @@ class Tracker:
83
75
  pass
84
76
 
85
77
  def _save_metrics(self):
86
- #--
78
+ """Save metrics to file"""
87
79
  try:
88
80
  with open(self.metrics_path, 'w') as f:
89
81
  json.dump(self.metrics, f)
@@ -91,7 +83,7 @@ class Tracker:
91
83
  pass
92
84
 
93
85
  def _load_privacy_settings(self):
94
- #--
86
+ """Load privacy settings"""
95
87
  if self.privacy_config_path.exists():
96
88
  try:
97
89
  with open(self.privacy_config_path, 'r') as f:
@@ -101,7 +93,7 @@ class Tracker:
101
93
  pass
102
94
 
103
95
  def setup_privacy(self, level=None):
104
- #--
96
+ """Configure privacy level for data collection"""
105
97
  privacy_levels = {
106
98
  "minimal": "Essential system data only",
107
99
  "standard": "Performance metrics and errors",
@@ -109,107 +101,90 @@ class Tracker:
109
101
  }
110
102
 
111
103
  if level is None:
112
- ##
113
-
104
+ # Default level
114
105
  level = "standard"
115
106
 
116
107
  if level not in privacy_levels:
117
108
  raise ValueError(f"Invalid privacy level: {level}. Choose from {', '.join(privacy_levels.keys())}")
118
109
 
119
- ##
120
-
110
+ # Store preference
121
111
  self.privacy_level = level
122
112
 
123
- ##
124
-
113
+ # Store in configuration file
125
114
  with open(self.privacy_config_path, "w") as f:
126
115
  json.dump({"level": level}, f)
127
116
 
128
117
  return level
129
118
 
130
119
  def get_privacy_level(self):
131
- #--
120
+ """Get current privacy level"""
132
121
  return self.privacy_level
133
122
 
134
123
  def _start_background_collector(self):
135
- #--
124
+ """Start background metrics collection"""
136
125
  def collect_metrics():
137
126
  while True:
138
127
  try:
139
128
  import psutil
140
129
 
141
- ##
142
-
130
+ # Update peak memory
143
131
  current_process = psutil.Process()
144
132
  memory_info = current_process.memory_info()
145
- memory_usage = memory_info.rss / (1024 * 1024) ##
146
-
133
+ memory_usage = memory_info.rss / (1024 * 1024) # MB
147
134
 
148
135
  if memory_usage > self.performance_metrics["peak_memory"]:
149
136
  self.performance_metrics["peak_memory"] = memory_usage
150
137
 
151
- ##
152
-
138
+ # Save metrics periodically
153
139
  self._save_metrics()
154
140
 
155
141
  except:
156
142
  pass
157
143
 
158
- time.sleep(300) ##
159
-
144
+ time.sleep(300) # Run every 5 minutes
160
145
 
161
- ##
162
-
146
+ # Start thread
163
147
  thread = threading.Thread(target=collect_metrics, daemon=True)
164
148
  thread.start()
165
149
 
166
150
  def record(self, event_type, data=None):
167
- #--
168
- ##
169
-
151
+ """Record an event"""
152
+ # Check privacy level
170
153
  if self.privacy_level == "minimal" and event_type != "errors":
171
- ##
172
-
154
+ # In minimal mode, only track errors
173
155
  return True
174
156
 
175
- ##
176
-
157
+ # Update counts
177
158
  if event_type in self.metrics:
178
159
  self.metrics[event_type] += 1
179
160
  else:
180
161
  self.metrics[event_type] = 1
181
162
 
182
- ##
183
-
163
+ # Special handling for errors
184
164
  if event_type == "errors":
185
165
  self.performance_metrics["last_error_time"] = datetime.now().isoformat()
186
166
 
187
- ##
188
-
167
+ # Special handling for function calls with timing data
189
168
  if event_type == "function_calls" and data and "execution_time" in data:
190
- ##
191
-
169
+ # Keep up to 100 latest execution times
192
170
  self.performance_metrics["execution_times"].append(data["execution_time"])
193
171
  if len(self.performance_metrics["execution_times"]) > 100:
194
172
  self.performance_metrics["execution_times"] = self.performance_metrics["execution_times"][-100:]
195
173
 
196
- ##
197
-
174
+ # Save if metrics change significantly
198
175
  if self.metrics["function_calls"] % 100 == 0 or event_type == "errors":
199
176
  self._save_metrics()
200
177
 
201
178
  return True
202
179
 
203
180
  def get_metrics(self):
204
- #--
205
- ##
206
-
181
+ """Get current metrics"""
182
+ # Calculate derived metrics
207
183
  avg_execution_time = 0
208
184
  if self.performance_metrics["execution_times"]:
209
185
  avg_execution_time = sum(self.performance_metrics["execution_times"]) / len(self.performance_metrics["execution_times"])
210
186
 
211
- ##
212
-
187
+ # Add derived metrics to output
213
188
  output = self.metrics.copy()
214
189
  output.update({
215
190
  "avg_execution_time": avg_execution_time,
@@ -221,7 +196,7 @@ class Tracker:
221
196
  return output
222
197
 
223
198
  def reset(self):
224
- #--
199
+ """Reset metrics"""
225
200
  self.metrics = {
226
201
  "startup_time": datetime.now().isoformat(),
227
202
  "function_calls": 0,
@@ -239,11 +214,10 @@ class Tracker:
239
214
  self._save_metrics()
240
215
  return True
241
216
 
242
- ##
243
-
217
+ # Create singleton instance
244
218
  tracker = Tracker()
245
219
 
246
220
 
247
221
  def record(event_type, data=None):
248
- #--
222
+ """Record an event"""
249
223
  return tracker.record(event_type, data)
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vnai
3
- Version: 2.0.3
3
+ Version: 2.0.5
4
4
  Summary: System optimization and resource management toolkit
5
- Home-page: https://github.com/yourusername/vnai
6
- Author: Your Name
7
- Author-email: your.email@example.com
5
+ Author-email: Vnstock HQ <support@vnstocks.com>
8
6
  License: MIT
7
+ Project-URL: Homepage, https://vnstocks.com
9
8
  Classifier: Programming Language :: Python :: 3
10
9
  Classifier: Operating System :: OS Independent
11
10
  Classifier: Development Status :: 4 - Beta
@@ -17,17 +16,5 @@ Requires-Dist: requests>=2.25.0
17
16
  Requires-Dist: psutil>=5.8.0
18
17
  Provides-Extra: dev
19
18
  Requires-Dist: pytest>=6.0.0; extra == "dev"
20
- Requires-Dist: black>=21.5b2; extra == "dev"
21
- Dynamic: author
22
- Dynamic: author-email
23
- Dynamic: classifier
24
- Dynamic: description
25
- Dynamic: description-content-type
26
- Dynamic: home-page
27
- Dynamic: license
28
- Dynamic: provides-extra
29
- Dynamic: requires-dist
30
- Dynamic: requires-python
31
- Dynamic: summary
32
19
 
33
- System resource management and performance optimization toolkit
20
+ # VnAI