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,273 @@
|
|
1
|
+
import os
|
2
|
+
import inspect
|
3
|
+
import importlib
|
4
|
+
import sys
|
5
|
+
import traceback
|
6
|
+
|
7
|
+
from pkgutil import iter_modules
|
8
|
+
|
9
|
+
from copy import deepcopy
|
10
|
+
|
11
|
+
from .code_cheker.base import BaseCodeChecker
|
12
|
+
|
13
|
+
class _PluginsManagerMixin:
|
14
|
+
|
15
|
+
def __init__(self):
|
16
|
+
super(_PluginsManagerMixin, self).__init__()
|
17
|
+
self.code_checker = BaseCodeChecker()
|
18
|
+
return
|
19
|
+
|
20
|
+
@property
|
21
|
+
def is_secured(self):
|
22
|
+
val = self.log.config_data.get("SECURED", False)
|
23
|
+
return val in [True, 'True', 'true', '1', 1]
|
24
|
+
|
25
|
+
def __get_avail_plugins(self, locations, debug=True):
|
26
|
+
"""
|
27
|
+
WARNING: This function is deprecated and should not be used. Now `_get_plugin_by_name` will
|
28
|
+
works without this helper function.
|
29
|
+
|
30
|
+
The function has been changed from protected to private and should NOT be used. Was
|
31
|
+
left here only for documentation purposes
|
32
|
+
"""
|
33
|
+
if not isinstance(locations, list):
|
34
|
+
locations = [locations]
|
35
|
+
names, modules = [], []
|
36
|
+
for plugins_location in locations:
|
37
|
+
path = plugins_location.replace('.', '/')
|
38
|
+
if False:
|
39
|
+
# if we use path-based search we will have a big issue with compiled modules!
|
40
|
+
path = os.path.join(self.log.code_base_folder, path) # code base can be in another folder !
|
41
|
+
if not os.path.isdir(path):
|
42
|
+
self.P("*** attemped to search for plugins in a invalid folder: {}".format(path), color='error')
|
43
|
+
continue
|
44
|
+
all_files = os.listdir(path)
|
45
|
+
files = [os.path.splitext(x)[0] for x in all_files if '.py' in x and '__init__' not in x]
|
46
|
+
else:
|
47
|
+
found_modules = iter_modules([path])
|
48
|
+
files = [x.name for x in found_modules]
|
49
|
+
if debug:
|
50
|
+
self.P(" Found {} in '{}'".format(files, path))
|
51
|
+
modules += [plugins_location + '.' + x for x in files]
|
52
|
+
names += [x.replace('_', '').lower() for x in files]
|
53
|
+
|
54
|
+
return names, modules
|
55
|
+
|
56
|
+
def get_package_base_path(self, package_name):
|
57
|
+
"""
|
58
|
+
Return the file path of an installed package parent directory.
|
59
|
+
|
60
|
+
:param package_name: The name of the installed package.
|
61
|
+
:return: The path to the package parent.
|
62
|
+
"""
|
63
|
+
spec = importlib.util.find_spec(package_name)
|
64
|
+
if spec is not None and spec.submodule_search_locations:
|
65
|
+
return os.path.dirname(spec.submodule_search_locations[0])
|
66
|
+
else:
|
67
|
+
self.P("Package '{}' not found.".format(package_name), color='r')
|
68
|
+
return None
|
69
|
+
|
70
|
+
def _get_plugin_by_name(self, lst_plugins_locations, name, search_in_packages=None, safe=False):
|
71
|
+
name = self.log.camel_to_snake(name)
|
72
|
+
|
73
|
+
if search_in_packages is None:
|
74
|
+
search_in_packages = []
|
75
|
+
|
76
|
+
total_sub_locations = []
|
77
|
+
# First we extract all the sublocations for each package
|
78
|
+
|
79
|
+
for package in search_in_packages:
|
80
|
+
package_path = self.get_package_base_path(package)
|
81
|
+
if package_path is None:
|
82
|
+
continue
|
83
|
+
|
84
|
+
for location in lst_plugins_locations:
|
85
|
+
base_location_name = location.split('.')[0]
|
86
|
+
# if the location is not in the package we skip it (locations should begin with the package name)
|
87
|
+
# TODO: review this please
|
88
|
+
if base_location_name != package:
|
89
|
+
continue
|
90
|
+
|
91
|
+
root_location = location.replace('.', os.path.sep)
|
92
|
+
package_root_location = os.path.join(package_path, root_location)
|
93
|
+
sub_locations = self.log.get_all_subfolders(package_root_location, as_package=True)
|
94
|
+
sub_locations = [x.replace(package_path.replace(os.path.sep, '.').strip('.'), '').strip('.') for x in sub_locations]
|
95
|
+
sub_locations.append(location)
|
96
|
+
total_sub_locations += sub_locations
|
97
|
+
# end for package locations
|
98
|
+
# end for package
|
99
|
+
|
100
|
+
# Then we extract all the sublocations for local files
|
101
|
+
for location in lst_plugins_locations:
|
102
|
+
root_location = location.replace('.', os.path.sep)
|
103
|
+
sub_locations = self.log.get_all_subfolders(root_location, as_package=True)
|
104
|
+
total_sub_locations += sub_locations
|
105
|
+
# endfor local
|
106
|
+
|
107
|
+
# we remove duplicates
|
108
|
+
total_sub_locations = list(set(total_sub_locations))
|
109
|
+
lst_plugins_locations = lst_plugins_locations + total_sub_locations
|
110
|
+
for loc in lst_plugins_locations:
|
111
|
+
if loc.endswith('__pycache__'):
|
112
|
+
continue
|
113
|
+
candidate = loc + '.' + name
|
114
|
+
try:
|
115
|
+
found = importlib.util.find_spec(candidate)
|
116
|
+
# if found something
|
117
|
+
# and that thing can be loaded
|
118
|
+
if found is not None and found.loader is not None:
|
119
|
+
self.P(" Trying {}: '{}' -> FOUND!".format("[SAFE]" if safe else "[UNSAFE]", candidate))
|
120
|
+
return candidate
|
121
|
+
else:
|
122
|
+
self.P(" Trying {}: '{}' -> NOT found.".format("[SAFE]" if safe else "[UNSAFE]", candidate))
|
123
|
+
except ModuleNotFoundError:
|
124
|
+
self.P(" Invalid package: '{}'".format(candidate))
|
125
|
+
return
|
126
|
+
|
127
|
+
|
128
|
+
def _perform_module_safety_check(self, module, safe_imports=None):
|
129
|
+
good = True
|
130
|
+
msg = ''
|
131
|
+
str_code = inspect.getsource(module)
|
132
|
+
self.P(" Performing code safety check on module `{}`:".format(module.__name__), color='m')
|
133
|
+
errors = self.code_checker.check_code_text(str_code, safe_imports=safe_imports)
|
134
|
+
if errors is not None:
|
135
|
+
info = " ERROR: Unsafe code in {}:\n{}".format(
|
136
|
+
module.__name__, '\n'.join(
|
137
|
+
[' *** ' + msg + 'at line(s): {}'.format(v) for msg, v in errors.items()]
|
138
|
+
))
|
139
|
+
self.P(info, color='error')
|
140
|
+
self._create_notification(
|
141
|
+
notif="EXCEPTION", # ct.STATUS_TYPE.STATUS_EXCEPTION, -- TODO: review & change after planning dependency tree
|
142
|
+
msg="Unsafe code in `{}`".format(module.__name__),
|
143
|
+
info=info,
|
144
|
+
modulename=module.__name__,
|
145
|
+
autocomplete_info=True,
|
146
|
+
printed=True,
|
147
|
+
)
|
148
|
+
if self.is_secured:
|
149
|
+
good = False
|
150
|
+
msg = info
|
151
|
+
else:
|
152
|
+
self.P(" ********* Pluging accepted due to UNSECURED node. **************", color='error')
|
153
|
+
good = True
|
154
|
+
else:
|
155
|
+
self.P(" * * * Module '{}' code check successful * * *".format(module.__name__), color='g')
|
156
|
+
# endif bad code
|
157
|
+
self.P(" Finished performing code safety check on module `{}`".format(module.__name__), color='m')
|
158
|
+
return good, msg
|
159
|
+
|
160
|
+
def _perform_class_safety_check(self, classdef):
|
161
|
+
good = True
|
162
|
+
msg = ''
|
163
|
+
str_code = inspect.getsource(classdef)
|
164
|
+
self.P(" Performing class code safety check on class `{}`".format(classdef.__name__), color='m')
|
165
|
+
### TODO: finish code analysis using BaseCodeChecker
|
166
|
+
self.P(" Finished class code safety check on class `{}`".format(classdef.__name__), color='m')
|
167
|
+
return good, msg
|
168
|
+
|
169
|
+
def _get_module_name_and_class(self,
|
170
|
+
locations,
|
171
|
+
name,
|
172
|
+
search_in_packages=None,
|
173
|
+
suffix=None,
|
174
|
+
verbose=1,
|
175
|
+
safety_check=False,
|
176
|
+
safe_locations=None,
|
177
|
+
safe_imports=None,
|
178
|
+
class_safety_check=False,
|
179
|
+
):
|
180
|
+
if not isinstance(locations, list):
|
181
|
+
locations = [locations]
|
182
|
+
|
183
|
+
|
184
|
+
_class_name, _cls_def, _config_dict = None, None, None
|
185
|
+
simple_name = name.replace('_','')
|
186
|
+
|
187
|
+
if suffix is None:
|
188
|
+
suffix = ''
|
189
|
+
|
190
|
+
suffix = suffix.replace('_', '')
|
191
|
+
|
192
|
+
_safe_module_name = None
|
193
|
+
is_safe_plugin = False
|
194
|
+
# first search is safe locations always!
|
195
|
+
if safe_locations is not None:
|
196
|
+
if not isinstance(safe_locations, list):
|
197
|
+
safe_locations = [safe_locations]
|
198
|
+
if len(safe_locations) > 0:
|
199
|
+
_safe_module_name = self._get_plugin_by_name(safe_locations, name, search_in_packages=search_in_packages, safe=True)
|
200
|
+
|
201
|
+
if safe_imports is not None and not isinstance(safe_imports, list):
|
202
|
+
safe_imports = [safe_imports]
|
203
|
+
|
204
|
+
# not a safe module so we search in normal locations
|
205
|
+
if _safe_module_name is None:
|
206
|
+
# in packages you should have only safe locations
|
207
|
+
_user_module_name = self._get_plugin_by_name(locations, name, search_in_packages=None, safe=False)
|
208
|
+
_module_name = _user_module_name
|
209
|
+
else:
|
210
|
+
is_safe_plugin = True
|
211
|
+
_module_name = _safe_module_name
|
212
|
+
|
213
|
+
|
214
|
+
if _module_name is None:
|
215
|
+
if verbose >= 1:
|
216
|
+
self.P("Error with finding plugin '{}' in locations '{}'".format(name, locations), color='r')
|
217
|
+
return _module_name, _class_name, _cls_def, _config_dict
|
218
|
+
|
219
|
+
self.P(" Found {} plugin '{}'".format(
|
220
|
+
'safe' if is_safe_plugin else 'user', _module_name),
|
221
|
+
color='g' if is_safe_plugin else 'm'
|
222
|
+
)
|
223
|
+
|
224
|
+
safety_check = False if is_safe_plugin else safety_check
|
225
|
+
class_safety_check = False if is_safe_plugin else class_safety_check
|
226
|
+
|
227
|
+
module = None
|
228
|
+
try:
|
229
|
+
if _module_name in sys.modules:
|
230
|
+
del sys.modules[_module_name]
|
231
|
+
module = importlib.import_module(_module_name)
|
232
|
+
if module is not None and safety_check:
|
233
|
+
is_good, msg = self._perform_module_safety_check(module, safe_imports=safe_imports)
|
234
|
+
if not is_good:
|
235
|
+
err_msg = "CODE SAFETY VIOLATION: {}".format(msg)
|
236
|
+
raise ValueError(err_msg)
|
237
|
+
classes = inspect.getmembers(module, inspect.isclass)
|
238
|
+
for _cls in classes:
|
239
|
+
if _cls[0].upper() == simple_name.upper() + suffix.upper():
|
240
|
+
_class_name, _cls_def = _cls
|
241
|
+
if _class_name is None:
|
242
|
+
if verbose >= 1:
|
243
|
+
self.P("ERROR: Could not find class match for {} (suffix: {}). Available classes are: {}".format(
|
244
|
+
simple_name, suffix, [x[0] for x in classes]
|
245
|
+
), color='r')
|
246
|
+
_config_dict_from_file = getattr(module, "_CONFIG", {})
|
247
|
+
_version = getattr(module, "__VER__", None) or getattr(module, "__VER__", None)
|
248
|
+
_version = _version or "0.0.0"
|
249
|
+
# incredibly enough if we do not deepcopy somehow data will be overriden and
|
250
|
+
# used in subsequent imports: basically the module remains in the memory
|
251
|
+
# and the changed dict will be created at subsequent importlib.import !
|
252
|
+
# change below to False to replicate issue !
|
253
|
+
if isinstance(_config_dict_from_file, dict) and True:
|
254
|
+
_config_dict = deepcopy(_config_dict_from_file)
|
255
|
+
else:
|
256
|
+
_config_dict = _config_dict_from_file
|
257
|
+
|
258
|
+
_config_dict['MODULE_VERSION'] = _version # added module version if available to the config dict
|
259
|
+
|
260
|
+
if _cls_def is not None and class_safety_check:
|
261
|
+
is_good, msg = self._perform_class_safety_check(_cls_def)
|
262
|
+
if not is_good:
|
263
|
+
raise ValueError("Unsafe class code exception: {}".format(msg))
|
264
|
+
_found_location = ".".join(_module_name.split('.')[:-1])
|
265
|
+
self.P(" Plugin '{}' loaded and code checked from {}".format(name, _found_location), color='g')
|
266
|
+
except:
|
267
|
+
str_err = traceback.format_exc()
|
268
|
+
msg = "Error preparing {} with module {}:\n{}".format(
|
269
|
+
name, _module_name, str_err)
|
270
|
+
self.P(msg, color='error')
|
271
|
+
module, _class_name, _cls_def, _config_dict = None, None, None, None
|
272
|
+
|
273
|
+
return module, _class_name, _cls_def, _config_dict
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import socket
|
2
|
+
from ipaddress import ip_address, AddressValueError
|
3
|
+
|
4
|
+
def resolve_domain_or_ip(url):
|
5
|
+
"""Resolves a domain name to its IP address or verifies an IP address.
|
6
|
+
|
7
|
+
Parameters
|
8
|
+
----------
|
9
|
+
url : str
|
10
|
+
The domain name or IP address.
|
11
|
+
|
12
|
+
Returns
|
13
|
+
-------
|
14
|
+
tuple: (bool, str, str)
|
15
|
+
<success>, <ip>, <original_url>
|
16
|
+
A tuple containing a boolean indicating if the resolution was successful,
|
17
|
+
a string with the resolved or verified IP address, and the original URL.
|
18
|
+
"""
|
19
|
+
try:
|
20
|
+
# Check if the URL is an IP address
|
21
|
+
ip_address(url)
|
22
|
+
return True, url, url
|
23
|
+
except:
|
24
|
+
pass
|
25
|
+
|
26
|
+
try:
|
27
|
+
# Try to resolve the domain name to an IP address
|
28
|
+
ip_of_url = socket.gethostbyname(url)
|
29
|
+
return True, ip_of_url, url
|
30
|
+
except:
|
31
|
+
return False, None, url
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
if __name__ == '__main__':
|
37
|
+
# Usage:
|
38
|
+
url = "r9092118.ala.eu-central-1.emqxsl.com"
|
39
|
+
success, ip, original_url = resolve_domain_or_ip(url)
|
40
|
+
if not success:
|
41
|
+
print(f"Cannot connect to {original_url} (IP: {ip})")
|
42
|
+
else:
|
43
|
+
print(f"Resolved {original_url} to IP: {ip}")
|
44
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import configparser
|
4
|
+
|
5
|
+
|
6
|
+
def find_dotenv(
|
7
|
+
filename: str = '.env',
|
8
|
+
) -> str:
|
9
|
+
"""
|
10
|
+
Search the `.env` file in cwd and the directories of the files from the callstack.
|
11
|
+
Returns path to the first env file if found, or an empty string otherwise
|
12
|
+
"""
|
13
|
+
|
14
|
+
files_checked_queue = [os.getcwd()]
|
15
|
+
|
16
|
+
frame = sys._getframe()
|
17
|
+
current_dir = None
|
18
|
+
|
19
|
+
while frame is not None:
|
20
|
+
base_dir = os.path.dirname(frame.f_code.co_filename)
|
21
|
+
if current_dir != base_dir:
|
22
|
+
files_checked_queue.append(base_dir)
|
23
|
+
current_dir = base_dir
|
24
|
+
# endif
|
25
|
+
frame = frame.f_back
|
26
|
+
# endwhile
|
27
|
+
|
28
|
+
for path in files_checked_queue:
|
29
|
+
check_path = os.path.join(path, filename)
|
30
|
+
if os.path.isfile(check_path):
|
31
|
+
return check_path
|
32
|
+
# endwhile
|
33
|
+
|
34
|
+
return ''
|
35
|
+
|
36
|
+
|
37
|
+
def load_dotenv(dotenv_path=None, *, verbose=False, load_env=True):
|
38
|
+
"""
|
39
|
+
Load environment variables from a .env file.
|
40
|
+
|
41
|
+
Parameters
|
42
|
+
----------
|
43
|
+
dotenv_path : str, optional
|
44
|
+
Path to the .env file. If not specified, it will search in the current directory of each file from the call stack, by default None
|
45
|
+
verbose : bool, optional
|
46
|
+
If True, the method will print logs, by default False
|
47
|
+
load_env : bool, optional
|
48
|
+
If True, the method will load the found variables in the environment, by default True
|
49
|
+
|
50
|
+
Returns
|
51
|
+
-------
|
52
|
+
dict
|
53
|
+
A dictionary containing the variables found in the .env file
|
54
|
+
"""
|
55
|
+
if dotenv_path is None:
|
56
|
+
dotenv_path = find_dotenv()
|
57
|
+
if not os.path.exists(dotenv_path):
|
58
|
+
if verbose:
|
59
|
+
print(f"Error: `{dotenv_path or '.env'}` file not found.")
|
60
|
+
return
|
61
|
+
|
62
|
+
if verbose:
|
63
|
+
print("Loading {}...".format(dotenv_path))
|
64
|
+
parser = configparser.ConfigParser()
|
65
|
+
with open(dotenv_path, 'r') as f:
|
66
|
+
parser.read_string('[data]\n' + f.read())
|
67
|
+
dct_data = {k.upper(): v for k, v in parser['data'].items()}
|
68
|
+
|
69
|
+
if load_env:
|
70
|
+
for key, value in dct_data.items():
|
71
|
+
if verbose:
|
72
|
+
print(" Setting '{}'".format(key))
|
73
|
+
os.environ[key] = value
|
74
|
+
# endif load_env
|
75
|
+
return dct_data
|