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.
Files changed (78) hide show
  1. naeural_client/__init__.py +13 -0
  2. naeural_client/_ver.py +13 -0
  3. naeural_client/base/__init__.py +6 -0
  4. naeural_client/base/distributed_custom_code_presets.py +44 -0
  5. naeural_client/base/generic_session.py +1763 -0
  6. naeural_client/base/instance.py +616 -0
  7. naeural_client/base/payload/__init__.py +1 -0
  8. naeural_client/base/payload/payload.py +66 -0
  9. naeural_client/base/pipeline.py +1499 -0
  10. naeural_client/base/plugin_template.py +5209 -0
  11. naeural_client/base/responses.py +209 -0
  12. naeural_client/base/transaction.py +157 -0
  13. naeural_client/base_decentra_object.py +143 -0
  14. naeural_client/bc/__init__.py +3 -0
  15. naeural_client/bc/base.py +1046 -0
  16. naeural_client/bc/chain.py +0 -0
  17. naeural_client/bc/ec.py +324 -0
  18. naeural_client/certs/__init__.py +0 -0
  19. naeural_client/certs/r9092118.ala.eu-central-1.emqxsl.com.crt +22 -0
  20. naeural_client/code_cheker/__init__.py +1 -0
  21. naeural_client/code_cheker/base.py +520 -0
  22. naeural_client/code_cheker/checker.py +294 -0
  23. naeural_client/comm/__init__.py +2 -0
  24. naeural_client/comm/amqp_wrapper.py +338 -0
  25. naeural_client/comm/mqtt_wrapper.py +539 -0
  26. naeural_client/const/README.md +3 -0
  27. naeural_client/const/__init__.py +9 -0
  28. naeural_client/const/base.py +101 -0
  29. naeural_client/const/comms.py +80 -0
  30. naeural_client/const/environment.py +26 -0
  31. naeural_client/const/formatter.py +7 -0
  32. naeural_client/const/heartbeat.py +111 -0
  33. naeural_client/const/misc.py +20 -0
  34. naeural_client/const/payload.py +190 -0
  35. naeural_client/default/__init__.py +1 -0
  36. naeural_client/default/instance/__init__.py +4 -0
  37. naeural_client/default/instance/chain_dist_custom_job_01_plugin.py +54 -0
  38. naeural_client/default/instance/custom_web_app_01_plugin.py +118 -0
  39. naeural_client/default/instance/net_mon_01_plugin.py +45 -0
  40. naeural_client/default/instance/view_scene_01_plugin.py +28 -0
  41. naeural_client/default/session/mqtt_session.py +72 -0
  42. naeural_client/io_formatter/__init__.py +2 -0
  43. naeural_client/io_formatter/base/__init__.py +1 -0
  44. naeural_client/io_formatter/base/base_formatter.py +80 -0
  45. naeural_client/io_formatter/default/__init__.py +3 -0
  46. naeural_client/io_formatter/default/a_dummy.py +51 -0
  47. naeural_client/io_formatter/default/aixp1.py +113 -0
  48. naeural_client/io_formatter/default/default.py +22 -0
  49. naeural_client/io_formatter/io_formatter_manager.py +96 -0
  50. naeural_client/logging/__init__.py +1 -0
  51. naeural_client/logging/base_logger.py +2056 -0
  52. naeural_client/logging/logger_mixins/__init__.py +12 -0
  53. naeural_client/logging/logger_mixins/class_instance_mixin.py +92 -0
  54. naeural_client/logging/logger_mixins/computer_vision_mixin.py +443 -0
  55. naeural_client/logging/logger_mixins/datetime_mixin.py +344 -0
  56. naeural_client/logging/logger_mixins/download_mixin.py +421 -0
  57. naeural_client/logging/logger_mixins/general_serialization_mixin.py +242 -0
  58. naeural_client/logging/logger_mixins/json_serialization_mixin.py +481 -0
  59. naeural_client/logging/logger_mixins/pickle_serialization_mixin.py +301 -0
  60. naeural_client/logging/logger_mixins/process_mixin.py +63 -0
  61. naeural_client/logging/logger_mixins/resource_size_mixin.py +81 -0
  62. naeural_client/logging/logger_mixins/timers_mixin.py +501 -0
  63. naeural_client/logging/logger_mixins/upload_mixin.py +260 -0
  64. naeural_client/logging/logger_mixins/utils_mixin.py +675 -0
  65. naeural_client/logging/small_logger.py +93 -0
  66. naeural_client/logging/tzlocal/__init__.py +20 -0
  67. naeural_client/logging/tzlocal/unix.py +231 -0
  68. naeural_client/logging/tzlocal/utils.py +113 -0
  69. naeural_client/logging/tzlocal/win32.py +151 -0
  70. naeural_client/logging/tzlocal/windows_tz.py +718 -0
  71. naeural_client/plugins_manager_mixin.py +273 -0
  72. naeural_client/utils/__init__.py +2 -0
  73. naeural_client/utils/comm_utils.py +44 -0
  74. naeural_client/utils/dotenv.py +75 -0
  75. naeural_client-2.0.0.dist-info/METADATA +365 -0
  76. naeural_client-2.0.0.dist-info/RECORD +78 -0
  77. naeural_client-2.0.0.dist-info/WHEEL +4 -0
  78. 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,2 @@
1
+ from .comm_utils import resolve_domain_or_ip
2
+ from .dotenv import load_dotenv
@@ -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