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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
vnai/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,29 @@ import threading
6
10
  import time
7
11
  import urllib.parse
8
12
 
13
+ # Module-level logger setup
9
14
  logger = logging.getLogger(__name__)
10
15
  if not logger.hasHandlers():
16
+ # Add a simple stream handler that only outputs the message text
11
17
  handler = logging.StreamHandler()
12
18
  handler.setFormatter(logging.Formatter('%(message)s'))
13
19
  logger.addHandler(handler)
14
20
  logger.setLevel(logging.INFO)
15
21
 
16
22
  class ContentManager:
23
+ """
24
+ Singleton manager to fetch remote or fallback promotional content and
25
+ present it in different environments (Jupyter, terminal, other).
26
+
27
+ Displays content automatically at randomized intervals via a background thread.
28
+ """
17
29
  _instance = None
18
30
  _lock = threading.Lock()
19
31
 
20
32
  def __new__(cls):
33
+ """
34
+ Ensure only one instance of ContentManager is created (thread-safe).
35
+ """
21
36
  with cls._lock:
22
37
  if cls._instance is None:
23
38
  cls._instance = super(ContentManager, cls).__new__(cls)
@@ -25,27 +40,36 @@ class ContentManager:
25
40
  return cls._instance
26
41
 
27
42
  def _initialize(self):
43
+ """
44
+ Internal initializer: sets up display timing, URLs, and starts the periodic display thread.
45
+ """
46
+ # Timestamp of last content display (epoch seconds)
28
47
  self.last_display = 0
48
+ # Minimum interval between displays (24 hours)
29
49
  self.display_interval = 24 * 3600
30
50
 
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
- )
51
+ # Base endpoints for fetching remote content and linking
52
+ self.content_base_url = "https://vnstock-beam.hf.space/content-delivery"
53
+ self.target_url = "https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
37
54
  self.image_url = (
38
55
  "https://vnstocks.com/img/trang-chu-vnstock-python-api-phan-tich-giao-dich-chung-khoan.jpg"
39
56
  )
40
57
 
58
+ # Launch the background thread to periodically present content
41
59
  self._start_periodic_display()
42
60
 
43
61
  def _start_periodic_display(self):
62
+ """
63
+ Launch a daemon thread that sleeps a random duration between 2–6 hours,
64
+ then checks if the display interval has elapsed and calls present_content.
65
+ """
44
66
  def periodic_display():
45
67
  while True:
68
+ # Randomize sleep to avoid synchronized requests across instances
46
69
  sleep_time = random.randint(2 * 3600, 6 * 3600)
47
70
  time.sleep(sleep_time)
48
71
 
72
+ # Present content if enough time has passed since last_display
49
73
  current_time = time.time()
50
74
  if current_time - self.last_display >= self.display_interval:
51
75
  self.present_content(context="periodic")
@@ -53,66 +77,87 @@ class ContentManager:
53
77
  thread = threading.Thread(target=periodic_display, daemon=True)
54
78
  thread.start()
55
79
 
56
- def fetch_remote_content(self, context="init", html=True):
80
+ def fetch_remote_content(self, context: str = "init", html: bool = True) -> str:
81
+ """
82
+ Fetch promotional content from remote service with context and format flag.
83
+
84
+ Args:
85
+ context: usage context (e.g., "init", "periodic", "loop").
86
+ html: if True, request HTML; otherwise plain text.
87
+
88
+ Returns:
89
+ The content string on HTTP 200, or None on failure.
90
+ """
57
91
  try:
92
+ # Build query params and URL
58
93
  params = {"context": context, "html": "true" if html else "false"}
59
94
  url = f"{self.content_base_url}?{urllib.parse.urlencode(params)}"
60
95
 
61
96
  response = requests.get(url, timeout=3)
62
97
  if response.status_code == 200:
63
98
  return response.text
99
+ # Log non-200 responses at debug level
64
100
  logger.debug(f"Non-200 response fetching content: {response.status_code}")
65
101
  return None
66
102
  except Exception as e:
103
+ # Log exceptions without interrupting user code
67
104
  logger.debug(f"Failed to fetch remote content: {e}")
68
105
  return None
69
106
 
