vnai 2.0.2__py3-none-any.whl → 2.0.3__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 CHANGED
@@ -1,81 +1,300 @@
1
- _L='default'
2
- _K='standard'
3
- _J='accepted_agreement'
4
- _I='environment.json'
5
- _H='terms_agreement.txt'
6
- _G='timestamp'
7
- _F=False
8
- _E='id'
9
- _D='.vnstock'
10
- _C='machine_id'
11
- _B=None
12
- _A=True
13
- import os,pathlib,json,time,threading,functools
1
+ ##
2
+
3
+ ##
4
+
5
+
6
+ import os
7
+ import pathlib
8
+ import json
9
+ import time
10
+ import threading
11
+ import functools
14
12
  from datetime import datetime
15
- from vnai.beam.quota import guardian,optimize
16
- from vnai.beam.metrics import collector,capture
13
+
14
+ ##
15
+
16
+ from vnai.beam.quota import guardian, optimize
17
+ from vnai.beam.metrics import collector, capture
17
18
  from vnai.beam.pulse import monitor
18
- from vnai.flow.relay import conduit,configure
19
+ from vnai.flow.relay import conduit, configure
19
20
  from vnai.flow.queue import buffer
20
21
  from vnai.scope.profile import inspector
21
- from vnai.scope.state import tracker,record
22
+ from vnai.scope.state import tracker, record
22
23
  from vnai.scope.promo import present
23
- TC_VAR='ACCEPT_TC'
24
- TC_VAL='tôi đồng ý'
25
- TC_PATH=pathlib.Path.home()/_D/_E/_H
26
- TERMS_AND_CONDITIONS='\nKhi 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.\n\nChi tiết:\n- Giấy phép sử dụng phần mềm: https://vnstocks.com/docs/tai-lieu/giay-phep-su-dung\n- Chính sách quyền riêng tư: https://vnstocks.com/docs/tai-lieu/chinh-sach-quyen-rieng-tu\n'
24
+
25
+ ##
26
+
27
+ TC_VAR = "ACCEPT_TC"
28
+ TC_VAL = "tôi đồng ý"
29
+ TC_PATH = pathlib.Path.home() / ".vnstock" / "id" / "terms_agreement.txt"
30
+
31
+ TERMS_AND_CONDITIONS = """
32
+ 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.
33
+
34
+ Chi tiết:
35
+ - Giấy phép sử dụng phần mềm: https://vnstocks.com/docs/tai-lieu/giay-phep-su-dung
36
+ - Chính sách quyền riêng tư: https://vnstocks.com/docs/tai-lieu/chinh-sach-quyen-rieng-tu
37
+ """
38
+
27
39
  class Core:
