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,294 @@
1
+ import ast
2
+
3
+
4
+ class CheckerConstants:
5
+ """
6
+ ASTChecker constants.
7
+ """
8
+ type_key = 'type'
9
+ error_key = 'error'
10
+ var = 'var'
11
+ attr = 'attribute'
12
+
13
+
14
+ class ASTChecker(ast.NodeVisitor):
15
+ """
16
+ An Abstract Syntax Tree based checker for custom code.
17
+ """
18
+
19
+ def __init__(self, unallowed_dict: dict, safe_imports: list):
20
+ """
21
+ Constructor for the AST checker.
22
+
23
+ Parameters
24
+ ----------
25
+ unallowed_dict - a dictionary for unallowed identifiers.
26
+ The dictionary has the indentifier names as keys. The
27
+ Values are dictionaries with keys 'error' and 'type'.
28
+ The error is used for error printing while the type
29
+ specifies the context in which the identifier is not
30
+ allowed. Valid values for the type are 'var' and
31
+ 'attribute':
32
+ - 'var': the identifier is not allowed to be used as
33
+ a variable
34
+ - 'attribute': the identifier is not allowed to be used
35
+ as the attribute name of an object.
36
+ safe_imports: list, a list of strings containing module names
37
+ from which we can import without producing an error.
38
+
39
+ Example:
40
+ TEST_UNALLOWED_DICT = {
41
+ 'getattr': {
42
+ 'error': 'Usage of `getattr()` is not allowed in plugin code ',
43
+ 'type': 'var',
44
+ },
45
+ 'log': {
46
+ 'error': 'Logger object cannot be used directly in plugin code - please use API ',
47
+ 'type': 'attribute',
48
+ }
49
+ }
50
+ safe_imports=['fiz']
51
+ checker = ASTChecker(TEST_UNALLOWED_DICT, safe_imports)
52
+ """
53
+ self.unallowed_dict = unallowed_dict
54
+ self.errors = {}
55
+ self.safe_imports = safe_imports
56
+ if self.safe_imports is None:
57
+ self.safe_imports = []
58
+ return
59
+
60
+ def add_error(self, node, error):
61
+ """
62
+ Add an error to the set of found errors.
63
+
64
+ Parameters
65
+ ----------
66
+ node: ast.AST, the AST node for which we've found this error.
67
+
68
+ error: str, the error message to be recorded.
69
+
70
+ Returns
71
+ -------
72
+ None
73
+ """
74
+ lst = self.errors.get(error)
75
+ if lst is None:
76
+ self.errors[error] = [node.lineno]
77
+ return
78
+ self.errors[error].append(node.lineno)
79
+ return
80
+
81
+ def _is_safe_import(self, name):
82
+ """
83
+ Check if the import of the class with the name `name` is considered to be
84
+ safe and should not produce an error.
85
+
86
+ Parameters
87
+ ----------
88
+ name: str, the name of the class being imported
89
+
90
+ Returns
91
+ -------
92
+ bool - True if the import is safe, False otherwise.
93
+ """
94
+ for safe_name in self.safe_imports:
95
+ if name == safe_name:
96
+ return True
97
+ if name.startswith(safe_name + '.'):
98
+ return True
99
+ return False
100
+
101
+ def visit_Import(self, node):
102
+ for imp_alias in node.names:
103
+ if not self._is_safe_import(imp_alias.name):
104
+ self.add_error(node, f'Import forbidden for {imp_alias.name} ')
105
+
106
+ self.generic_visit(node)
107
+ return
108
+
109
+ def visit_ImportFrom(self, node):
110
+ if not self._is_safe_import(node.module):
111
+ self.add_error(node, f'Import forbidden for {node.module} ')
112
+ self.generic_visit(node)
113
+ return
114
+
115
+ def visit_Attribute(self, node):
116
+ handle = self.unallowed_dict.get(node.attr)
117
+ if handle is not None and handle[CheckerConstants.type_key] == CheckerConstants.attr:
118
+ self.add_error(node, handle[CheckerConstants.error_key])
119
+ self.generic_visit(node)
120
+ return
121
+
122
+ def visit_Name(self, node):
123
+ handle = self.unallowed_dict.get(node.id)
124
+ if handle is not None and handle[CheckerConstants.type_key] == CheckerConstants.var:
125
+ self.add_error(node, handle[CheckerConstants.error_key])
126
+ self.generic_visit(node)
127
+ return
128
+
129
+ def validate(self, code: str) -> str:
130
+ """
131
+ Runs code validation on the given code.
132
+
133
+ Parameters
134
+ ----------
135
+ code: str, the code to validate
136
+
137
+ Returns
138
+ -------
139
+ dict - a dictionary with the error strings as the keys and a list
140
+ of lines numbers where these occured as the values.
141
+ """
142
+ try:
143
+ tree = ast.parse(code, type_comments=True)
144
+ self.visit(tree)
145
+ return self.errors
146
+ except Exception as e:
147
+ return {
148
+ f"Unable to parse code {e}": [0]
149
+ }
150
+
151
+
152
+ if __name__ == '__main__':
153
+ TEST_UNALLOWED_DICT = {
154
+ 'globals': {
155
+ 'error': 'Global vars access is not allowed in plugin code ',
156
+ 'type': 'var',
157
+ },
158
+
159
+ 'locals': {
160
+ 'error': 'Local vars dict access is not allowed in plugin code ',
161
+ 'type': 'var',
162
+ },
163
+
164
+ 'memoryview': {
165
+ 'error': 'Pointer handling is unsafe in plugin code ',
166
+ 'type': 'var',
167
+ },
168
+
169
+ 'log': {
170
+ 'error': 'Logger object cannot be used directly in plugin code - please use API ',
171
+ 'type': 'attribute',
172
+ },
173
+
174
+ 'vars': {
175
+ 'error': 'Usage of `vars(obj)` is not allowed in plugin code ',
176
+ 'type': 'var',
177
+ },
178
+
179
+ 'dir': {
180
+ 'error': 'Usage of `dir(obj)` is not allowed in plugin code ',
181
+ 'type': 'var',
182
+ },
183
+
184
+ 'global_shmem': {
185
+ 'error': 'Usage of `global_shmem` is not allowed in plugin code ',
186
+ 'type': 'attribute',
187
+ },
188
+
189
+ 'plugins_shmem': {
190
+ 'error': 'Usage of `plugins_shmem` is not allowed in plugin code ',
191
+ 'type': 'attribute',
192
+ },
193
+
194
+ 'config_data': {
195
+ 'error': 'Usage of `config_data` is not allowed in plugin code ',
196
+ 'type': 'attribute',
197
+ },
198
+
199
+ '_default_config': {
200
+ 'error': 'Usage of `_default_config` is not allowed in plugin code ',
201
+ 'type': 'attribute',
202
+ },
203
+
204
+ '_upstream_config': {
205
+ 'error': 'Usage of `_upstream_config` is not allowed in plugin code ',
206
+ 'type': 'attribute',
207
+ },
208
+
209
+ 'exec': {
210
+ 'error': 'Usage of `exec()` is not allowed in plugin code ',
211
+ 'type': 'var',
212
+ },
213
+
214
+ 'eval': {
215
+ 'error': 'Usage of `eval()` is not allowed in plugin code ',
216
+ 'type': 'var',
217
+ },
218
+
219
+ 'getattr': {
220
+ 'error': 'Usage of `getattr()` is not allowed in plugin code ',
221
+ 'type': 'var',
222
+ },
223
+ }
224
+ safe_imports = ['fiz', 'biz']
225
+ checker = ASTChecker(TEST_UNALLOWED_DICT, safe_imports)
226
+
227
+ code = """import ast
228
+ import foo as bar
229
+ from foo import bar as baz
230
+
231
+ import biz
232
+
233
+ print(eval('some_string'))
234
+
235
+ foo=eval
236
+ foo('another_string')
237
+
238
+ self.log.P()
239
+ __builtins__.eval()
240
+
241
+ def bar():
242
+ global x
243
+ x = x + 42
244
+ return
245
+
246
+ """
247
+ print(checker.validate(code))
248
+
249
+ # Make sure we print sane errors when parsing fails.
250
+ code = "x &&&& y"
251
+ checker = ASTChecker(TEST_UNALLOWED_DICT, safe_imports)
252
+ print(checker.validate(code))
253
+
254
+ # Make sure we print sane errors when parsing fails.
255
+ code = """
256
+ a = x + y
257
+ """
258
+ checker = ASTChecker(TEST_UNALLOWED_DICT, safe_imports)
259
+ print(checker.validate(code))
260
+
261
+ # Even though the following import starts with 'fiz' which is safe we should
262
+ # fail it.
263
+ code = """
264
+ import fizzy
265
+ """
266
+ checker = ASTChecker(TEST_UNALLOWED_DICT, safe_imports)
267
+ print(checker.validate(code))
268
+
269
+ # Case when an user defines a method
270
+ code = """
271
+ def foo():
272
+ import fizzy
273
+ return
274
+ """
275
+ checker = ASTChecker(TEST_UNALLOWED_DICT, safe_imports)
276
+ print(checker.validate(code))
277
+
278
+ # Case when the user defines a method with an empty line in body
279
+ code = """
280
+
281
+ return
282
+ """
283
+ checker = ASTChecker(TEST_UNALLOWED_DICT, safe_imports)
284
+ print(checker.validate(code))
285
+
286
+ # Case when the user defines a method with variables not defined
287
+ code = """
288
+ def foo():
289
+
290
+ x = a + 1
291
+ return
292
+ """
293
+ checker = ASTChecker(TEST_UNALLOWED_DICT, safe_imports)
294
+ print(checker.validate(code))
@@ -0,0 +1,2 @@
1
+ from .amqp_wrapper import AMQPWrapper
2
+ from .mqtt_wrapper import MQTTWrapper
@@ -0,0 +1,338 @@
1
+ # PIKA
2
+
3
+ import uuid
4
+ from time import sleep
5
+
6
+ import pika
7
+
8
+
9
+ from ..const import COLORS, COMMS, BASE_CT, PAYLOAD_CT
10
+
11
+
12
+ class AMQPWrapper(object):
13
+ def __init__(
14
+ self,
15
+ log,
16
+ config,
17
+ recv_buff=None,
18
+ send_channel_name=None,
19
+ recv_channel_name=None,
20
+ comm_type=None,
21
+ verbosity=1,
22
+ **kwargs
23
+ ):
24
+ self._config = config
25
+ self._recv_buff = recv_buff
26
+ self._send_to = None
27
+ self._comm_type = comm_type
28
+ self.__verbosity = verbosity
29
+ self.send_channel_name = send_channel_name
30
+ self.recv_channel_name = recv_channel_name
31
+ self._disconnected_log = []
32
+
33
+ if self.recv_channel_name is not None:
34
+ assert self._recv_buff is not None
35
+
36
+ self._recv_objects = {'queue': None, 'exchange': None}
37
+ self._send_objects = {'queue': None, 'exchange': None}
38
+
39
+ self._connection = None
40
+ self._channel = None
41
+
42
+ super(AMQPWrapper, self).__init__(log=log, **kwargs)
43
+ return
44
+
45
+ def P(self, s, color=None, verbosity=1, **kwargs):
46
+ if verbosity > self.__verbosity:
47
+ return
48
+ if color is None or (isinstance(color, str) and color[0] not in ['e', 'r']):
49
+ color = COLORS.COMM
50
+ super().P(s, prefix=False, color=color, **kwargs)
51
+ return
52
+
53
+ @property
54
+ def send_channel_name(self):
55
+ return self._send_channel_name
56
+
57
+ @property
58
+ def recv_channel_name(self):
59
+ return self._recv_channel_name
60
+
61
+ @send_channel_name.setter
62
+ def send_channel_name(self, x):
63
+ if isinstance(x, tuple):
64
+ self._send_channel_name, self._send_to = x
65
+ else:
66
+ self._send_channel_name = x
67
+ return
68
+
69
+ @recv_channel_name.setter
70
+ def recv_channel_name(self, x):
71
+ self._recv_channel_name = x
72
+ return
73
+
74
+ @property
75
+ def cfg_broker(self):
76
+ return self._config[COMMS.BROKER]
77
+
78
+ @property
79
+ def cfg_user(self):
80
+ return self._config[COMMS.USER]
81
+
82
+ @property
83
+ def cfg_pass(self):
84
+ return self._config[COMMS.PASS]
85
+
86
+ @property
87
+ def cfg_vhost(self):
88
+ return self._config[COMMS.VHOST]
89
+
90
+ @property
91
+ def cfg_port(self):
92
+ return self._config[COMMS.PORT]
93
+
94
+ @property
95
+ def cfg_routing_key(self):
96
+ return self._config.get(COMMS.ROUTING_KEY, "")
97
+
98
+ @property
99
+ def cfg_node_id(self):
100
+ return self._config.get(COMMS.EE_ID, self._config.get(COMMS.SB_ID, None))
101
+
102
+ @property
103
+ def send_channel_def(self):
104
+ if self.send_channel_name is None:
105
+ return
106
+
107
+ cfg = self._config[self.send_channel_name].copy()
108
+ queue = cfg.get(COMMS.QUEUE, cfg[COMMS.EXCHANGE])
109
+ if self._send_to is not None and "{}" in queue:
110
+ queue = queue.format(self._send_to)
111
+
112
+ assert "{}" not in queue
113
+
114
+ cfg[COMMS.QUEUE] = queue
115
+ return cfg
116
+
117
+ @property
118
+ def recv_channel_def(self):
119
+ if self.recv_channel_name is None:
120
+ return
121
+
122
+ cfg = self._config[self.recv_channel_name].copy()
123
+ queue = cfg.get(COMMS.QUEUE, cfg[COMMS.EXCHANGE])
124
+ cfg[COMMS.QUEUE] = queue
125
+ _queue_device_specific = cfg.pop(COMMS.QUEUE_DEVICE_SPECIFIC, True)
126
+ if _queue_device_specific:
127
+ cfg[COMMS.QUEUE] += '/{}'.format(self.cfg_node_id)
128
+ cfg[COMMS.QUEUE] += '/{}'.format(str(uuid.uuid4())[:8])
129
+ return cfg
130
+
131
+ @property
132
+ def connection(self):
133
+ return self._connection
134
+
135
+ @property
136
+ def channel(self):
137
+ return self._channel
138
+
139
+ @property
140
+ def recv_queue(self):
141
+ return self._recv_objects['queue']
142
+
143
+ @property
144
+ def recv_exchange(self):
145
+ return self._recv_objects['exchange']
146
+
147
+ @property
148
+ def send_queue(self):
149
+ return self._send_objects['queue']
150
+
151
+ @property
152
+ def send_exchange(self):
153
+ return self._send_objects['exchange']
154
+
155
+ def server_connect(self, max_retries=5):
156
+ url = 'amqp://{}:{}@{}:{}/{}'.format(self.cfg_user, self.cfg_pass, self.cfg_broker, self.cfg_port, self.cfg_vhost)
157
+
158
+ nr_retry = 1
159
+ has_connection = False
160
+ exception = None
161
+
162
+ while nr_retry <= max_retries:
163
+ try:
164
+ self._connection = pika.BlockingConnection(parameters=pika.URLParameters(url))
165
+ sleep(1)
166
+ self._channel = self._connection.channel()
167
+ has_connection = True
168
+ except Exception as e:
169
+ exception = e
170
+ # end try-except
171
+
172
+ if has_connection:
173
+ break
174
+
175
+ nr_retry += 1
176
+ # endwhile
177
+
178
+ if has_connection:
179
+ msg = 'AMQP (Pika) SERVER conn ok: {}{}'.format(self.cfg_broker, self.cfg_port)
180
+ msg_type = PAYLOAD_CT.STATUS_TYPE.STATUS_NORMAL
181
+ else:
182
+ msg = 'AMQP (Pika) SERVER connection could not be initialized after {} retries (reason:{})'.format(
183
+ max_retries, exception
184
+ )
185
+ msg_type = PAYLOAD_CT.STATUS_TYPE.STATUS_EXCEPTION
186
+ # endif
187
+
188
+ dct_ret = {
189
+ 'has_connection': has_connection,
190
+ 'msg': msg,
191
+ 'msg_type': msg_type
192
+ }
193
+
194
+ return dct_ret
195
+
196
+ def establish_one_way_connection(self, channel_name, max_retries=5):
197
+ cfg = None
198
+ if channel_name.lower() == 'send':
199
+ cfg = self.send_channel_def
200
+ elif channel_name.lower() == 'recv':
201
+ cfg = self.recv_channel_def
202
+ # endif
203
+
204
+ if cfg is None:
205
+ return
206
+
207
+ exchange = cfg[COMMS.EXCHANGE]
208
+ queue = cfg[COMMS.QUEUE]
209
+ exchange_type = cfg.get(COMMS.EXCHANGE_TYPE, 'fanout')
210
+ queue_durable = cfg.get(COMMS.QUEUE_DURABLE, True)
211
+ queue_exclusive = cfg.get(COMMS.QUEUE_EXCLUSIVE, False)
212
+
213
+ nr_retry = 1
214
+ has_connection = False
215
+ exception = None
216
+
217
+ while nr_retry <= max_retries:
218
+ try:
219
+ self._channel.exchange_declare(
220
+ exchange=exchange,
221
+ exchange_type=exchange_type
222
+ )
223
+ self._channel.queue_declare(
224
+ queue=queue,
225
+ durable=queue_durable,
226
+ exclusive=queue_exclusive
227
+ )
228
+ self._channel.queue_bind(
229
+ queue=queue,
230
+ exchange=exchange,
231
+ routing_key=self.cfg_routing_key
232
+ )
233
+
234
+ has_connection = True
235
+ except Exception as e:
236
+ exception = e
237
+ # end try-except
238
+
239
+ if has_connection:
240
+ break
241
+
242
+ sleep(1)
243
+ nr_retry += 1
244
+ # endwhile
245
+
246
+ if has_connection:
247
+ msg = "AMQP (Pika) '{}' connection successfully established on exchange '{}', queue '{}'".format(
248
+ channel_name.lower(), exchange, queue,
249
+ )
250
+ msg_type = PAYLOAD_CT.STATUS_TYPE.STATUS_NORMAL
251
+ else:
252
+ msg = "AMQP (Pika) '{}' connection on exchange '{}', queue '{}' could not be initialized after {} retries (reason:{})".format(
253
+ channel_name.lower(), exchange, queue, max_retries, exception
254
+ )
255
+ msg_type = PAYLOAD_CT.STATUS_TYPE.STATUS_EXCEPTION
256
+ # endif
257
+
258
+ dct_objects = {'queue': queue, 'exchange': exchange}
259
+ if channel_name.lower() == 'send':
260
+ self._send_objects = dct_objects
261
+ elif channel_name.lower() == 'recv':
262
+ self._recv_objects = dct_objects
263
+ # endif
264
+
265
+ dct_ret = {
266
+ 'has_connection': has_connection,
267
+ 'msg': msg,
268
+ 'msg_type': msg_type
269
+ }
270
+
271
+ return dct_ret
272
+
273
+ def receive(self):
274
+ method_frame, header_frame, body = self._channel.basic_get(queue=self.recv_queue)
275
+ if method_frame:
276
+ msg = body.decode('utf-8')
277
+ self._channel.basic_ack(method_frame.delivery_tag)
278
+ self._recv_buff.append(msg)
279
+ # endif
280
+ return
281
+
282
+ def send(self, message):
283
+ properties = pika.BasicProperties(content_type='application/json')
284
+ self._channel.basic_publish(
285
+ exchange=self.send_exchange,
286
+ routing_key=self.cfg_routing_key,
287
+ body=message,
288
+ properties=properties
289
+ )
290
+
291
+ ####
292
+ self.D("Sent message '{}'".format(message))
293
+ ####
294
+
295
+ return
296
+
297
+ def release(self):
298
+ msgs = []
299
+
300
+ if self.recv_queue is not None:
301
+ try:
302
+ self._channel.queue_unbind(
303
+ queue=self.recv_queue,
304
+ exchange=self.recv_exchange,
305
+ routing_key=self.cfg_routing_key,
306
+ )
307
+
308
+ self._channel.queue_delete(queue=self.recv_queue)
309
+ msgs.append("AMQP (Pika) deleted queue '{}'".format(self.recv_queue))
310
+ except Exception as e:
311
+ msgs.append("AMQP (Pika) exception when deleting queue '{}'".format(self.recv_queue))
312
+ # end try-except
313
+ # endif
314
+
315
+ try:
316
+ self._channel.cancel()
317
+ self._channel.close()
318
+ del self._channel
319
+ self._channel = None
320
+ msgs.append('AMQP (Pika) closed channel')
321
+ except Exception as e:
322
+ msgs.append('AMQP (Pika) exception when closing channel: `{}`'.format(str(e)))
323
+ # end try-except
324
+
325
+ try:
326
+ self._connection.close()
327
+ del self._connection
328
+ self._connection = None
329
+ msgs.append('AMQP (Pika) disconnected')
330
+ except Exception as e:
331
+ msgs.append('AMQP (Pika) exception when disconnecting: `{}`'.format(str(e)))
332
+ # end try-except
333
+
334
+ dct_ret = {
335
+ 'msgs': msgs
336
+ }
337
+
338
+ return dct_ret