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,616 @@
|
|
1
|
+
from ..const import PAYLOAD_DATA
|
2
|
+
from .transaction import Transaction
|
3
|
+
from .responses import PipelineOKResponse, PluginConfigOKResponse, PluginInstanceCommandOKResponse
|
4
|
+
from time import time, sleep
|
5
|
+
|
6
|
+
|
7
|
+
class Instance():
|
8
|
+
"""
|
9
|
+
The Instance class is a wrapper around a plugin instance. It provides a simple API for sending commands to the instance and updating its configuration.
|
10
|
+
"""
|
11
|
+
|
12
|
+
def __init__(self, log, pipeline, instance_id, signature, on_data=None, on_notification=None, config={}, is_attached=False, **kwargs):
|
13
|
+
"""
|
14
|
+
Create a new instance of the plugin.
|
15
|
+
|
16
|
+
Parameters
|
17
|
+
----------
|
18
|
+
log : Logger
|
19
|
+
The logger object
|
20
|
+
pipeline : Pipeline
|
21
|
+
The pipeline that the instance is part of
|
22
|
+
instance_id : str
|
23
|
+
The unique identifier of the instance
|
24
|
+
signature : str
|
25
|
+
The name of the plugin signature
|
26
|
+
on_data : Callable[[Pipeline, str, str, dict], None], optional
|
27
|
+
Callback that handles messages received from this instance. As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
28
|
+
Defaults to None.
|
29
|
+
on_notification : Callable[[Pipeline, dict], None], optional
|
30
|
+
Callback that handles notifications received from this instance. As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
31
|
+
Defaults to None.
|
32
|
+
config : dict, optional
|
33
|
+
Parameters used to customize the functionality. One can change the AI engine used for object detection,
|
34
|
+
or finetune alerter parameters to better fit a camera located in a low light environment.
|
35
|
+
Defaults to {}
|
36
|
+
"""
|
37
|
+
self.log = log
|
38
|
+
self.pipeline = pipeline
|
39
|
+
self.instance_id = instance_id
|
40
|
+
self.signature = signature.upper()
|
41
|
+
self.config = {}
|
42
|
+
|
43
|
+
if is_attached:
|
44
|
+
assert len(kwargs) == 0, "When attaching an instance, no additional parameters are allowed"
|
45
|
+
self.config = config
|
46
|
+
self.proposed_config = None
|
47
|
+
else:
|
48
|
+
self.proposed_config = {**config, **kwargs}
|
49
|
+
self.proposed_config = {k.upper(): v for k, v in self.proposed_config.items()}
|
50
|
+
self.__staged_config = None
|
51
|
+
self.__was_last_operation_successful = None
|
52
|
+
|
53
|
+
self.on_data_callbacks = []
|
54
|
+
self.temporary_on_data_callbacks = {}
|
55
|
+
self.on_notification_callbacks = []
|
56
|
+
self.temporary_on_notification_callbacks = {}
|
57
|
+
|
58
|
+
if on_data:
|
59
|
+
self.on_data_callbacks.append(on_data)
|
60
|
+
if on_notification:
|
61
|
+
self.on_notification_callbacks.append(on_notification)
|
62
|
+
|
63
|
+
return
|
64
|
+
|
65
|
+
# Message handling
|
66
|
+
if True:
|
67
|
+
def _on_data(self, pipeline, data):
|
68
|
+
"""
|
69
|
+
Handle the data received from the instance.
|
70
|
+
|
71
|
+
Parameters
|
72
|
+
----------
|
73
|
+
pipeline : Pipeline
|
74
|
+
The pipeline that the instance is part of
|
75
|
+
data : dict | Payload
|
76
|
+
The data received from the instance
|
77
|
+
"""
|
78
|
+
for callback in self.on_data_callbacks:
|
79
|
+
callback(pipeline, data)
|
80
|
+
for callback in self.temporary_on_data_callbacks.values():
|
81
|
+
callback(pipeline, data)
|
82
|
+
return
|
83
|
+
|
84
|
+
def _on_notification(self, pipeline, data):
|
85
|
+
"""
|
86
|
+
Handle the notification received from the instance.
|
87
|
+
|
88
|
+
Parameters
|
89
|
+
----------
|
90
|
+
pipeline : Pipeline
|
91
|
+
The pipeline that the instance is part of
|
92
|
+
data : dict | Payload
|
93
|
+
The notification received from the instance
|
94
|
+
"""
|
95
|
+
for callback in self.on_notification_callbacks:
|
96
|
+
callback(pipeline, data)
|
97
|
+
for callback in self.temporary_on_notification_callbacks.values():
|
98
|
+
callback(pipeline, data)
|
99
|
+
return
|
100
|
+
|
101
|
+
def _add_on_data_callback(self, callback):
|
102
|
+
"""
|
103
|
+
Add a new callback to the list of callbacks that handle the data received from the instance.
|
104
|
+
|
105
|
+
Parameters
|
106
|
+
----------
|
107
|
+
callback : Callable[[Pipeline, dict], None]
|
108
|
+
The callback to add
|
109
|
+
"""
|
110
|
+
self.on_data_callbacks.append(callback)
|
111
|
+
return
|
112
|
+
|
113
|
+
def _add_temporary_on_data_callback(self, attachment, callback):
|
114
|
+
"""
|
115
|
+
Add a new temporary callback to the list of callbacks that handle the data received from the instance.
|
116
|
+
|
117
|
+
Parameters
|
118
|
+
----------
|
119
|
+
attachment : object
|
120
|
+
The attachment id of the callback
|
121
|
+
callback : Callable[[Pipeline, dict], None]
|
122
|
+
The callback to add
|
123
|
+
"""
|
124
|
+
# TODO: this can fail (very small chance, but still)
|
125
|
+
# FIXME: make add / delete happen after callbacks
|
126
|
+
self.temporary_on_data_callbacks[attachment] = callback
|
127
|
+
return
|
128
|
+
|
129
|
+
def _remove_temporary_on_data_callback(self, attachment):
|
130
|
+
"""
|
131
|
+
Remove a temporary callback from the list of callbacks that handle the data received from the instance.
|
132
|
+
|
133
|
+
Parameters
|
134
|
+
----------
|
135
|
+
attachment : object
|
136
|
+
The attachment id of the callback
|
137
|
+
"""
|
138
|
+
if attachment in self.temporary_on_data_callbacks:
|
139
|
+
del self.temporary_on_data_callbacks[attachment]
|
140
|
+
return
|
141
|
+
|
142
|
+
def _reset_on_data_callback(self):
|
143
|
+
"""
|
144
|
+
Reset the list of callbacks that handle the data received from the instance.
|
145
|
+
"""
|
146
|
+
self.on_data_callbacks = []
|
147
|
+
return
|
148
|
+
|
149
|
+
def _add_on_notification_callback(self, callback):
|
150
|
+
"""
|
151
|
+
Add a new callback to the list of callbacks that handle the notifications received from the instance.
|
152
|
+
|
153
|
+
Parameters
|
154
|
+
----------
|
155
|
+
callback : Callable[[Pipeline, dict], None]
|
156
|
+
The callback to add
|
157
|
+
"""
|
158
|
+
self.on_notification_callbacks.append(callback)
|
159
|
+
return
|
160
|
+
|
161
|
+
def _add_temporary_on_notification_callback(self, attachment, callback):
|
162
|
+
"""
|
163
|
+
Add a new temporary callback to the list of callbacks that handle the notifications received from the instance.
|
164
|
+
|
165
|
+
Parameters
|
166
|
+
----------
|
167
|
+
attachment : object
|
168
|
+
The attachment id of the callback
|
169
|
+
callback : Callable[[Pipeline, dict], None]
|
170
|
+
The callback to add
|
171
|
+
"""
|
172
|
+
self.temporary_on_notification_callbacks[attachment] = callback
|
173
|
+
return
|
174
|
+
|
175
|
+
def _remove_temporary_on_notification_callback(self, attachment):
|
176
|
+
"""
|
177
|
+
Remove a temporary callback from the list of callbacks that handle the notifications received from the instance.
|
178
|
+
|
179
|
+
Parameters
|
180
|
+
----------
|
181
|
+
attachment : object
|
182
|
+
The attachment id of the callback
|
183
|
+
"""
|
184
|
+
if attachment in self.temporary_on_notification_callbacks:
|
185
|
+
del self.temporary_on_notification_callbacks[attachment]
|
186
|
+
return
|
187
|
+
|
188
|
+
def _reset_on_notification_callback(self):
|
189
|
+
"""
|
190
|
+
Reset the list of callbacks that handle the notifications received from the instance.
|
191
|
+
"""
|
192
|
+
self.on_notification_callbacks = []
|
193
|
+
return
|
194
|
+
|
195
|
+
# Utils
|
196
|
+
if True:
|
197
|
+
def __repr__(self) -> str:
|
198
|
+
node_addr = self.pipeline.node_addr
|
199
|
+
pipeline_name = self.pipeline.name
|
200
|
+
signature = self.signature
|
201
|
+
instance_id = self.instance_id
|
202
|
+
|
203
|
+
return f"<Instance: {node_addr}/{pipeline_name}/{signature}/{instance_id}>"
|
204
|
+
|
205
|
+
def _is_tainted(self):
|
206
|
+
"""
|
207
|
+
Check if the instance has a proposed configuration.
|
208
|
+
|
209
|
+
Returns
|
210
|
+
-------
|
211
|
+
bool
|
212
|
+
True if the instance has a proposed configuration, False otherwise
|
213
|
+
"""
|
214
|
+
return self.proposed_config is not None
|
215
|
+
|
216
|
+
def _get_config_dictionary(self):
|
217
|
+
"""
|
218
|
+
Get the configuration of the instance as a dictionary.
|
219
|
+
|
220
|
+
Returns
|
221
|
+
-------
|
222
|
+
dict
|
223
|
+
The configuration of the instance as a dictionary
|
224
|
+
"""
|
225
|
+
config_dict = {
|
226
|
+
'INSTANCE_ID': self.instance_id,
|
227
|
+
**self.config
|
228
|
+
}
|
229
|
+
|
230
|
+
return config_dict
|
231
|
+
|
232
|
+
def _get_proposed_config_dictionary(self, full=False):
|
233
|
+
"""
|
234
|
+
Get the proposed configuration of the instance as a dictionary.
|
235
|
+
|
236
|
+
Returns
|
237
|
+
-------
|
238
|
+
dict
|
239
|
+
The proposed configuration of the instance as a dictionary
|
240
|
+
"""
|
241
|
+
if self.proposed_config is None:
|
242
|
+
return self._get_config_dictionary()
|
243
|
+
|
244
|
+
proposed_config_dict = {
|
245
|
+
'INSTANCE_ID': self.instance_id,
|
246
|
+
**({} if not full else self.config),
|
247
|
+
**self.proposed_config
|
248
|
+
}
|
249
|
+
|
250
|
+
return proposed_config_dict
|
251
|
+
|
252
|
+
def _apply_staged_config(self, verbose=False):
|
253
|
+
"""
|
254
|
+
Apply the staged configuration to the instance.
|
255
|
+
"""
|
256
|
+
if self.__staged_config is None:
|
257
|
+
return
|
258
|
+
|
259
|
+
if verbose:
|
260
|
+
self.P(f'Applying staged configuration to instance <{self.instance_id}>', color="g")
|
261
|
+
self.__was_last_operation_successful = True
|
262
|
+
|
263
|
+
self.config = {**self.config, **self.__staged_config}
|
264
|
+
self.__staged_config = None
|
265
|
+
return
|
266
|
+
|
267
|
+
def _discard_staged_config(self, fail_reason: str):
|
268
|
+
"""
|
269
|
+
Discard the staged configuration for the instance.
|
270
|
+
"""
|
271
|
+
self.P(f'Discarding staged configuration for instance <{self.instance_id}>. Reason: {fail_reason}', color="r")
|
272
|
+
self.__was_last_operation_successful = False
|
273
|
+
|
274
|
+
self.__staged_config = None
|
275
|
+
return
|
276
|
+
|
277
|
+
def _stage_proposed_config(self):
|
278
|
+
"""
|
279
|
+
Stage the proposed configuration for the instance.
|
280
|
+
"""
|
281
|
+
if self.proposed_config is None:
|
282
|
+
return
|
283
|
+
|
284
|
+
if self.__staged_config is not None:
|
285
|
+
raise ValueError("Instance configuration has already been staged, waiting for confirmation from Execution Engine")
|
286
|
+
|
287
|
+
# self.__staged_config is None
|
288
|
+
self.__staged_config = self.proposed_config
|
289
|
+
self.proposed_config = None
|
290
|
+
|
291
|
+
self.__was_last_operation_successful = None
|
292
|
+
return
|
293
|
+
|
294
|
+
def _handle_instance_command_success(self):
|
295
|
+
"""
|
296
|
+
Handle the success of the instance command.
|
297
|
+
"""
|
298
|
+
self.P(f'Instance command successful for instance <{self.instance_id}>', color="g")
|
299
|
+
self.__was_last_operation_successful = True
|
300
|
+
return
|
301
|
+
|
302
|
+
def _handle_instance_command_failure(self, fail_reason: str):
|
303
|
+
"""
|
304
|
+
Handle the failure of the instance command.
|
305
|
+
"""
|
306
|
+
self.P(f'Instance command failed for instance <{self.instance_id}>. Reason: {fail_reason}', color="r")
|
307
|
+
self.__was_last_operation_successful = False
|
308
|
+
return
|
309
|
+
|
310
|
+
def __register_transaction_for_instance_command(self, session_id: str = None, timeout: float = 0) -> list[Transaction]:
|
311
|
+
"""
|
312
|
+
Register a new transaction for the instance command.
|
313
|
+
This method is called before sending an instance command to the Naeural edge node.
|
314
|
+
|
315
|
+
Parameters
|
316
|
+
----------
|
317
|
+
session_id : str, optional
|
318
|
+
The session ID of the transaction, by default None
|
319
|
+
timeout : float, optional
|
320
|
+
The timeout for the transaction, by default 0
|
321
|
+
|
322
|
+
Returns
|
323
|
+
-------
|
324
|
+
list[Transaction]
|
325
|
+
The list of transactions generated
|
326
|
+
"""
|
327
|
+
required_responses = [
|
328
|
+
PipelineOKResponse(self.pipeline.node_id, self.pipeline.name),
|
329
|
+
PluginInstanceCommandOKResponse(self.pipeline.node_id, self.pipeline.name, self.signature, self.instance_id),
|
330
|
+
# PluginConfigOKResponse(self.pipeline.node_id, self.pipeline.name, self.signature, self.instance_id),
|
331
|
+
]
|
332
|
+
|
333
|
+
transactions = [self.pipeline.session._register_transaction(
|
334
|
+
session_id=session_id,
|
335
|
+
lst_required_responses=required_responses,
|
336
|
+
timeout=timeout,
|
337
|
+
on_success_callback=self._handle_instance_command_success,
|
338
|
+
on_failure_callback=self._handle_instance_command_failure,
|
339
|
+
)]
|
340
|
+
|
341
|
+
return transactions
|
342
|
+
|
343
|
+
def _get_instance_update_required_responses(self):
|
344
|
+
"""
|
345
|
+
Get the responses required to update the instance.
|
346
|
+
|
347
|
+
Returns
|
348
|
+
-------
|
349
|
+
list[str]
|
350
|
+
The list of responses required to update the instance
|
351
|
+
"""
|
352
|
+
responses = [
|
353
|
+
PipelineOKResponse(self.pipeline.node_id, self.pipeline.name),
|
354
|
+
PluginConfigOKResponse(self.pipeline.node_id, self.pipeline.name, self.signature, self.instance_id),
|
355
|
+
]
|
356
|
+
|
357
|
+
return responses
|
358
|
+
|
359
|
+
def _get_instance_remove_required_responses(self):
|
360
|
+
"""
|
361
|
+
Get the responses required to delete the instance.
|
362
|
+
|
363
|
+
Returns
|
364
|
+
-------
|
365
|
+
list[str]
|
366
|
+
The list of responses required to delete the instance
|
367
|
+
"""
|
368
|
+
responses = [
|
369
|
+
PipelineOKResponse(self.pipeline.node_id, self.pipeline.name),
|
370
|
+
]
|
371
|
+
|
372
|
+
return responses
|
373
|
+
|
374
|
+
# API
|
375
|
+
if True:
|
376
|
+
@property
|
377
|
+
def was_last_operation_successful(self) -> bool:
|
378
|
+
"""
|
379
|
+
Get the status of the last operation.
|
380
|
+
|
381
|
+
Example:
|
382
|
+
--------
|
383
|
+
```python
|
384
|
+
instance.send_instance_command("RESTART", wait_confirmation=False)
|
385
|
+
|
386
|
+
if instance.was_last_operation_successful is not None:
|
387
|
+
if instance.was_last_operation_successful:
|
388
|
+
print("Last operation was successful")
|
389
|
+
else:
|
390
|
+
print("Last operation failed")
|
391
|
+
else:
|
392
|
+
print("No ACK received yet for this operation")
|
393
|
+
```
|
394
|
+
|
395
|
+
Returns
|
396
|
+
-------
|
397
|
+
bool, None
|
398
|
+
True if the last operation was successful, False if it failed, None if the ACK has not been received yet
|
399
|
+
"""
|
400
|
+
return self.__was_last_operation_successful
|
401
|
+
|
402
|
+
def _sync_configuration_with_remote(self, config):
|
403
|
+
self.config = {**self.config, **config}
|
404
|
+
return
|
405
|
+
|
406
|
+
def update_instance_config(self, config={}, **kwargs):
|
407
|
+
"""
|
408
|
+
Update the configuration of the instance.
|
409
|
+
The new configuration is merged with the existing one.
|
410
|
+
Parameters can be passed as a dictionary in `config` or as `kwargs`.
|
411
|
+
|
412
|
+
Parameters
|
413
|
+
----------
|
414
|
+
config : dict, optional
|
415
|
+
The new configuration of the instance, by default {}
|
416
|
+
|
417
|
+
Returns
|
418
|
+
-------
|
419
|
+
dict | None
|
420
|
+
The new configuration of the instance as a dictionary if `send_command` is False, otherwise None
|
421
|
+
"""
|
422
|
+
|
423
|
+
if self.__staged_config is not None:
|
424
|
+
raise ValueError("Instance configuration has already been staged, waiting for confirmation from Execution Engine")
|
425
|
+
|
426
|
+
if self.proposed_config is None:
|
427
|
+
self.proposed_config = {}
|
428
|
+
|
429
|
+
self.proposed_config = {**self.proposed_config, **config, **{k.upper(): v for k, v in kwargs.items()}}
|
430
|
+
|
431
|
+
for k, v in self.config.items():
|
432
|
+
if k in self.proposed_config:
|
433
|
+
if self.proposed_config[k] == v:
|
434
|
+
del self.proposed_config[k]
|
435
|
+
|
436
|
+
if len(self.proposed_config) == 0:
|
437
|
+
self.proposed_config = None
|
438
|
+
|
439
|
+
return
|
440
|
+
|
441
|
+
def send_instance_command(self, command, payload=None, command_params=None, wait_confirmation=True, session_id=None, timeout=10):
|
442
|
+
"""
|
443
|
+
Send a command to the instance.
|
444
|
+
This command can block until the command is confirmed by the Naeural edge node.
|
445
|
+
|
446
|
+
Example:
|
447
|
+
--------
|
448
|
+
```python
|
449
|
+
instance.send_instance_command('START', wait_confirmation=True)
|
450
|
+
|
451
|
+
transactions_p1 = instance1.send_instance_command('START', wait_confirmation=False)
|
452
|
+
transactions_p2 = instance2.send_instance_command('START', wait_confirmation=False)
|
453
|
+
# wait for both commands to be confirmed, but after both commands are sent
|
454
|
+
session.wait_for_transactions(transactions_p1 + transactions_p2)
|
455
|
+
```
|
456
|
+
Parameters
|
457
|
+
----------
|
458
|
+
command : str
|
459
|
+
The command to send
|
460
|
+
payload : dict, optional
|
461
|
+
The payload of the command, by default {}
|
462
|
+
command_params : dict, optional
|
463
|
+
The parameters of the command, by default {}
|
464
|
+
wait_confirmation : bool, optional
|
465
|
+
Whether to wait for the confirmation of the command, by default False
|
466
|
+
timeout : int, optional
|
467
|
+
The timeout for the transaction, by default 10
|
468
|
+
|
469
|
+
Returns
|
470
|
+
-------
|
471
|
+
list[Transaction] | None
|
472
|
+
The list of transactions generated, or None if `wait_confirmation` is False.
|
473
|
+
|
474
|
+
"""
|
475
|
+
self.P(f'Sending command <{command}> to instance <{self.__repr__()}>', color="b")
|
476
|
+
|
477
|
+
self.__was_last_operation_successful = None
|
478
|
+
|
479
|
+
transactions = self.__register_transaction_for_instance_command(timeout=timeout)
|
480
|
+
|
481
|
+
self.pipeline.session._send_command_instance_command(
|
482
|
+
worker=self.pipeline.node_addr,
|
483
|
+
pipeline_name=self.pipeline.name,
|
484
|
+
signature=self.signature,
|
485
|
+
instance_id=self.instance_id,
|
486
|
+
command=command,
|
487
|
+
payload=payload,
|
488
|
+
command_params=command_params,
|
489
|
+
session_id=session_id,
|
490
|
+
)
|
491
|
+
|
492
|
+
if wait_confirmation:
|
493
|
+
self.pipeline.session.wait_for_transactions(transactions)
|
494
|
+
else:
|
495
|
+
return transactions
|
496
|
+
return
|
497
|
+
|
498
|
+
def close(self):
|
499
|
+
"""
|
500
|
+
Close the instance.
|
501
|
+
"""
|
502
|
+
self.pipeline.remove_plugin_instance(self)
|
503
|
+
return
|
504
|
+
|
505
|
+
def stop(self):
|
506
|
+
"""
|
507
|
+
Close the instance. Alias for `close`.
|
508
|
+
"""
|
509
|
+
self.close()
|
510
|
+
|
511
|
+
def P(self, *args, **kwargs):
|
512
|
+
self.log.P(*args, **kwargs)
|
513
|
+
return
|
514
|
+
|
515
|
+
def D(self, *args, **kwargs):
|
516
|
+
self.log.D(*args, **kwargs)
|
517
|
+
return
|
518
|
+
|
519
|
+
def temporary_attach(self, on_data=None, on_notification=None):
|
520
|
+
"""
|
521
|
+
Attach a temporary callback to the instance.
|
522
|
+
|
523
|
+
Parameters
|
524
|
+
----------
|
525
|
+
on_data : Callable[[Pipeline, str, str, dict], None], optional
|
526
|
+
Callback that handles messages received from this instance. As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
527
|
+
Defaults to None.
|
528
|
+
on_notification : Callable[[Pipeline, dict], None], optional
|
529
|
+
Callback that handles notifications received from this instance. As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
530
|
+
Defaults to None.
|
531
|
+
|
532
|
+
Returns
|
533
|
+
-------
|
534
|
+
object
|
535
|
+
The attachment id of the callback
|
536
|
+
"""
|
537
|
+
attachment = object()
|
538
|
+
if on_data:
|
539
|
+
self._add_temporary_on_data_callback(attachment, on_data)
|
540
|
+
if on_notification:
|
541
|
+
self._add_temporary_on_notification_callback(attachment, on_notification)
|
542
|
+
|
543
|
+
return attachment
|
544
|
+
|
545
|
+
def temporary_detach(self, attachment):
|
546
|
+
"""
|
547
|
+
Detach a temporary callback from the instance.
|
548
|
+
|
549
|
+
Parameters
|
550
|
+
----------
|
551
|
+
attachment : object
|
552
|
+
The attachment id of the callback
|
553
|
+
"""
|
554
|
+
self._remove_temporary_on_data_callback(attachment)
|
555
|
+
self._remove_temporary_on_notification_callback(attachment)
|
556
|
+
return
|
557
|
+
|
558
|
+
def convert_to_specialized_class(self, specialized_class):
|
559
|
+
"""
|
560
|
+
Convert the object to a specialized class.
|
561
|
+
A specialized class is a class that inherits from the Instance class and
|
562
|
+
provides additional methods for ease of use.
|
563
|
+
"""
|
564
|
+
self.__class__ = specialized_class
|
565
|
+
return self
|
566
|
+
|
567
|
+
def send_instance_command_and_wait_for_response_payload(self, command, payload=None, command_params=None, timeout_command=10, timeout_response_payload=3, response_params_key="COMMAND_PARAMS"):
|
568
|
+
"""
|
569
|
+
Send a command to the instance and wait for the response payload.
|
570
|
+
|
571
|
+
Parameters
|
572
|
+
----------
|
573
|
+
command : str | dict
|
574
|
+
The command to send
|
575
|
+
payload : dict, optional
|
576
|
+
The payload of the command, by default {}
|
577
|
+
command_params : dict, optional
|
578
|
+
The parameters of the command, by default {}
|
579
|
+
timeout : int, optional
|
580
|
+
The timeout for the transaction, by default 10
|
581
|
+
|
582
|
+
Returns
|
583
|
+
-------
|
584
|
+
dict: dict | None
|
585
|
+
The payload received from the instance, or None if the command failed or if the payload was not received
|
586
|
+
"""
|
587
|
+
result_payload = None
|
588
|
+
uid = self.log.get_uid()
|
589
|
+
|
590
|
+
def wait_payload_on_data(pipeline, data):
|
591
|
+
nonlocal result_payload
|
592
|
+
if response_params_key in data and data[response_params_key].get("SDK_REQUEST") == uid:
|
593
|
+
result_payload = data
|
594
|
+
return
|
595
|
+
|
596
|
+
attachment = self.temporary_attach(on_data=wait_payload_on_data)
|
597
|
+
|
598
|
+
if payload is None:
|
599
|
+
payload = {}
|
600
|
+
payload["SDK_REQUEST"] = uid
|
601
|
+
|
602
|
+
self.send_instance_command(
|
603
|
+
command=command,
|
604
|
+
payload=payload,
|
605
|
+
command_params=command_params,
|
606
|
+
wait_confirmation=True,
|
607
|
+
timeout=timeout_command,
|
608
|
+
)
|
609
|
+
|
610
|
+
start_time = time()
|
611
|
+
while time() - start_time < timeout_response_payload and result_payload is None:
|
612
|
+
sleep(0.1)
|
613
|
+
|
614
|
+
self.temporary_detach(attachment)
|
615
|
+
|
616
|
+
return result_payload
|
@@ -0,0 +1 @@
|
|
1
|
+
from .payload import Payload
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import base64
|
2
|
+
import io
|
3
|
+
from collections import UserDict
|
4
|
+
|
5
|
+
import numpy as np
|
6
|
+
|
7
|
+
|
8
|
+
class Payload(UserDict):
|
9
|
+
"""
|
10
|
+
This class enriches the default python dict, providing
|
11
|
+
helpful methods to process the payloads received from Naeural edge nodes.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def get_images_as_np(self, key='IMG') -> list:
|
15
|
+
"""
|
16
|
+
Extract the image from the payload.
|
17
|
+
The image is returned as a numpy array.
|
18
|
+
|
19
|
+
Parameters
|
20
|
+
----------
|
21
|
+
key : str, optional
|
22
|
+
The key from which to extract the image, by default 'IMG'.
|
23
|
+
Can be modified if the user wants to extract an image from a different key
|
24
|
+
|
25
|
+
Returns
|
26
|
+
-------
|
27
|
+
NDArray[Any] | None
|
28
|
+
The image if it was found or None otherwise.
|
29
|
+
"""
|
30
|
+
images = self.get_images_as_PIL(key)
|
31
|
+
images = [np.array(image) if image is not None else None for image in images]
|
32
|
+
return images
|
33
|
+
|
34
|
+
def get_images_as_PIL(self, key='IMG') -> list:
|
35
|
+
"""
|
36
|
+
Extract the image from the payload.
|
37
|
+
The image is returned as a PIL image.
|
38
|
+
|
39
|
+
Parameters
|
40
|
+
----------
|
41
|
+
key : str, optional
|
42
|
+
The key from which to extract the image, by default 'IMG'.
|
43
|
+
Can be modified if the user wants to extract an image from a different key
|
44
|
+
|
45
|
+
Returns
|
46
|
+
-------
|
47
|
+
List[Image] | None
|
48
|
+
A list of images if there were any or None otherwise.
|
49
|
+
"""
|
50
|
+
base64_img = self.data.get(key, None)
|
51
|
+
if base64_img is None:
|
52
|
+
return [None]
|
53
|
+
if isinstance(base64_img, list):
|
54
|
+
return [self._image_from_b64(b64) if b64 is not None else None for b64 in base64_img]
|
55
|
+
return [self._image_from_b64(base64_img)]
|
56
|
+
|
57
|
+
def _image_from_b64(self, base64_img):
|
58
|
+
image = None
|
59
|
+
try:
|
60
|
+
from PIL import Image
|
61
|
+
|
62
|
+
base64_decoded = base64.b64decode(base64_img)
|
63
|
+
image = Image.open(io.BytesIO(base64_decoded))
|
64
|
+
except ModuleNotFoundError:
|
65
|
+
raise "This functionality requires the PIL library. To use this feature, please install it using 'pip install pillow'"
|
66
|
+
return image
|