28
- def __init__(A):A.initialized=_F;A.webhook_url=_B;A.init_time=datetime.now().isoformat();A.home_dir=pathlib.Path.home();A.project_dir=A.home_dir/_D;A.id_dir=A.project_dir/_E;A.terms_file_path=TC_PATH;A.system_info=_B;A.project_dir.mkdir(exist_ok=_A);A.id_dir.mkdir(exist_ok=_A);A.initialize()
29
- def initialize(A,webhook_url=_B):
30
- C=webhook_url
31
- if A.initialized:return _A
32
- if not A._check_terms():A._accept_terms()
33
- from vnai.scope.profile import inspector as B;B.setup_vnstock_environment();present()
34
- if C:A.webhook_url=C;configure(C)
35
- record('initialization',{_G:datetime.now().isoformat()});A.system_info=B.examine();conduit.queue({'type':'system_info','data':{'commercial':B.detect_commercial_usage(),'packages':B.scan_packages()}},priority='high');A.initialized=_A;return _A
36
- def _check_terms(A):return os.path.exists(A.terms_file_path)
37
- def _accept_terms(C):
38
- A=inspector.examine()
39
- if TC_VAR in os.environ and os.environ[TC_VAR]==TC_VAL:E=TC_VAL
40
- else:E=TC_VAL;os.environ[TC_VAR]=TC_VAL
41
- D=datetime.now();F=f"""Người dùng có mã nhận dạng {A[_C]} đã chấp nhận điều khoản & điều kiện sử dụng Vnstock lúc {D}
42
- ---
43
-
44
- THÔNG TIN THIẾT BỊ: {json.dumps(A,indent=2)}
45
-
46
- Đính kèm bản sao nội dung bạn đã đọc, hiểu rõ và đồng ý dưới đây:
47
- {TERMS_AND_CONDITIONS}"""
48
- with open(C.terms_file_path,'w',encoding='utf-8')as B:B.write(F)
49
- G=C.id_dir/_I;H={_J:_A,_G:D.isoformat(),_C:A[_C]}
50
- with open(G,'w')as B:json.dump(H,B)
51
- return _A
52
- def status(A):return{'initialized':A.initialized,'health':monitor.report(),'metrics':tracker.get_metrics()}
53
- def configure_privacy(B,level=_K):from vnai.scope.state import tracker as A;return A.setup_privacy(level)
54
- core=Core()
55
- def tc_init(webhook_url=_B):return core.initialize(webhook_url)
56
- def setup(webhook_url=_B):return core.initialize(webhook_url)
57
- def optimize_execution(resource_type=_L):return optimize(resource_type)
58
- def agg_execution(resource_type=_L):return optimize(resource_type,ad_cooldown=1500,content_trigger_threshold=100000)
59
- def measure_performance(module_type='function'):return capture(module_type)
60
- def accept_license_terms(terms_text=_B):
61
- A=terms_text
62
- if A is _B:A=TERMS_AND_CONDITIONS
63
- D=inspector.examine();C=pathlib.Path.home()/_D/_E/_H;os.makedirs(os.path.dirname(C),exist_ok=_A)
64
- with open(C,'w',encoding='utf-8')as B:B.write(f"Terms accepted at {datetime.now().isoformat()}\n");B.write(f"System: {json.dumps(D)}\n\n");B.write(A)
65
- return _A
40
+ #--
41
+
42
+ def __init__(self):
43
+ #--
44
+ self.initialized = False
45
+ self.webhook_url = None
46
+ self.init_time = datetime.now().isoformat()
47
+ self.home_dir = pathlib.Path.home()
48
+ self.project_dir = self.home_dir / ".vnstock"
49
+ self.id_dir = self.project_dir / 'id'
50
+ self.terms_file_path = TC_PATH
51
+ self.system_info = None
52
+
53
+ ##
54
+
55
+ self.project_dir.mkdir(exist_ok=True)
56
+ self.id_dir.mkdir(exist_ok=True)
57
+
58
+ ##
59
+
60
+ self.initialize()
61
+
62
+ def initialize(self, webhook_url=None):
63
+ #--
64
+ if self.initialized:
65
+ return True
66
+
67
+ ##
68
+
69
+ if not self._check_terms():
70
+ self._accept_terms()
71
+
72
+ ##
73
+
74
+ from vnai.scope.profile import inspector
75
+ inspector.setup_vnstock_environment()
76
+
77
+ ##
78
+
79
+ present()
80
+
81
+ ##
82
+
83
+ if webhook_url:
84
+ self.webhook_url = webhook_url
85
+ configure(webhook_url)
86
+
87
+ ##
88
+
89
+ record("initialization", {"timestamp": datetime.now().isoformat()})
90
+
91
+ ##
92
+
93
+ self.system_info = inspector.examine()
94
+
95
+ ##
96
+
97
+ conduit.queue({
98
+ "type": "system_info",
99
+ "data": {
100
+ "commercial": inspector.detect_commercial_usage(),
101
+ "packages": inspector.scan_packages()
102
+ }
103
+ }, priority="high")
104
+
105
+ self.initialized = True
106
+ return True
107
+
108
+ def _check_terms(self):
109
+ #--
110
+ return os.path.exists(self.terms_file_path)
111
+
112
+ def _accept_terms(self):
113
+ #--
114
+ ##
115
+
116
+ system_info = inspector.examine()
117
+
118
+ ##
119
+
120
+ if TC_VAR in os.environ and os.environ[TC_VAR] == TC_VAL:
121
+ response = TC_VAL
122
+ else:
123
+ ##
124
+
125
+ response = TC_VAL
126
+ os.environ[TC_VAR] = TC_VAL
127
+
128
+ ##
129
+
130
+ now = datetime.now()
131
+ signed_agreement = (
132
+ f"Người dùng có mã nhận dạng {system_info['machine_id']} đã chấp nhận "
133
+ f"điều khoản & điều kiện sử dụng Vnstock lúc {now}\n"
134
+ f"---\n\n"
135
+ f"THÔNG TIN THIẾT BỊ: {json.dumps(system_info, indent=2)}\n\n"
136
+ f"Đính kèm bản sao nội dung bạn đã đọc, hiểu rõ và đồng ý dưới đây:\n"
137
+ f"{TERMS_AND_CONDITIONS}"
138
+ )
139
+
140
+ ##
141
+
142
+ with open(self.terms_file_path, "w", encoding="utf-8") as f:
143
+ f.write(signed_agreement)
144
+
145
+ ##
146
+
147
+ env_file = self.id_dir / "environment.json"
148
+ env_data = {
149
+ "accepted_agreement": True,
150
+ "timestamp": now.isoformat(),
151
+ "machine_id": system_info['machine_id']
152
+ }
153
+
154
+ with open(env_file, "w") as f:
155
+ json.dump(env_data, f)
156
+
157
+ return True
158
+
159
+ def status(self):
160
+ #--
161
+ return {
162
+ "initialized": self.initialized,
163
+ "health": monitor.report(),
164
+ "metrics": tracker.get_metrics()
165
+ ##
166
+
167
+ }
168
+
169
+ def configure_privacy(self, level="standard"):
170
+ #--
171
+ from vnai.scope.state import tracker
172
+ return tracker.setup_privacy(level)
173
+
174
+
175
+ ##
176
+
177
+ core = Core()
178
+
179
+ ##
180
+
181
+ def tc_init(webhook_url=None):
182
+ return core.initialize(webhook_url)
183
+
184
+ ##
185
+
186
+ def setup(webhook_url=None):
187
+ #--
188
+ return core.initialize(webhook_url)
189
+
190
+ def optimize_execution(resource_type="default"):
191
+ #--
192
+ return optimize(resource_type)
193
+
194
+ def agg_execution(resource_type="default"):
195
+ #--
196
+ return optimize(resource_type, ad_cooldown=1500, content_trigger_threshold=100000)
197
+
198
+ def measure_performance(module_type="function"):
199
+ #--
200
+ return capture(module_type)
201
+
202
+ def accept_license_terms(terms_text=None):
203
+ #--
204
+ if terms_text is None:
205
+ terms_text = TERMS_AND_CONDITIONS
206
+
207
+ ##
208
+
209
+ system_info = inspector.examine()
210
+
211
+ ##
212
+
213
+ terms_file = pathlib.Path.home() / ".vnstock" / "id" / "terms_agreement.txt"
214
+ os.makedirs(os.path.dirname(terms_file), exist_ok=True)
215
+
216
+ with open(terms_file, "w", encoding="utf-8") as f:
217
+ f.write(f"Terms accepted at {datetime.now().isoformat()}\n")
218
+ f.write(f"System: {json.dumps(system_info)}\n\n")
219
+ f.write(terms_text)
220
+
221
+ return True
222
+
66
223
  def accept_vnstock_terms():