70
- def present_content(self, environment=None, context="init"):
107
+ def present_content(self, environment: str = None, context: str = "init") -> None:
108
+ """
109
+ Present content according to the detected environment:
110
+ - In Jupyter: use display(HTML or Markdown).
111
+ - In terminal or other: log via logger.info().
112
+
113
+ Args:
114
+ environment: override detected environment ("jupyter", "terminal", else).
115
+ context: same context flag passed to fetch_remote_content and fallback logic.
116
+ """
117
+ # Update last display timestamp
71
118
  self.last_display = time.time()
72
119
 
120
+ # Auto-detect environment if not provided
73
121
  if environment is None:
74
122
  try:
75
123
  from vnai.scope.profile import inspector
76
-
77
- environment = (
78
- inspector.examine().get("environment", "unknown")
79
- )
124
+ environment = inspector.examine().get("environment", "unknown")
80
125
  except Exception:
81
126
  environment = "unknown"
82
127
 
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)
128
+ # Retrieve remote or HTML/text content based on environment
129
+ remote_content = self.fetch_remote_content(
130
+ context=context, html=(environment == "jupyter")
131
+ )
132
+ # Generate fallback messages if remote fetch fails
133
+ fallback = self._generate_fallback_content(context)
93
134
 
94
135
  if environment == "jupyter":
136
+ # Rich display in Jupyter notebooks
95
137
  try:
96
138
  from IPython.display import display, HTML, Markdown
97
139
 
98
140
  if remote_content:
99
141
  display(HTML(remote_content))
100
142
  else:
143
+ # Try Markdown, fallback to HTML
101
144
  try:
102
- display(Markdown(fallback_content["markdown"]))
145
+ display(Markdown(fallback["markdown"]))
103
146
  except Exception:
104
- display(HTML(fallback_content["html"]))
147
+ display(HTML(fallback["html"]))
105
148
  except Exception as e:
106
149
  logger.debug(f"Jupyter display failed: {e}")
107
150
 
108
151
  elif environment == "terminal":
152
+ # Log terminal-friendly or raw content via logger
109
153
  if remote_content:
110
154
  logger.info(remote_content)
111
155
  else:
112
- logger.info(fallback_content["terminal"])
156
+ logger.info(fallback["terminal"])
113
157
 
114
158
  else:
115
- logger.info(fallback_content["simple"])
159
+ # Generic simple message for other environments
160
+ logger.info(fallback["simple"])
116
161
 
117
162
  def _generate_fallback_content(self, context):
118
163
  fallback = {"html": "", "markdown": "", "terminal": "", "simple": ""}
@@ -211,8 +256,15 @@ Khám phá các tính năng mới nhất và tham gia cộng đồng để nhậ
211
256
  )
212
257
  return fallback
213
258
 
214
- # Singleton instance
259
+ # Singleton instance for module-level use
215
260
  manager = ContentManager()
216
261
 
217
- def present(context="init"): # module-level shortcut
262
+
263
+ def present(context: str = "init") -> None:
264
+ """
265
+ Shortcut to ContentManager.present_content for external callers.
266
+
267
+ Args:
268
+ context: propagate context string to ContentManager.
269
+ """
218
270
  return manager.present_content(context=context)
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,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vnai
3
- Version: 2.0.3
3
+ Version: 2.0.4
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
+ Home-page: https://github.com/vnstock-hq/initialization/new/main
6
+ Author: Vnstock HQ
7
+ Author-email: support@vnstocks.com
8
8
  License: MIT
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  Classifier: Operating System :: OS Independent
@@ -17,7 +17,6 @@ Requires-Dist: requests>=2.25.0
17
17
  Requires-Dist: psutil>=5.8.0
18
18
  Provides-Extra: dev
19
19
  Requires-Dist: pytest>=6.0.0; extra == "dev"
20
- Requires-Dist: black>=21.5b2; extra == "dev"
21
20
  Dynamic: author
22
21
  Dynamic: author-email
23
22
  Dynamic: classifier
