naeural-client 2.0.0__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.
- naeural_client/__init__.py +13 -0
- naeural_client/_ver.py +13 -0
- naeural_client/base/__init__.py +6 -0
- naeural_client/base/distributed_custom_code_presets.py +44 -0
- naeural_client/base/generic_session.py +1763 -0
- naeural_client/base/instance.py +616 -0
- naeural_client/base/payload/__init__.py +1 -0
- naeural_client/base/payload/payload.py +66 -0
- naeural_client/base/pipeline.py +1499 -0
- naeural_client/base/plugin_template.py +5209 -0
- naeural_client/base/responses.py +209 -0
- naeural_client/base/transaction.py +157 -0
- naeural_client/base_decentra_object.py +143 -0
- naeural_client/bc/__init__.py +3 -0
- naeural_client/bc/base.py +1046 -0
- naeural_client/bc/chain.py +0 -0
- naeural_client/bc/ec.py +324 -0
- naeural_client/certs/__init__.py +0 -0
- naeural_client/certs/r9092118.ala.eu-central-1.emqxsl.com.crt +22 -0
- naeural_client/code_cheker/__init__.py +1 -0
- naeural_client/code_cheker/base.py +520 -0
- naeural_client/code_cheker/checker.py +294 -0
- naeural_client/comm/__init__.py +2 -0
- naeural_client/comm/amqp_wrapper.py +338 -0
- naeural_client/comm/mqtt_wrapper.py +539 -0
- naeural_client/const/README.md +3 -0
- naeural_client/const/__init__.py +9 -0
- naeural_client/const/base.py +101 -0
- naeural_client/const/comms.py +80 -0
- naeural_client/const/environment.py +26 -0
- naeural_client/const/formatter.py +7 -0
- naeural_client/const/heartbeat.py +111 -0
- naeural_client/const/misc.py +20 -0
- naeural_client/const/payload.py +190 -0
- naeural_client/default/__init__.py +1 -0
- naeural_client/default/instance/__init__.py +4 -0
- naeural_client/default/instance/chain_dist_custom_job_01_plugin.py +54 -0
- naeural_client/default/instance/custom_web_app_01_plugin.py +118 -0
- naeural_client/default/instance/net_mon_01_plugin.py +45 -0
- naeural_client/default/instance/view_scene_01_plugin.py +28 -0
- naeural_client/default/session/mqtt_session.py +72 -0
- naeural_client/io_formatter/__init__.py +2 -0
- naeural_client/io_formatter/base/__init__.py +1 -0
- naeural_client/io_formatter/base/base_formatter.py +80 -0
- naeural_client/io_formatter/default/__init__.py +3 -0
- naeural_client/io_formatter/default/a_dummy.py +51 -0
- naeural_client/io_formatter/default/aixp1.py +113 -0
- naeural_client/io_formatter/default/default.py +22 -0
- naeural_client/io_formatter/io_formatter_manager.py +96 -0
- naeural_client/logging/__init__.py +1 -0
- naeural_client/logging/base_logger.py +2056 -0
- naeural_client/logging/logger_mixins/__init__.py +12 -0
- naeural_client/logging/logger_mixins/class_instance_mixin.py +92 -0
- naeural_client/logging/logger_mixins/computer_vision_mixin.py +443 -0
- naeural_client/logging/logger_mixins/datetime_mixin.py +344 -0
- naeural_client/logging/logger_mixins/download_mixin.py +421 -0
- naeural_client/logging/logger_mixins/general_serialization_mixin.py +242 -0
- naeural_client/logging/logger_mixins/json_serialization_mixin.py +481 -0
- naeural_client/logging/logger_mixins/pickle_serialization_mixin.py +301 -0
- naeural_client/logging/logger_mixins/process_mixin.py +63 -0
- naeural_client/logging/logger_mixins/resource_size_mixin.py +81 -0
- naeural_client/logging/logger_mixins/timers_mixin.py +501 -0
- naeural_client/logging/logger_mixins/upload_mixin.py +260 -0
- naeural_client/logging/logger_mixins/utils_mixin.py +675 -0
- naeural_client/logging/small_logger.py +93 -0
- naeural_client/logging/tzlocal/__init__.py +20 -0
- naeural_client/logging/tzlocal/unix.py +231 -0
- naeural_client/logging/tzlocal/utils.py +113 -0
- naeural_client/logging/tzlocal/win32.py +151 -0
- naeural_client/logging/tzlocal/windows_tz.py +718 -0
- naeural_client/plugins_manager_mixin.py +273 -0
- naeural_client/utils/__init__.py +2 -0
- naeural_client/utils/comm_utils.py +44 -0
- naeural_client/utils/dotenv.py +75 -0
- naeural_client-2.0.0.dist-info/METADATA +365 -0
- naeural_client-2.0.0.dist-info/RECORD +78 -0
- naeural_client-2.0.0.dist-info/WHEEL +4 -0
- naeural_client-2.0.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,520 @@
|
|
1
|
+
import io
|
2
|
+
import zlib
|
3
|
+
import sys
|
4
|
+
import base64
|
5
|
+
import traceback
|
6
|
+
import re
|
7
|
+
import inspect
|
8
|
+
import ctypes
|
9
|
+
import threading
|
10
|
+
import queue
|
11
|
+
|
12
|
+
from .checker import ASTChecker, CheckerConstants
|
13
|
+
|
14
|
+
__VER__ = '0.6.1'
|
15
|
+
|
16
|
+
UNALLOWED_DICT = {
|
17
|
+
'import ': {
|
18
|
+
'error': 'Imports are not allowed in plugin code ',
|
19
|
+
'type': 'import',
|
20
|
+
},
|
21
|
+
|
22
|
+
'from ': {
|
23
|
+
'error': 'Imports are not allowed in plugin code ',
|
24
|
+
'type': 'import',
|
25
|
+
},
|
26
|
+
|
27
|
+
'globals': {
|
28
|
+
'error': 'Global vars access is not allowed in plugin code ',
|
29
|
+
'type': CheckerConstants.var,
|
30
|
+
},
|
31
|
+
|
32
|
+
'__builtins__': {
|
33
|
+
'error': '__builtins__ access is not allowed in plugin code ',
|
34
|
+
'type': CheckerConstants.var,
|
35
|
+
},
|
36
|
+
|
37
|
+
'locals': {
|
38
|
+
'error': 'Local vars dict access is not allowed in plugin code ',
|
39
|
+
'type': CheckerConstants.var,
|
40
|
+
},
|
41
|
+
|
42
|
+
'memoryview': {
|
43
|
+
'error': 'Pointer handling is unsafe in plugin code ',
|
44
|
+
'type': CheckerConstants.var,
|
45
|
+
},
|
46
|
+
|
47
|
+
'log': {
|
48
|
+
'error': 'Logger object cannot be used directly in plugin code - please use API ',
|
49
|
+
'type': CheckerConstants.attr,
|
50
|
+
},
|
51
|
+
|
52
|
+
'vars': {
|
53
|
+
'error': 'Usage of `vars(obj)` is not allowed in plugin code ',
|
54
|
+
'type': CheckerConstants.var,
|
55
|
+
},
|
56
|
+
|
57
|
+
'dir': {
|
58
|
+
'error': 'Usage of `dir(obj)` is not allowed in plugin code ',
|
59
|
+
'type': CheckerConstants.var,
|
60
|
+
},
|
61
|
+
|
62
|
+
'global_shmem': {
|
63
|
+
'error': 'Usage of `global_shmem` is not allowed in plugin code ',
|
64
|
+
'type': CheckerConstants.attr,
|
65
|
+
},
|
66
|
+
|
67
|
+
'plugins_shmem': {
|
68
|
+
'error': 'Usage of `plugins_shmem` is not allowed in plugin code ',
|
69
|
+
'type': CheckerConstants.attr,
|
70
|
+
},
|
71
|
+
|
72
|
+
'config_data': {
|
73
|
+
'error': 'Usage of `config_data` is not allowed in plugin code ',
|
74
|
+
'type': CheckerConstants.attr,
|
75
|
+
},
|
76
|
+
|
77
|
+
'_default_config': {
|
78
|
+
'error': 'Usage of `_default_config` is not allowed in plugin code ',
|
79
|
+
'type': CheckerConstants.attr,
|
80
|
+
},
|
81
|
+
|
82
|
+
'__traceback__': {
|
83
|
+
'error': 'Usage of `__traceback__` as an attribute is not allowed in plugin code ',
|
84
|
+
'type': CheckerConstants.attr,
|
85
|
+
},
|
86
|
+
|
87
|
+
'_upstream_config': {
|
88
|
+
'error': 'Usage of `_upstream_config` is not allowed in plugin code ',
|
89
|
+
'type': CheckerConstants.attr,
|
90
|
+
},
|
91
|
+
|
92
|
+
'exec': {
|
93
|
+
'error': 'Usage of `exec()` is not allowed in plugin code ',
|
94
|
+
'type': CheckerConstants.var,
|
95
|
+
},
|
96
|
+
|
97
|
+
'eval': {
|
98
|
+
'error': 'Usage of `eval()` is not allowed in plugin code ',
|
99
|
+
'type': CheckerConstants.var,
|
100
|
+
},
|
101
|
+
|
102
|
+
'getattr': {
|
103
|
+
'error': 'Usage of `getattr()` is not allowed in plugin code ',
|
104
|
+
'type': CheckerConstants.var,
|
105
|
+
},
|
106
|
+
|
107
|
+
'open': {
|
108
|
+
'error': 'Usage of `open()` is not allowed in plugin code ',
|
109
|
+
'type': CheckerConstants.var,
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
RESULT_VARS = ['__result', '_result', 'result']
|
114
|
+
|
115
|
+
|
116
|
+
class CodeExecutionTimeoutError(Exception):
|
117
|
+
pass
|
118
|
+
|
119
|
+
|
120
|
+
class BaseCodeChecker:
|
121
|
+
"""
|
122
|
+
This class should be used either as a associated object for code checking or
|
123
|
+
as a mixin for running code
|
124
|
+
"""
|
125
|
+
|
126
|
+
def __init__(self):
|
127
|
+
super(BaseCodeChecker, self).__init__()
|
128
|
+
self.printed_lines = []
|
129
|
+
self.__exec_code_lock = threading.Lock()
|
130
|
+
return
|
131
|
+
|
132
|
+
def __msg(self, m, color='d'):
|
133
|
+
if hasattr(self, 'P'):
|
134
|
+
self.P(m, color=color)
|
135
|
+
elif hasattr(self, 'log'):
|
136
|
+
self.log.P(m, color=color)
|
137
|
+
else:
|
138
|
+
print(m)
|
139
|
+
return
|
140
|
+
|
141
|
+
def _is_safe_import(self, code, safe_imports):
|
142
|
+
if safe_imports is None:
|
143
|
+
return False
|
144
|
+
for imp in safe_imports:
|
145
|
+
if imp in code:
|
146
|
+
return True
|
147
|
+
return False
|
148
|
+
|
149
|
+
def _check_unsafe_code(self, code, safe_imports=None):
|
150
|
+
checker = ASTChecker(UNALLOWED_DICT, safe_imports)
|
151
|
+
errors = checker.validate(code)
|
152
|
+
if len(errors) == 0:
|
153
|
+
return None
|
154
|
+
return errors
|
155
|
+
|
156
|
+
# PUB
|
157
|
+
|
158
|
+
def check_code_text(self, code, safe_imports=None):
|
159
|
+
return self._check_unsafe_code(code, safe_imports=safe_imports)
|
160
|
+
|
161
|
+
def str_to_base64(self, str, verbose=False, compress=False):
|
162
|
+
l_i = len(str)
|
163
|
+
l_c = -1
|
164
|
+
b_str = bytes(str, 'utf-8')
|
165
|
+
if compress:
|
166
|
+
b_str = zlib.compress(b_str, level=9)
|
167
|
+
l_c = sys.getsizeof(b_str)
|
168
|
+
b_encoded = base64.b64encode(b_str)
|
169
|
+
str_encoded = b_encoded.decode('utf-8')
|
170
|
+
l_b64 = len(str_encoded)
|
171
|
+
if verbose:
|
172
|
+
self.__msg("Initial/Compress/B64: {}/{}/{}".format(
|
173
|
+
l_i, l_c, l_b64), color='g'
|
174
|
+
)
|
175
|
+
return str_encoded
|
176
|
+
|
177
|
+
def code_to_base64(self, code, verbose=False, compress=True, return_errors=False):
|
178
|
+
if verbose:
|
179
|
+
self.__msg("Processing:\n{}".format(code), color='y')
|
180
|
+
errors = self._check_unsafe_code(code)
|
181
|
+
if errors is not None:
|
182
|
+
err_msg = "Cannot serialize code due to: '{}'".format(errors)
|
183
|
+
self.__msg(err_msg, color='r')
|
184
|
+
return None if not return_errors else (None, err_msg)
|
185
|
+
self.__msg("Code checking succeeded", color='g')
|
186
|
+
str_encoded = self.str_to_base64(code, verbose=verbose, compress=compress)
|
187
|
+
return str_encoded if not return_errors else (str_encoded, None)
|
188
|
+
|
189
|
+
def base64_to_code(self, b64code, decompress=True):
|
190
|
+
decoded = None
|
191
|
+
try:
|
192
|
+
b_decoded = base64.b64decode(b64code)
|
193
|
+
if decompress:
|
194
|
+
b_decoded = zlib.decompress(b_decoded)
|
195
|
+
s_decoded = b_decoded.decode('utf-8')
|
196
|
+
decoded = s_decoded
|
197
|
+
except:
|
198
|
+
pass
|
199
|
+
return decoded
|
200
|
+
|
201
|
+
def prepare_b64code(self, str_b64code, check_for_result=True, result_vars=RESULT_VARS):
|
202
|
+
errors, code = None, None
|
203
|
+
code = self.base64_to_code(str_b64code)
|
204
|
+
to_check_code = code
|
205
|
+
if code is not None:
|
206
|
+
if self._can_encapsulate_code_in_method(code):
|
207
|
+
# we have a return statement in the code,
|
208
|
+
# so we need to encapsulate the code in a function
|
209
|
+
to_check_code = self._encapsulate_code_in_method(
|
210
|
+
exec_code__code=code,
|
211
|
+
exec_code__arguments=[]
|
212
|
+
)
|
213
|
+
# endif can encapsulate code in method
|
214
|
+
errors = self._check_unsafe_code(to_check_code)
|
215
|
+
if errors is None:
|
216
|
+
if code is None:
|
217
|
+
errors = 'Provided ascii data is not a valid base64 object'
|
218
|
+
# endif no valid code provided
|
219
|
+
# endif no errors
|
220
|
+
return code, errors
|
221
|
+
|
222
|
+
def _add_line_after_each_line(self, code, codeline='plugin.sleep(0.001)'):
|
223
|
+
lines = code.splitlines()
|
224
|
+
refactor = []
|
225
|
+
has_loop = False
|
226
|
+
for line in lines:
|
227
|
+
rstripped = line.rstrip()
|
228
|
+
stripped = line.lstrip()
|
229
|
+
is_loop = stripped.startswith(('while', 'for'))
|
230
|
+
has_loop = has_loop or is_loop
|
231
|
+
if is_loop and rstripped[-1] != ':':
|
232
|
+
parts = line.split(':')
|
233
|
+
if len(parts) > 0:
|
234
|
+
line = parts[0] + ': ' + codeline + ';' + parts[1]
|
235
|
+
has_loop = False # loop solved
|
236
|
+
elif has_loop and not is_loop:
|
237
|
+
nspc = len(line) - len(stripped)
|
238
|
+
spc = nspc * ' '
|
239
|
+
refactor.append(spc + codeline)
|
240
|
+
has_loop = False # loop solved
|
241
|
+
# endif
|
242
|
+
refactor.append(line)
|
243
|
+
str_refactor = '\n'.join(refactor)
|
244
|
+
return str_refactor
|
245
|
+
|
246
|
+
def _can_encapsulate_code_in_method(self, exec_code__code):
|
247
|
+
return re.search(r'\breturn\b', exec_code__code) is not None
|
248
|
+
|
249
|
+
def _encapsulate_code_in_method(self, exec_code__code, exec_code__arguments):
|
250
|
+
for i in range(len(exec_code__arguments)):
|
251
|
+
__var = exec_code__arguments[i]
|
252
|
+
if isinstance(__var, tuple):
|
253
|
+
exec_code__arguments[i] = f"{__var[0]}={__var[1]}"
|
254
|
+
exec_code__arguments = ', '.join(exec_code__arguments)
|
255
|
+
|
256
|
+
if re.search(r'\breturn\b', exec_code__code) is not None:
|
257
|
+
# we have a return statement in the code,
|
258
|
+
# so we need to encapsulate the code in a function
|
259
|
+
# 1. indent the code
|
260
|
+
exec_code__code = "\n".join([' ' + l for l in exec_code__code.splitlines()])
|
261
|
+
# 2. add the function definition
|
262
|
+
exec_code__code = "{}\n{}".format(
|
263
|
+
f"def __exec_code__({exec_code__arguments}):",
|
264
|
+
exec_code__code,
|
265
|
+
)
|
266
|
+
return exec_code__code
|
267
|
+
|
268
|
+
def custom_print(self, print_queue, *args, **kwargs):
|
269
|
+
"""
|
270
|
+
Custom print function that will be used in the plugin code.
|
271
|
+
"""
|
272
|
+
# redirect the print to the plugin cache
|
273
|
+
outstream = io.StringIO()
|
274
|
+
print(*args, file=outstream, **kwargs)
|
275
|
+
printed_value = outstream.getvalue()
|
276
|
+
print_queue.put(printed_value)
|
277
|
+
# print to the console
|
278
|
+
print(f'[CUST_CODE_PRINT]{printed_value}')
|
279
|
+
return
|
280
|
+
|
281
|
+
def execute_code(self, code, local_vars, output_queue, print_queue):
|
282
|
+
exec_code__result_var = None
|
283
|
+
exec_code__warnings = []
|
284
|
+
|
285
|
+
local_vars['print'] = lambda *args, **kwargs: self.custom_print(print_queue, *args, **kwargs)
|
286
|
+
try:
|
287
|
+
# Execute the code
|
288
|
+
with self.__exec_code_lock:
|
289
|
+
exec(code, local_vars)
|
290
|
+
|
291
|
+
# Capture result variables
|
292
|
+
for _var in local_vars.get('exec_code__result_vars', []):
|
293
|
+
if _var in local_vars:
|
294
|
+
exec_code__result_var = local_vars[_var]
|
295
|
+
break
|
296
|
+
# endfor all result vars
|
297
|
+
# endwith lock
|
298
|
+
# Put the results in the queue
|
299
|
+
output_queue.put({
|
300
|
+
"result_var": exec_code__result_var,
|
301
|
+
"warnings": exec_code__warnings,
|
302
|
+
"error": None
|
303
|
+
})
|
304
|
+
except Exception as e:
|
305
|
+
output_queue.put({
|
306
|
+
"result_var": None,
|
307
|
+
"warnings": exec_code__warnings,
|
308
|
+
"error": traceback.format_exc()
|
309
|
+
})
|
310
|
+
|
311
|
+
def __code_exec_stop_thread(self, thread):
|
312
|
+
"""
|
313
|
+
Stop the specified thread.
|
314
|
+
Parameters
|
315
|
+
----------
|
316
|
+
thread : threading.Thread
|
317
|
+
The thread to stop.
|
318
|
+
"""
|
319
|
+
tid = thread.ident
|
320
|
+
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), ctypes.py_object(SystemExit))
|
321
|
+
if res == 0:
|
322
|
+
raise ValueError("Invalid thread ID")
|
323
|
+
elif res > 1:
|
324
|
+
# If it modifies more than one thread, something went wrong, so revert it
|
325
|
+
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), 0)
|
326
|
+
raise SystemError("PyThreadState_SetAsyncExc failed")
|
327
|
+
|
328
|
+
def execute_code_with_timeout(self, code, timeout, local_vars=None):
|
329
|
+
if local_vars is None:
|
330
|
+
local_vars = {}
|
331
|
+
|
332
|
+
# Queue to collect output
|
333
|
+
output_queue = queue.Queue()
|
334
|
+
print_queue = queue.Queue()
|
335
|
+
|
336
|
+
# Create a separate thread for code execution
|
337
|
+
thread = threading.Thread(target=self.execute_code, args=(code, local_vars, output_queue, print_queue))
|
338
|
+
thread.daemon = True
|
339
|
+
# process = multiprocessing.Process(target=self.execute_code, args=(code, local_vars, output_queue, print_queue))
|
340
|
+
|
341
|
+
# Start the process
|
342
|
+
# process.start()
|
343
|
+
thread.start()
|
344
|
+
|
345
|
+
# Wait for the process to complete or timeout
|
346
|
+
# process.join(timeout)
|
347
|
+
thread.join(timeout)
|
348
|
+
|
349
|
+
# If process is still alive after timeout, terminate it
|
350
|
+
if thread.is_alive():
|
351
|
+
# process.terminate()
|
352
|
+
# thread.join()
|
353
|
+
# TODO: maybe still send partial results or prints?
|
354
|
+
self.__code_exec_stop_thread(thread)
|
355
|
+
|
356
|
+
return {
|
357
|
+
"result_var": None,
|
358
|
+
"warnings": [],
|
359
|
+
"printed_lines": [],
|
360
|
+
"error": f"Code execution took longer than {timeout} seconds."
|
361
|
+
}
|
362
|
+
# endif process is still alive
|
363
|
+
|
364
|
+
printed_lines = []
|
365
|
+
while not print_queue.empty():
|
366
|
+
printed_lines.append(print_queue.get())
|
367
|
+
# Get the output from the queue
|
368
|
+
if not output_queue.empty():
|
369
|
+
exec_result = output_queue.get()
|
370
|
+
exec_result['printed_lines'] = printed_lines
|
371
|
+
return exec_result
|
372
|
+
|
373
|
+
return {
|
374
|
+
"result_var": None,
|
375
|
+
"warnings": [],
|
376
|
+
"printed_lines": printed_lines,
|
377
|
+
"error": "No result returned."
|
378
|
+
}
|
379
|
+
|
380
|
+
def exec_code(self, str_b64code, debug=False, result_vars=None, self_var=None, modify=True, return_printed=False, timeout=None):
|
381
|
+
exec_code__result_vars = result_vars or RESULT_VARS
|
382
|
+
exec_code__warnings = []
|
383
|
+
exec_code__result_var = None
|
384
|
+
|
385
|
+
# Prepare the code
|
386
|
+
exec_code__code, exec_code__errors = self.prepare_b64code(str_b64code, result_vars=exec_code__result_vars)
|
387
|
+
|
388
|
+
if exec_code__errors:
|
389
|
+
self.__msg(f"Cannot execute remote code: {exec_code__errors}", color='r')
|
390
|
+
return exec_code__result_var, exec_code__errors, exec_code__warnings
|
391
|
+
|
392
|
+
# Optionally modify the code
|
393
|
+
if modify:
|
394
|
+
exec_code__code = self._add_line_after_each_line(code=exec_code__code)
|
395
|
+
|
396
|
+
if debug:
|
397
|
+
self.__msg(f"DEBUG EXEC: Executing:\n{exec_code__code}")
|
398
|
+
|
399
|
+
# Add `self` to locals if specified
|
400
|
+
local_vars = locals().copy()
|
401
|
+
if self_var and isinstance(self_var, str) and len(self_var) > 3:
|
402
|
+
local_vars[self_var] = self
|
403
|
+
|
404
|
+
# Handle encapsulating the code in a method if needed
|
405
|
+
if self._can_encapsulate_code_in_method(exec_code__code):
|
406
|
+
exec_code__code = self._encapsulate_code_in_method(exec_code__code, exec_code__arguments=[self_var])
|
407
|
+
exec_code__code = f"{exec_code__code}\nresult = __exec_code__({self_var})"
|
408
|
+
|
409
|
+
# Prepare to capture printed output
|
410
|
+
self.printed_lines = []
|
411
|
+
local_vars['print'] = self.custom_print
|
412
|
+
|
413
|
+
# Execute the code with a timeout
|
414
|
+
exec_result = self.execute_code_with_timeout(exec_code__code, timeout, local_vars=local_vars)
|
415
|
+
|
416
|
+
exec_code__result_var = exec_result.get("result_var")
|
417
|
+
exec_code__errors = exec_result.get("error")
|
418
|
+
exec_code__warnings.extend(exec_result.get("warnings"))
|
419
|
+
|
420
|
+
# Collect results
|
421
|
+
res = (exec_code__result_var, exec_code__errors, exec_code__warnings)
|
422
|
+
|
423
|
+
if return_printed:
|
424
|
+
res += (exec_result.get("printed_lines", []),)
|
425
|
+
|
426
|
+
return res
|
427
|
+
|
428
|
+
def _get_method_from_custom_code(self, str_b64code, debug=False, result_vars=RESULT_VARS, self_var=None, modify=True, method_arguments=[]):
|
429
|
+
exec_code__result_vars = result_vars
|
430
|
+
exec_code__debug = debug
|
431
|
+
exec_code__self_var = self_var
|
432
|
+
exec_code__modify = modify
|
433
|
+
exec_code__warnings = []
|
434
|
+
exec_code__code, exec_code__errors = self.prepare_b64code(
|
435
|
+
str_b64code,
|
436
|
+
result_vars=exec_code__result_vars,
|
437
|
+
)
|
438
|
+
exec_code__result_var = None
|
439
|
+
has_result = False
|
440
|
+
if exec_code__errors is not None:
|
441
|
+
self.__msg("Cannot execute remote code: {}".format(exec_code__errors), color='r')
|
442
|
+
return exec_code__result_var, exec_code__errors, exec_code__warnings
|
443
|
+
|
444
|
+
# code does not have any safety errors
|
445
|
+
if exec_code__modify:
|
446
|
+
exec_code__code = self._add_line_after_each_line(code=exec_code__code)
|
447
|
+
if exec_code__debug:
|
448
|
+
self.__msg("DEBUG EXEC: Executing: \n{}".format(exec_code__code))
|
449
|
+
if exec_code__self_var is not None and isinstance(exec_code__self_var, str) and len(exec_code__self_var) > 3:
|
450
|
+
locals()[exec_code__self_var] = self
|
451
|
+
|
452
|
+
try:
|
453
|
+
if self._can_encapsulate_code_in_method(exec_code__code):
|
454
|
+
# we have a return statement in the code,
|
455
|
+
# so we need to encapsulate the code in a function
|
456
|
+
exec_code__code = self._encapsulate_code_in_method(
|
457
|
+
exec_code__code=exec_code__code,
|
458
|
+
exec_code__arguments=method_arguments
|
459
|
+
)
|
460
|
+
exec_code__code = "{}\n{}".format(
|
461
|
+
exec_code__code,
|
462
|
+
f"result = __exec_code__"
|
463
|
+
)
|
464
|
+
else:
|
465
|
+
# in this case we want to have our code in a method
|
466
|
+
# so we will break here
|
467
|
+
exec_code__errors = ["Cannot encapsulate code in method. No return statement found."]
|
468
|
+
return exec_code__result_var, exec_code__errors, exec_code__warnings
|
469
|
+
# endif can encapsulate code in method
|
470
|
+
|
471
|
+
exec(exec_code__code)
|
472
|
+
if exec_code__debug:
|
473
|
+
self.__msg("DEBUG EXEC: locals(): \n{}".format(locals()))
|
474
|
+
for _var in exec_code__result_vars:
|
475
|
+
if _var in locals():
|
476
|
+
if exec_code__debug:
|
477
|
+
self.__msg("DEBUG EXEC: Extracting var '{}' from {}".format(_var, locals()))
|
478
|
+
exec_code__result_var = locals().get(_var)
|
479
|
+
has_result = True
|
480
|
+
break
|
481
|
+
if not has_result:
|
482
|
+
exec_code__warnings.append("No result variable is set. Possible options: {}".format(exec_code__result_vars))
|
483
|
+
except Exception as e:
|
484
|
+
exec_code__result_var = None
|
485
|
+
if hasattr(self, 'log'):
|
486
|
+
exec_code__errors = list(self.log.get_error_info())
|
487
|
+
exec_code__errors.append(traceback.format_exc())
|
488
|
+
else:
|
489
|
+
exec_code__errors = str(e)
|
490
|
+
# end try-except
|
491
|
+
return exec_code__result_var, exec_code__errors, exec_code__warnings
|
492
|
+
|
493
|
+
def method_to_base64(self, func, verbose=False):
|
494
|
+
code = self.get_function_source_code(func)
|
495
|
+
return self.code_to_base64(code, verbose=verbose)
|
496
|
+
|
497
|
+
def get_function_source_code(self, func):
|
498
|
+
"""
|
499
|
+
Get the source code of a function and remove the indentation.
|
500
|
+
|
501
|
+
Parameters
|
502
|
+
----------
|
503
|
+
func : Callable
|
504
|
+
The function.
|
505
|
+
|
506
|
+
Returns
|
507
|
+
-------
|
508
|
+
str
|
509
|
+
The source code of the function.
|
510
|
+
"""
|
511
|
+
plain_code = inspect.getsourcelines(func)[0]
|
512
|
+
plain_code = plain_code[1:]
|
513
|
+
first_code_line = 0
|
514
|
+
# ignore empty lines at the beginning, but keep them
|
515
|
+
while plain_code[first_code_line].strip() == '':
|
516
|
+
first_code_line += 1
|
517
|
+
indent = len(plain_code[first_code_line]) - len(plain_code[first_code_line].lstrip())
|
518
|
+
plain_code = '\n'.join([line.rstrip()[indent:] for line in plain_code])
|
519
|
+
|
520
|
+
return plain_code
|