67
- from vnai.scope.profile import inspector as C;D=C.examine();E=pathlib.Path.home();A=E/_D;A.mkdir(exist_ok=_A);B=A/_E;B.mkdir(exist_ok=_A);F=B/_I;G={_J:_A,_G:datetime.now().isoformat(),_C:D[_C]}
68
- try:
69
- with open(F,'w')as H:json.dump(G,H)
70
- print('Vnstock terms accepted successfully.');return _A
71
- except Exception as I:print(f"Error accepting terms: {I}");return _F
72
- def setup_for_colab():from vnai.scope.profile import inspector as A;A.detect_colab_with_delayed_auth(immediate=_A);A.setup_vnstock_environment();return'Environment set up for Google Colab'
73
- def display_content():return present()
74
- def configure_privacy(level=_K):from vnai.scope.state import tracker as A;return A.setup_privacy(level)
75
- def check_commercial_usage():from vnai.scope.profile import inspector as A;return A.detect_commercial_usage()
76
- def authenticate_for_persistence():from vnai.scope.profile import inspector as A;return A.get_or_create_user_id()
224
+ #--
225
+ ##
226
+
227
+ from vnai.scope.profile import inspector
228
+ system_info = inspector.examine()
229
+
230
+ ##
231
+
232
+ home_dir = pathlib.Path.home()
233
+ project_dir = home_dir / ".vnstock"
234
+ project_dir.mkdir(exist_ok=True)
235
+ id_dir = project_dir / 'id'
236
+ id_dir.mkdir(exist_ok=True)
237
+
238
+ ##
239
+
240
+ env_file = id_dir / "environment.json"
241
+ env_data = {
242
+ "accepted_agreement": True,
243
+ "timestamp": datetime.now().isoformat(),
244
+ "machine_id": system_info['machine_id']
245
+ }
246
+
247
+ try:
248
+ with open(env_file, "w") as f:
249
+ json.dump(env_data, f)
250
+ print("Vnstock terms accepted successfully.")
251
+ return True
252
+ except Exception as e:
253
+ print(f"Error accepting terms: {e}")
254
+ return False
255
+
256
+ def setup_for_colab():
257
+ #--
258
+ from vnai.scope.profile import inspector
259
+
260
+ ##
261
+
262
+ inspector.detect_colab_with_delayed_auth(immediate=True)
263
+
264
+ ##
265
+
266
+ inspector.setup_vnstock_environment()
267
+
268
+ return "Environment set up for Google Colab"
269
+
270
+ def display_content():
271
+ #--
272
+ return present()
273
+
274
+ def configure_privacy(level="standard"):
275
+ #--
276
+ from vnai.scope.state import tracker
277
+ return tracker.setup_privacy(level)
278
+
279
+ def check_commercial_usage():
280
+ #--
281
+ from vnai.scope.profile import inspector
282
+ return inspector.detect_commercial_usage()
283
+
284
+ def authenticate_for_persistence():
285
+ #--
286
+ from vnai.scope.profile import inspector
287
+ return inspector.get_or_create_user_id()
288
+
77
289
  def configure_webhook(webhook_id='80b8832b694a75c8ddc811ac7882a3de'):