@@ -0,0 +1,16 @@
1
+ vnai/__init__.py,sha256=ClnS_1T23BjbzejrIW4KTsAVBhXuq9bjDUcRP5AIcPo,9082
2
+ vnai/beam/__init__.py,sha256=xKb_iu9aAPXCulI7dENrvqVIhelSD1mIqKE9Go3GAHw,200
3
+ vnai/beam/metrics.py,sha256=xVmVw93yhKeWzRZJurmrD9mWur16HyLJl_p1XqMwW_w,7187
4
+ vnai/beam/pulse.py,sha256=jp1YwjLaMhne2nYhM5PofveDsdrSp2YtewQ2jjE78Is,3470
5
+ vnai/beam/quota.py,sha256=yP5_Z62QJwOoCEgqqWuNkzm1Dar70UiJsu6W27LNqiw,21218
6
+ vnai/flow/__init__.py,sha256=K3OeabzAWGrdPgTAOlDqrJh2y9aQW2pgLZg8tblN3ho,147
7
+ vnai/flow/queue.py,sha256=b9YKUbiXDZRC3fVgEnA77EO0EMXAi8eCoBkHnAUI5Sc,4162
8
+ vnai/flow/relay.py,sha256=RtIPRZ3BlQd-XgTbisJg0iC1HqikAjHGyyo8aTj_fUw,15766
9
+ vnai/scope/__init__.py,sha256=overJZ_UiEfBRNcSieE1GPU_9X3oS4C5l6JeBaFFVxk,267
10
+ vnai/scope/profile.py,sha256=6LL7Djke9F1HVA9eEExud2jZ5yGUfy9_NYt68nIj2-8,30737
11
+ vnai/scope/promo.py,sha256=N4aWZdh92rVNovjCwuUjv-QdhoRwloPAn4Hydx4IcRs,12515
12
+ vnai/scope/state.py,sha256=LlcZNKBy2mcAnD765BO2Tlv3Zzbak2TOEz4RUPMCFZ8,7490
13
+ vnai-2.0.4.dist-info/METADATA,sha256=rgGwwXBeevv4rjzJ-hLADRkGnPcuTaC3rMnSgd_WMRw,988
14
+ vnai-2.0.4.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
15
+ vnai-2.0.4.dist-info/top_level.txt,sha256=4zI0qZHePCwvgSqXl4420sBcd0VzZn4MEcRsAIFae3k,5
16
+ vnai-2.0.4.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- vnai/__init__.py,sha256=SF3HcEadrvSmtotBCy8a6637UwTFiSAaAN0Azi0X4qg,7219
2
- vnai/beam/__init__.py,sha256=h19ZEQFnWTvqed0mpTcuQf-sT9XifWhfVbaobTEs91Q,131
3
- vnai/beam/metrics.py,sha256=lNYEeav5kMm8SGnlsxwz6aD3fRzOytopHhUPkUx9Avs,5950
4
- vnai/beam/pulse.py,sha256=-rN4tbcgNKmA74P2ThVkQqxabG0-UlhfRbFCyYPvKFw,2920
5
- vnai/beam/quota.py,sha256=DRlqZpztppdFDJymuJS6VCKLHZrkI_PX8cUtStHDSFM,16934
6
- vnai/flow/__init__.py,sha256=VW6R7c30M4q8PhOUqXyHwWlmk1BwPrYvcxtLFI0fWJo,91
7
- vnai/flow/queue.py,sha256=0R6LjF6PSUz37JV2SnsOQMBHps3bG5c0BGfEOUIPhcA,3650
8
- vnai/flow/relay.py,sha256=wIRojuZKQ5Jk1N-p2PkA9fkx-F7kwgVu0JIc0Iya5aw,13783
9
- vnai/scope/__init__.py,sha256=yB0qWMlKd2ix5tFlBcRPX6SYR1O8Di0mwgQC15u8l2o,207
10
- vnai/scope/profile.py,sha256=tIy39_FpcNKZCg3Dy5mgy0jdLQCH2FK2nfvUlTM72wM,26781
11
- vnai/scope/promo.py,sha256=NlKcyadl6BlOr2TMlKkdri4dkm8xIoI_3Z05ErcVnGo,9930
12
- vnai/scope/state.py,sha256=XUhuXNx1W6pce5vRY3IkSrR__MGetRo8lQt1bQIAOLM,6550
13
- vnai-2.0.3.dist-info/METADATA,sha256=IlnWkYA8m9Rxjyfu7JCgpwJw0C7ldherJqhQnAdRrug,1017
14
- vnai-2.0.3.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
15
- vnai-2.0.3.dist-info/top_level.txt,sha256=4zI0qZHePCwvgSqXl4420sBcd0VzZn4MEcRsAIFae3k,5
16
- vnai-2.0.3.dist-info/RECORD,,
File without changes