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,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