78
- A=webhook_id
79
- if not A:return _F
80
- from vnai.flow.relay import configure as B;C=f"https://botbuilder.larksuite.com/api/trigger-webhook/{A}";return B(C)
290
+ #--
291
+ if not webhook_id:
292
+ return False
293
+
294
+ from vnai.flow.relay import configure
295
+ webhook_url = f'https://botbuilder.larksuite.com/api/trigger-webhook/{webhook_id}'
296
+ return configure(webhook_url)
297
+
298
+ ##
299
+
81
300
  configure_webhook()
vnai/beam/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
- from vnai.beam.quota import guardian,optimize
2
- from vnai.beam.metrics import collector,capture
1
+ from vnai.beam.quota import guardian, optimize
2
+ from vnai.beam.metrics import collector, capture
3
3
  from vnai.beam.pulse import monitor
vnai/beam/metrics.py CHANGED
@@ -1,59 +1,209 @@
1
- _K='success'
2
- _J='buffer_size'
3
- _I='request'
4
- _H='rate_limit'
5
- _G='execution_time'
6
- _F='timestamp'
7
- _E=False
8
- _D=True
9
- _C='error'
10
- _B=None
11
- _A='function'
12
- import sys,time,threading
1
+ ##
2
+
3
+ ##
4
+
5
+
6
+ import sys
7
+ import time
8
+ import threading
13
9
  from datetime import datetime
10
+
14
11
  class Collector:
