vnai 2.0.4__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/__init__.py +1 -0
- vnai/beam/metrics.py +32 -27
- vnai/beam/quota.py +3 -0
- vnai/flow/relay.py +36 -10
- vnai/scope/promo.py +156 -37
- {vnai-2.0.4.dist-info → vnai-2.0.5.dist-info}/METADATA +4 -16
- vnai-2.0.5.dist-info/RECORD +16 -0
- {vnai-2.0.4.dist-info → vnai-2.0.5.dist-info}/WHEEL +1 -1
- vnai-2.0.4.dist-info/RECORD +0 -16
- {vnai-2.0.4.dist-info → vnai-2.0.5.dist-info}/top_level.txt +0 -0
vnai/__init__.py
CHANGED
@@ -17,6 +17,7 @@ from vnai.flow.relay import conduit, configure
|
|
17
17
|
from vnai.flow.queue import buffer
|
18
18
|
from vnai.scope.profile import inspector
|
19
19
|
from vnai.scope.state import tracker, record
|
20
|
+
import vnai.scope.promo
|
20
21
|
from vnai.scope.promo import present
|
21
22
|
|
22
23
|
# Constants for terms and conditions
|
vnai/beam/metrics.py
CHANGED
@@ -34,53 +34,58 @@ class Collector:
|
|
34
34
|
}
|
35
35
|
self.function_count = 0
|
36
36
|
self.colab_auth_triggered = False
|
37
|
+
self.max_metric_length = 200 # Keep only the latest 200 entries
|
37
38
|
|
38
39
|
def record(self, metric_type, data, priority=None):
|
39
|
-
"""Record operation metrics"""
|
40
|
+
"""Record operation metrics with deduplication and throttling"""
|
41
|
+
|
40
42
|
# Ensure data is a dictionary
|
41
43
|
if not isinstance(data, dict):
|
42
44
|
data = {"value": str(data)}
|
43
|
-
|
45
|
+
|
44
46
|
# Add timestamp if not present
|
45
47
|
if "timestamp" not in data:
|
46
48
|
data["timestamp"] = datetime.now().isoformat()
|
47
|
-
|
48
|
-
# For
|
49
|
-
|
50
|
-
|
51
|
-
# Remove any system info and just reference machine_id
|
52
|
-
if "system" in data:
|
53
|
-
del data["system"]
|
54
|
-
|
55
|
-
# Get machine_id for reference
|
49
|
+
|
50
|
+
# For non-system info, simplify and tag machine
|
51
|
+
if metric_type != "system_info":
|
52
|
+
data.pop("system", None)
|
56
53
|
from vnai.scope.profile import inspector
|
57
54
|
data["machine_id"] = inspector.fingerprint()
|
58
|
-
|
59
|
-
#
|
55
|
+
|
56
|
+
# ==== THROTTLING ====
|
57
|
+
now = time.time()
|
58
|
+
last_time = self._last_record_time.get(metric_type, 0)
|
59
|
+
if now - last_time < self.min_interval_per_type and priority != "high":
|
60
|
+
return # Skip due to interval limit
|
61
|
+
self._last_record_time[metric_type] = now
|
62
|
+
|
63
|
+
# ==== DEDUPLICATION ====
|
64
|
+
data_hash = hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest()
|
65
|
+
if data_hash in self._recent_hashes and priority != "high":
|
66
|
+
return # Skip duplicate
|
67
|
+
self._recent_hashes.append(data_hash)
|
68
|
+
|
69
|
+
# ==== RECORD LOGIC ====
|
60
70
|
if metric_type in self.metrics:
|
61
71
|
self.metrics[metric_type].append(data)
|
72
|
+
# Prune oldest if too long
|
73
|
+
if len(self.metrics[metric_type]) > self.max_metric_length:
|
74
|
+
self.metrics[metric_type] = self.metrics[metric_type][-self.max_metric_length:]
|
62
75
|
else:
|
63
76
|
self.metrics["function"].append(data)
|
64
|
-
|
65
|
-
#
|
77
|
+
|
78
|
+
# Function metric tracking (Colab trigger)
|
66
79
|
if metric_type == "function":
|
67
80
|
self.function_count += 1
|
68
|
-
|
69
|
-
# Check if we should trigger Colab authentication
|
70
81
|
if self.function_count > 10 and not self.colab_auth_triggered and 'google.colab' in sys.modules:
|
71
82
|
self.colab_auth_triggered = True
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
daemon=True
|
76
|
-
).start()
|
77
|
-
|
78
|
-
# Check buffer size and send if threshold reached
|
83
|
+
threading.Thread(target=self._trigger_colab_auth, daemon=True).start()
|
84
|
+
|
85
|
+
# Auto-send triggers
|
79
86
|
if sum(len(metric_list) for metric_list in self.metrics.values()) >= self.thresholds["buffer_size"]:
|
80
87
|
self._send_metrics()
|
81
|
-
|
82
|
-
# Send immediately for high priority metrics
|
83
|
-
if priority == "high" or (metric_type == "error"):
|
88
|
+
if priority == "high" or metric_type == "error":
|
84
89
|
self._send_metrics()
|
85
90
|
|
86
91
|
def _trigger_colab_auth(self):
|
vnai/beam/quota.py
CHANGED
@@ -47,7 +47,10 @@ class Guardian:
|
|
47
47
|
self.resource_limits["default"] = {"min": 60, "hour": 3000}
|
48
48
|
self.resource_limits["TCBS"] = {"min": 60, "hour": 3000}
|
49
49
|
self.resource_limits["VCI"] = {"min": 60, "hour": 3000}
|
50
|
+
self.resource_limits["MBK"] = {"min": 600, "hour": 36000}
|
51
|
+
self.resource_limits["MAS.ext"] = {"min": 600, "hour": 36000}
|
50
52
|
self.resource_limits["VCI.ext"] = {"min": 600, "hour": 36000}
|
53
|
+
self.resource_limits["FMK.ext"] = {"min": 600, "hour": 36000}
|
51
54
|
self.resource_limits["VND.ext"] = {"min": 600, "hour": 36000}
|
52
55
|
self.resource_limits["CAF.ext"] = {"min": 600, "hour": 36000}
|
53
56
|
self.resource_limits["SPL.ext"] = {"min": 600, "hour": 36000}
|
vnai/flow/relay.py
CHANGED
@@ -191,13 +191,37 @@ class Conduit:
|
|
191
191
|
).start()
|
192
192
|
|
193
193
|
def queue(self, package, priority=None):
|
194
|
+
# --- Auto add 'segment' field to every payload ---
|
195
|
+
try:
|
196
|
+
from vnai.scope.promo import ContentManager
|
197
|
+
is_paid = ContentManager().is_paid_user
|
198
|
+
segment_val = "paid" if is_paid else "free"
|
199
|
+
except Exception:
|
200
|
+
segment_val = "free"
|
201
|
+
|
202
|
+
def ensure_segment(d):
|
203
|
+
if not isinstance(d, dict):
|
204
|
+
return d
|
205
|
+
d = dict(d) # tạo bản sao để không ảnh hưởng dict gốc
|
206
|
+
if "segment" not in d:
|
207
|
+
d["segment"] = segment_val
|
208
|
+
return d
|
209
|
+
# Add segment to package if not present
|
210
|
+
if isinstance(package, dict) and "segment" not in package:
|
211
|
+
package["segment"] = segment_val
|
212
|
+
# Add segment to data if exists and is dict
|
213
|
+
if isinstance(package, dict) and isinstance(package.get("data"), dict):
|
214
|
+
if "segment" not in package["data"]:
|
215
|
+
package["data"]["segment"] = segment_val
|
216
|
+
# --- End auto segment ---
|
217
|
+
|
194
218
|
"""Queue data package"""
|
195
219
|
if not package:
|
196
220
|
return False
|
197
221
|
|
198
222
|
# Handle non-dictionary packages
|
199
223
|
if not isinstance(package, dict):
|
200
|
-
self.add_function_call({"message": str(package)})
|
224
|
+
self.add_function_call(ensure_segment({"message": str(package)}))
|
201
225
|
return True
|
202
226
|
|
203
227
|
# Add timestamp if not present
|
@@ -216,13 +240,12 @@ class Conduit:
|
|
216
240
|
data.pop("system")
|
217
241
|
if machine_id:
|
218
242
|
data["machine_id"] = machine_id
|
219
|
-
|
220
243
|
if package_type == "function":
|
221
|
-
self.add_function_call(data)
|
244
|
+
self.add_function_call(ensure_segment(data))
|
222
245
|
elif package_type == "api_request":
|
223
|
-
self.add_api_request(data)
|
246
|
+
self.add_api_request(ensure_segment(data))
|
224
247
|
elif package_type == "rate_limit":
|
225
|
-
self.add_rate_limit(data)
|
248
|
+
self.add_rate_limit(ensure_segment(data))
|
226
249
|
elif package_type == "system_info":
|
227
250
|
# For system info, we'll add it as a special function call
|
228
251
|
# but remove duplicated data
|
@@ -239,19 +262,22 @@ class Conduit:
|
|
239
262
|
if isinstance(metrics_list, list):
|
240
263
|
if metric_type == "function":
|
241
264
|
for item in metrics_list:
|
242
|
-
self.add_function_call(item)
|
265
|
+
self.add_function_call(ensure_segment(item))
|
243
266
|
elif metric_type == "rate_limit":
|
244
267
|
for item in metrics_list:
|
245
|
-
self.add_rate_limit(item)
|
268
|
+
self.add_rate_limit(ensure_segment(item))
|
246
269
|
elif metric_type == "request":
|
247
270
|
for item in metrics_list:
|
248
|
-
self.add_api_request(item)
|
271
|
+
self.add_api_request(ensure_segment(item))
|
249
272
|
else:
|
250
273
|
# Default to function calls
|
251
|
-
|
274
|
+
if isinstance(data, dict) and data is not package:
|
275
|
+
self.add_function_call(ensure_segment(data))
|
276
|
+
else:
|
277
|
+
self.add_function_call(ensure_segment(package))
|
252
278
|
else:
|
253
279
|
# No type specified, default to function call
|
254
|
-
self.add_function_call(package)
|
280
|
+
self.add_function_call(ensure_segment(package))
|
255
281
|
|
256
282
|
# Handle high priority items
|
257
283
|
if priority == "high":
|
vnai/scope/promo.py
CHANGED
@@ -10,6 +10,26 @@ import threading
|
|
10
10
|
import time
|
11
11
|
import urllib.parse
|
12
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
|
+
|
13
33
|
# Module-level logger setup
|
14
34
|
logger = logging.getLogger(__name__)
|
15
35
|
if not logger.hasHandlers():
|
@@ -43,19 +63,83 @@ class ContentManager:
|
|
43
63
|
"""
|
44
64
|
Internal initializer: sets up display timing, URLs, and starts the periodic display thread.
|
45
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
|
+
|
46
129
|
# Timestamp of last content display (epoch seconds)
|
47
130
|
self.last_display = 0
|
48
131
|
# Minimum interval between displays (24 hours)
|
49
132
|
self.display_interval = 24 * 3600
|
50
133
|
|
51
134
|
# Base endpoints for fetching remote content and linking
|
52
|
-
self.content_base_url = "https://
|
135
|
+
self.content_base_url = "https://hq.vnstocks.com/static"
|
53
136
|
self.target_url = "https://vnstocks.com/lp-khoa-hoc-python-chung-khoan"
|
54
137
|
self.image_url = (
|
55
138
|
"https://vnstocks.com/img/trang-chu-vnstock-python-api-phan-tich-giao-dich-chung-khoan.jpg"
|
56
139
|
)
|
57
140
|
|
58
141
|
# Launch the background thread to periodically present content
|
142
|
+
logger.debug(f"[promo] is_paid_user = {self.is_paid_user}")
|
59
143
|
self._start_periodic_display()
|
60
144
|
|
61
145
|
def _start_periodic_display(self):
|
@@ -63,21 +147,36 @@ class ContentManager:
|
|
63
147
|
Launch a daemon thread that sleeps a random duration between 2–6 hours,
|
64
148
|
then checks if the display interval has elapsed and calls present_content.
|
65
149
|
"""
|
150
|
+
logger.debug("[promo] Khởi tạo thread hiển thị quảng cáo định kỳ...")
|
66
151
|
def periodic_display():
|
152
|
+
logger.debug("[promo] Thread quảng cáo bắt đầu chạy.")
|
67
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
|
68
158
|
# Randomize sleep to avoid synchronized requests across instances
|
69
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ờ...")
|
70
161
|
time.sleep(sleep_time)
|
71
162
|
|
72
163
|
# Present content if enough time has passed since last_display
|
73
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")
|
74
166
|
if current_time - self.last_display >= self.display_interval:
|
167
|
+
logger.debug("[promo] Đã đủ thời gian, sẽ gọi present_content(context='periodic')")
|
75
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.")
|
76
171
|
|
77
172
|
thread = threading.Thread(target=periodic_display, daemon=True)
|
78
173
|
thread.start()
|
79
174
|
|
80
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
|
+
|
81
180
|
"""
|
82
181
|
Fetch promotional content from remote service with context and format flag.
|
83
182
|
|
@@ -104,16 +203,21 @@ class ContentManager:
|
|
104
203
|
logger.debug(f"Failed to fetch remote content: {e}")
|
105
204
|
return None
|
106
205
|
|
107
|
-
def present_content(self,
|
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)}")
|
108
209
|
"""
|
109
|
-
|
110
|
-
|
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.
|
210
|
+
Display promotional content in the appropriate environment.
|
211
|
+
ad_category: Loại quảng cáo (FREE, ANNOUNCEMENT, ...)
|
116
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}")
|
117
221
|
# Update last display timestamp
|
118
222
|
self.last_display = time.time()
|
119
223
|
|
@@ -122,42 +226,52 @@ class ContentManager:
|
|
122
226
|
try:
|
123
227
|
from vnai.scope.profile import inspector
|
124
228
|
environment = inspector.examine().get("environment", "unknown")
|
125
|
-
|
229
|
+
logger.debug(f"[promo] Đã detect environment: {environment}")
|
230
|
+
except Exception as e:
|
231
|
+
logger.debug(f"[promo] Không detect được environment: {e}")
|
126
232
|
environment = "unknown"
|
127
233
|
|
128
234
|
# Retrieve remote or HTML/text content based on environment
|
129
235
|
remote_content = self.fetch_remote_content(
|
130
236
|
context=context, html=(environment == "jupyter")
|
131
237
|
)
|
238
|
+
logger.debug(f"[promo] remote_content = {bool(remote_content)} (None -> False, có nội dung -> True)")
|
132
239
|
# Generate fallback messages if remote fetch fails
|
133
240
|
fallback = self._generate_fallback_content(context)
|
241
|
+
logger.debug(f"[promo] fallback keys: {list(fallback.keys())}")
|
134
242
|
|
135
243
|
if environment == "jupyter":
|
136
|
-
|
244
|
+
logger.debug("[promo] Đang ở môi trường Jupyter, sẽ thử display HTML/Markdown.")
|
137
245
|
try:
|
138
246
|
from IPython.display import display, HTML, Markdown
|
139
247
|
|
140
248
|
if remote_content:
|
249
|
+
logger.debug("[promo] Hiển thị quảng cáo bằng HTML từ remote_content.")
|
141
250
|
display(HTML(remote_content))
|
142
251
|
else:
|
143
|
-
|
252
|
+
logger.debug("[promo] Không có remote_content, thử display fallback Markdown/HTML.")
|
144
253
|
try:
|
145
254
|
display(Markdown(fallback["markdown"]))
|
146
|
-
except Exception:
|
255
|
+
except Exception as e:
|
256
|
+
logger.debug(f"[promo] Lỗi khi display Markdown: {e}, fallback HTML.")
|
147
257
|
display(HTML(fallback["html"]))
|
148
258
|
except Exception as e:
|
149
|
-
logger.debug(f"Jupyter display failed: {e}")
|
259
|
+
logger.debug(f"[promo] Jupyter display failed: {e}")
|
150
260
|
|
151
261
|
elif environment == "terminal":
|
262
|
+
logger.debug("[promo] Đang ở môi trường terminal, sẽ log quảng cáo ra logger.")
|
152
263
|
# Log terminal-friendly or raw content via logger
|
153
264
|
if remote_content:
|
154
|
-
logger.
|
265
|
+
logger.debug("[promo] Hiển thị quảng cáo bằng remote_content cho terminal.")
|
266
|
+
logger.debug(remote_content)
|
155
267
|
else:
|
156
|
-
logger.
|
268
|
+
logger.debug("[promo] Không có remote_content, hiển thị fallback terminal.")
|
269
|
+
logger.debug(fallback["terminal"])
|
157
270
|
|
158
271
|
else:
|
272
|
+
logger.debug(f"[promo] Môi trường khác ({environment}), hiển thị fallback simple.")
|
159
273
|
# Generic simple message for other environments
|
160
|
-
logger.
|
274
|
+
logger.debug(fallback["simple"])
|
161
275
|
|
162
276
|
def _generate_fallback_content(self, context):
|
163
277
|
fallback = {"html": "", "markdown": "", "terminal": "", "simple": ""}
|
@@ -225,46 +339,51 @@ class ContentManager:
|
|
225
339
|
|
226
340
|
Cảm ơn bạn đã sử dụng package phân tích chứng khoán #1 tại Việt Nam
|
227
341
|
|
228
|
-
* Tài liệu: [Sổ tay hướng dẫn](https://vnstocks.com/docs
|
229
|
-
* Cộng đồng: [Nhóm Facebook](https://
|
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)
|
230
344
|
|
231
345
|
Khám phá các tính năng mới nhất và tham gia cộng đồng để nhận hỗ trợ.
|
232
346
|
"""
|
233
347
|
)
|
234
348
|
fallback["terminal"] = (
|
235
349
|
"""
|
236
|
-
|
237
|
-
║
|
238
|
-
║ 👋 Chào mừng bạn đến với Vnstock!
|
239
|
-
║
|
240
|
-
║ Cảm ơn bạn đã sử dụng package phân tích
|
241
|
-
║ chứng khoán #1 tại Việt Nam
|
242
|
-
║
|
243
|
-
║ ✓ Tài liệu: https://vnstocks.com/docs
|
244
|
-
║ ✓ Cộng đồng: https://
|
245
|
-
║
|
246
|
-
║ Khám phá các tính năng mới nhất và tham gia
|
247
|
-
║ cộng đồng để nhận hỗ trợ.
|
248
|
-
║
|
249
|
-
|
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
|
+
╚════════════════════════════════════════════════════════════╝
|
250
364
|
"""
|
251
365
|
)
|
252
366
|
fallback["simple"] = (
|
253
367
|
"👋 Chào mừng bạn đến với Vnstock! "
|
254
|
-
"Tài liệu: https://vnstocks.com/
|
255
|
-
"Cộng đồng: https://
|
368
|
+
"Tài liệu: https://vnstocks.com/onboard | "
|
369
|
+
"Cộng đồng: https://facebook.com/groups/vnstock.official"
|
256
370
|
)
|
257
371
|
return fallback
|
258
372
|
|
259
373
|
# Singleton instance for module-level use
|
260
374
|
manager = ContentManager()
|
261
375
|
|
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
|
+
|
262
380
|
|
263
|
-
def present(context: str = "init") -> None:
|
381
|
+
def present(context: str = "init", ad_category: int = AdCategory.FREE) -> None:
|
264
382
|
"""
|
265
383
|
Shortcut to ContentManager.present_content for external callers.
|
266
384
|
|
267
385
|
Args:
|
268
386
|
context: propagate context string to ContentManager.
|
387
|
+
ad_category: loại quảng cáo (FREE, ANNOUNCEMENT, ...)
|
269
388
|
"""
|
270
|
-
|
389
|
+
manager.present_content(context=context, ad_category=ad_category)
|
@@ -1,11 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: vnai
|
3
|
-
Version: 2.0.
|
3
|
+
Version: 2.0.5
|
4
4
|
Summary: System optimization and resource management toolkit
|
5
|
-
|
6
|
-
Author: Vnstock HQ
|
7
|
-
Author-email: support@vnstocks.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,16 +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
|
-
Dynamic: author
|
21
|
-
Dynamic: author-email
|
22
|
-
Dynamic: classifier
|
23
|
-
Dynamic: description
|
24
|
-
Dynamic: description-content-type
|
25
|
-
Dynamic: home-page
|
26
|
-
Dynamic: license
|
27
|
-
Dynamic: provides-extra
|
28
|
-
Dynamic: requires-dist
|
29
|
-
Dynamic: requires-python
|
30
|
-
Dynamic: summary
|
31
19
|
|
32
|
-
|
20
|
+
# VnAI
|
@@ -0,0 +1,16 @@
|
|
1
|
+
vnai/__init__.py,sha256=t27DlC7AGj-c4MrunqFne0bohTHE4NfZQL_nEsvWmlc,9106
|
2
|
+
vnai/beam/__init__.py,sha256=xKb_iu9aAPXCulI7dENrvqVIhelSD1mIqKE9Go3GAHw,200
|
3
|
+
vnai/beam/metrics.py,sha256=Yjht8nMLxm0JaRSVcHUwHyPkfWReIzgD5uuaXAFNjlE,7472
|
4
|
+
vnai/beam/pulse.py,sha256=jp1YwjLaMhne2nYhM5PofveDsdrSp2YtewQ2jjE78Is,3470
|
5
|
+
vnai/beam/quota.py,sha256=Ob_IoVpDKL6IdxxivkU1Z5x2nvIf-X1DQeQXdCiRUiU,21424
|
6
|
+
vnai/flow/__init__.py,sha256=K3OeabzAWGrdPgTAOlDqrJh2y9aQW2pgLZg8tblN3ho,147
|
7
|
+
vnai/flow/queue.py,sha256=b9YKUbiXDZRC3fVgEnA77EO0EMXAi8eCoBkHnAUI5Sc,4162
|
8
|
+
vnai/flow/relay.py,sha256=XA4dognPrZ7IQbrgckeEjFw80IgBoK7i8LRmd1A4vR8,17058
|
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=-RlhLhPCXKgC8ofUVI3Nyl8sRAXsAb5Jr3Vcx5Kp-Pc,19193
|
12
|
+
vnai/scope/state.py,sha256=LlcZNKBy2mcAnD765BO2Tlv3Zzbak2TOEz4RUPMCFZ8,7490
|
13
|
+
vnai-2.0.5.dist-info/METADATA,sha256=mPsBTATubpJejbTeTGiKzUi0Ngpu7JamuFpITlJSTs0,666
|
14
|
+
vnai-2.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
+
vnai-2.0.5.dist-info/top_level.txt,sha256=4zI0qZHePCwvgSqXl4420sBcd0VzZn4MEcRsAIFae3k,5
|
16
|
+
vnai-2.0.5.dist-info/RECORD,,
|
vnai-2.0.4.dist-info/RECORD
DELETED
@@ -1,16 +0,0 @@
|
|
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,,
|
File without changes
|