15
- _instance=_B;_lock=threading.Lock()
16
- def __new__(A):
17
- with A._lock:
18
- if A._instance is _B:A._instance=super(Collector,A).__new__(A);A._instance._initialize()
19
- return A._instance
20
- def _initialize(A):A.metrics={_A:[],_H:[],_I:[],_C:[]};A.thresholds={_J:50,'error_threshold':.1,'performance_threshold':5.};A.function_count=0;A.colab_auth_triggered=_E
21
- def record(A,metric_type,data,priority=_B):
22
- D='system';C=metric_type;B=data
23
- if not isinstance(B,dict):B={'value':str(B)}
24
- if _F not in B:B[_F]=datetime.now().isoformat()
25
- if C!='system_info'and isinstance(B,dict):
26
- if D in B:del B[D]
27
- from vnai.scope.profile import inspector as E;B['machine_id']=E.fingerprint()
28
- if C in A.metrics:A.metrics[C].append(B)
29
- else:A.metrics[_A].append(B)
30
- if C==_A:
31
- A.function_count+=1
32
- if A.function_count>10 and not A.colab_auth_triggered and'google.colab'in sys.modules:A.colab_auth_triggered=_D;threading.Thread(target=A._trigger_colab_auth,daemon=_D).start()
33
- if sum(len(A)for A in A.metrics.values())>=A.thresholds[_J]:A._send_metrics()
34
- if priority=='high'or C==_C:A._send_metrics()
35
- def _trigger_colab_auth(B):
36
- try:from vnai.scope.profile import inspector as A;A.get_or_create_user_id()
37
- except:pass
38
- def _send_metrics(F):
39
- E='vnai';D='source';C='unknown';from vnai.flow.relay import track_function_call as H,track_rate_limit as I,track_api_request as J
40
- for(B,G)in F.metrics.items():
41
- if not G:continue
42
- for A in G:
43
- try:
44
- if B==_A:H(function_name=A.get(_A,C),source=A.get(D,E),execution_time=A.get(_G,0),success=A.get(_K,_D),error=A.get(_C),args=A.get('args'))
45
- elif B==_H:I(source=A.get(D,E),limit_type=A.get('limit_type',C),limit_value=A.get('limit_value',0),current_usage=A.get('current_usage',0),is_exceeded=A.get('is_exceeded',_E))
46
- elif B==_I:J(endpoint=A.get('endpoint',C),source=A.get(D,E),method=A.get('method','GET'),status_code=A.get('status_code',200),execution_time=A.get(_G,0),request_size=A.get('request_size',0),response_size=A.get('response_size',0))
47
- except Exception as K:continue
48
- F.metrics[B]=[]
49
- def get_metrics_summary(A):return{A:len(B)for(A,B)in A.metrics.items()}
50
- collector=Collector()
51
- def capture(module_type=_A):
52
- def A(func):
53
- def A(*A,**D):
54
- E=time.time();B=_E;C=_B
55
- try:F=func(*A,**D);B=_D;return F
56
- except Exception as G:C=str(G);raise
57
- finally:H=time.time()-E;collector.record(module_type,{_A:func.__name__,_G:H,_K:B,_C:C,_F:datetime.now().isoformat(),'args':str(A)[:100]if A else _B})
58
- return A
59
- return A
12
+ #--
13
+
14
+ _instance = None
15
+ _lock = threading.Lock()
16
+
17
+ def __new__(cls):
18
+ with cls._lock:
19
+ if cls._instance is None:
20
+ cls._instance = super(Collector, cls).__new__(cls)
21
+ cls._instance._initialize()
22
+ return cls._instance
23
+
24
+ def _initialize(self):
25
+ #--
26
+ self.metrics = {
27
+ "function": [],
28
+ "rate_limit": [],
29
+ "request": [],
30
+ "error": []
31
+ }
32
+ self.thresholds = {
33
+ "buffer_size": 50,
34
+ "error_threshold": 0.1,
35
+ "performance_threshold": 5.0
36
+ }
37
+ self.function_count = 0
38
+ self.colab_auth_triggered = False
39
+
40
+ def record(self, metric_type, data, priority=None):
41
+ #--
42
+ ##
43
+
44
+ if not isinstance(data, dict):
45
+ data = {"value": str(data)}
46
+
47
+ ##
48
+
49
+ if "timestamp" not in data:
50
+ data["timestamp"] = datetime.now().isoformat()
51
+
52
+ ##
53
+
54
+ ##
55
+
56
+ if metric_type != "system_info" and isinstance(data, dict):
57
+ ##
58
+
59
+ if "system" in data:
60
+ del data["system"]
61
+
62
+ ##
63
+
64
+ from vnai.scope.profile import inspector
65
+ data["machine_id"] = inspector.fingerprint()
66
+
67
+ ##
68
+
69
+ if metric_type in self.metrics:
70
+ self.metrics[metric_type].append(data)
71
+ else:
72
+ self.metrics["function"].append(data)
73
+
74
+ ##
75
+
76
+ if metric_type == "function":
77
+ self.function_count += 1
78
+
79
+ ##
80
+
81
+ if self.function_count > 10 and not self.colab_auth_triggered and 'google.colab' in sys.modules:
82
+ self.colab_auth_triggered = True
83
+ ##
84
+
85
+ threading.Thread(
86
+ target=self._trigger_colab_auth,
87
+ daemon=True
88
+ ).start()
89
+
90
+ ##
91
+
92
+ if sum(len(metric_list) for metric_list in self.metrics.values()) >= self.thresholds["buffer_size"]:
93
+ self._send_metrics()
94
+
95
+ ##
96
+
97
+ if priority == "high" or (metric_type == "error"):
98
+ self._send_metrics()
99
+
100
+ def _trigger_colab_auth(self):
101
+ #--
102
+ try:
103
+ from vnai.scope.profile import inspector
104
+ inspector.get_or_create_user_id()
105
+ except:
106
+ pass ##
107
+
108
+
109
+ def _send_metrics(self):
110
+ #--
111
+ ##
112
+
113
+ from vnai.flow.relay import track_function_call, track_rate_limit, track_api_request
114
+
115
+ ##
116
+
117
+ for metric_type, data_list in self.metrics.items():
118
+ if not data_list:
119
+ continue
120
+
121
+ ##
122
+
123
+ for data in data_list:
124
+ try:
125
+ if metric_type == "function":
126
+ ##
127
+
128
+ track_function_call(
129
+ function_name=data.get("function", "unknown"),
130
+ source=data.get("source", "vnai"),
131
+ execution_time=data.get("execution_time", 0),
132
+ success=data.get("success", True),
133
+ error=data.get("error"),
134
+ args=data.get("args")
135
+ )
136
+ elif metric_type == "rate_limit":
137
+ ##
138
+
139
+ track_rate_limit(
140
+ source=data.get("source", "vnai"),
141
+ limit_type=data.get("limit_type", "unknown"),
142
+ limit_value=data.get("limit_value", 0),
143
+ current_usage=data.get("current_usage", 0),
144
+ is_exceeded=data.get("is_exceeded", False)
145
+ )
146
+ elif metric_type == "request":
147
+ ##
148
+
149
+ track_api_request(
150
+ endpoint=data.get("endpoint", "unknown"),
151
+ source=data.get("source", "vnai"),
152
+ method=data.get("method", "GET"),
153
+ status_code=data.get("status_code", 200),
154
+ execution_time=data.get("execution_time", 0),
155
+ request_size=data.get("request_size", 0),
156
+ response_size=data.get("response_size", 0)
157
+ )
158
+ except Exception as e:
159
+ ##
160
+
161
+ continue
162
+
163
+ ##
164
+
165
+ self.metrics[metric_type] = []
166
+
167
+ def get_metrics_summary(self):
168
+ #--
169
+ return {
170
+ metric_type: len(data_list)
171
+ for metric_type, data_list in self.metrics.items()
172
+ }
173
+
174
+ ##
175
+
176
+ collector = Collector()
177
+
178
+ def capture(module_type="function"):
179
+ #--
180
+ def decorator(func):
181
+ def wrapper(*args, **kwargs):
182
+ start_time = time.time()
183
+ success = False
184
+ error = None
185
+
186
+ try:
187
+ result = func(*args, **kwargs)
188
+ success = True
189
+ return result
190
+ except Exception as e:
191
+ error = str(e)
192
+ raise
193
+ finally:
194
+ execution_time = time.time() - start_time
195
+
196
+ collector.record(
197
+ module_type,
198
+ {
199
+ "function": func.__name__,
200
+ "execution_time": execution_time,
201
+ "success": success,
202
+ "error": error,
203
+ "timestamp": datetime.now().isoformat(),
204
+ "args": str(args)[:100] if args else None ##
205
+
206
+ }
207
+ )
208
+ return wrapper
209
+ return decorator