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,1499 @@
|
|
1
|
+
# TODO: for custom plugin, do the plugin verification locally too
|
2
|
+
|
3
|
+
import os
|
4
|
+
from time import sleep, time
|
5
|
+
|
6
|
+
from ..code_cheker.base import BaseCodeChecker
|
7
|
+
from ..const import PAYLOAD_DATA
|
8
|
+
from .distributed_custom_code_presets import DistributedCustomCodePresets
|
9
|
+
from .instance import Instance
|
10
|
+
from .responses import PipelineArchiveResponse, PipelineOKResponse
|
11
|
+
from .transaction import Transaction
|
12
|
+
|
13
|
+
|
14
|
+
class Pipeline(BaseCodeChecker):
|
15
|
+
"""
|
16
|
+
A `Pipeline` is a an object that encapsulates a one-to-many, data acquisition to data processing, flow of data.
|
17
|
+
|
18
|
+
A `Pipeline` contains one thread of data acquisition (which does not mean only one source of data), and many
|
19
|
+
processing units, usually named `Plugins`.
|
20
|
+
|
21
|
+
An `Instance` is a running thread of a `Plugin` type, and one may want to have multiple `Instances`, because each can be configured independently.
|
22
|
+
|
23
|
+
As such, one will work with `Instances`, by reffering to them with the unique identifier (Pipeline, Plugin, Instance).
|
24
|
+
|
25
|
+
In the documentation, the following reffer to the same thing:
|
26
|
+
`Pipeline` == `Stream`
|
27
|
+
|
28
|
+
`Plugin` == `Signature`
|
29
|
+
"""
|
30
|
+
|
31
|
+
def __init__(self, session, log, *, node_addr, name, config={}, plugins=[], on_data=None, on_notification=None, is_attached=False, existing_config=None, **kwargs) -> None:
|
32
|
+
"""
|
33
|
+
A `Pipeline` is a an object that encapsulates a one-to-many, data acquisition to data processing, flow of data.
|
34
|
+
|
35
|
+
A `Pipeline` contains one thread of data acquisition (which does not mean only one source of data), and many
|
36
|
+
processing units, usually named `Plugins`.
|
37
|
+
|
38
|
+
An `Instance` is a running thread of a `Plugin` type, and one may want to have multiple `Instances`, because each can be configured independently.
|
39
|
+
|
40
|
+
As such, one will work with `Instances`, by referring to them with the unique identifier (Pipeline, Plugin, Instance).
|
41
|
+
|
42
|
+
In the documentation, the following refer to the same thing:
|
43
|
+
`Pipeline` == `Stream`
|
44
|
+
|
45
|
+
`Plugin` == `Signature`
|
46
|
+
|
47
|
+
Parameters
|
48
|
+
----------
|
49
|
+
session : Session
|
50
|
+
The Session object which owns this pipeline. A pipeline must be attached to a Session because that is the only
|
51
|
+
way the `on_X` callbacks are called
|
52
|
+
log : Logger
|
53
|
+
A logger object which implements basic logging functionality and some other utils stuff. Can be ignored for now.
|
54
|
+
In the future, the documentation for the Logger base class will be available and developers will be able to use
|
55
|
+
custom-made Loggers.
|
56
|
+
node_addr : str
|
57
|
+
Address of the Naeural edge node that will handle this pipeline.
|
58
|
+
name : str
|
59
|
+
The name of this pipeline.
|
60
|
+
data_source : str
|
61
|
+
This is the name of the DCT plugin, which resembles the desired functionality of the acquisition.
|
62
|
+
config : dict, optional
|
63
|
+
This is the dictionary that contains the configuration of the acquisition source, by default {}
|
64
|
+
plugins : List | None, optional
|
65
|
+
This is the list with manually configured business plugins that will be in the pipeline at creation time.
|
66
|
+
We recommend to leave this as `[]` or as `None` and use the API to create plugin instances.
|
67
|
+
on_data : Callable[[Pipeline, str, str, dict], None], optional,
|
68
|
+
Callback that handles messages received from any plugin instance.
|
69
|
+
As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
70
|
+
This callback acts as a default payload processor and will be called even if for a given instance
|
71
|
+
the user has defined a specific callback.
|
72
|
+
Defaults to None.
|
73
|
+
on_notification : Callable[[Pipeline, dict], None], optional
|
74
|
+
Callback that handles notifications received from this instance.
|
75
|
+
As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
76
|
+
This callback acts as a default payload processor and will be called even if for a given instance
|
77
|
+
the user has defined a specific callback.
|
78
|
+
Defaults to None.
|
79
|
+
is_attached : bool
|
80
|
+
This is used internally to allow the user to create or attach to a pipeline, and then use the same
|
81
|
+
objects in the same way, by default True
|
82
|
+
**kwargs : dict
|
83
|
+
The user can provide the configuration of the acquisition source directly as kwargs.
|
84
|
+
"""
|
85
|
+
self.log = log
|
86
|
+
self.session = session
|
87
|
+
self.node_addr = node_addr
|
88
|
+
self.name = name
|
89
|
+
|
90
|
+
self.config = {}
|
91
|
+
plugins = config.pop('PLUGINS', plugins)
|
92
|
+
|
93
|
+
if is_attached:
|
94
|
+
assert existing_config is not None, "When attaching to a pipeline, the existing configuration should be found in the heartbeat of the Naeural edge node."
|
95
|
+
assert config == {}, "Cannot provide a configuration when attaching to a pipeline."
|
96
|
+
assert len(kwargs) == 0, "Cannot provide a configuration when attaching to a pipeline."
|
97
|
+
self.config = {k.upper(): v for k, v in existing_config.items()}
|
98
|
+
self.config = self.__pop_ignored_keys_from_config(self.config)
|
99
|
+
self.proposed_config = None
|
100
|
+
else:
|
101
|
+
self.proposed_config = {**config, **kwargs}
|
102
|
+
self.proposed_config = {k.upper(): v for k, v in self.proposed_config.items()}
|
103
|
+
self.proposed_config = self.__pop_ignored_keys_from_config(self.proposed_config)
|
104
|
+
self.__staged_config = None
|
105
|
+
|
106
|
+
self.__was_last_operation_successful = None
|
107
|
+
|
108
|
+
self.proposed_remove_instances = []
|
109
|
+
self.__staged_remove_instances = []
|
110
|
+
|
111
|
+
self.on_data_callbacks = []
|
112
|
+
self.on_notification_callbacks = []
|
113
|
+
|
114
|
+
if on_data is not None:
|
115
|
+
self.on_data_callbacks.append(on_data)
|
116
|
+
if on_notification is not None:
|
117
|
+
self.on_notification_callbacks.append(on_notification)
|
118
|
+
|
119
|
+
self.lst_plugin_instances: list[Instance] = []
|
120
|
+
|
121
|
+
self.__init_plugins(plugins, is_attached)
|
122
|
+
return
|
123
|
+
|
124
|
+
# Utils
|
125
|
+
if True:
|
126
|
+
def __init_instance(self, signature, instance_id, config, on_data, on_notification, is_attached):
|
127
|
+
instance_class = None
|
128
|
+
str_signature = None
|
129
|
+
if isinstance(signature, str):
|
130
|
+
instance_class = Instance
|
131
|
+
str_signature = signature.upper()
|
132
|
+
else:
|
133
|
+
instance_class = signature
|
134
|
+
str_signature = instance_class.signature.upper()
|
135
|
+
instance = instance_class(self.log,
|
136
|
+
pipeline=self,
|
137
|
+
signature=str_signature,
|
138
|
+
instance_id=instance_id,
|
139
|
+
config=config,
|
140
|
+
on_data=on_data,
|
141
|
+
on_notification=on_notification,
|
142
|
+
is_attached=is_attached
|
143
|
+
)
|
144
|
+
self.lst_plugin_instances.append(instance)
|
145
|
+
return instance
|
146
|
+
|
147
|
+
def __init_plugins(self, plugins, is_attached):
|
148
|
+
"""
|
149
|
+
Initialize the plugins list. This method is called at the creation of the pipeline and is used to create the instances of the plugins that are part of the pipeline.
|
150
|
+
|
151
|
+
Parameters
|
152
|
+
----------
|
153
|
+
plugins : List | None
|
154
|
+
The list of plugins, as they are found in the pipeline configuration dictionary in the heartbeat.
|
155
|
+
is_attached : bool
|
156
|
+
This is used internally to allow the user to create or attach to a pipeline, and then use the same objects in the same way.
|
157
|
+
|
158
|
+
"""
|
159
|
+
if plugins is None:
|
160
|
+
return
|
161
|
+
|
162
|
+
for dct_signature_instances in plugins:
|
163
|
+
signature = dct_signature_instances['SIGNATURE']
|
164
|
+
instances = dct_signature_instances['INSTANCES']
|
165
|
+
for dct_instance in instances:
|
166
|
+
config = {k.upper(): v for k, v in dct_instance.items()}
|
167
|
+
instance_id = config.pop('INSTANCE_ID')
|
168
|
+
self.__init_instance(signature, instance_id, config, None, None, is_attached=is_attached)
|
169
|
+
# end for dct_instance
|
170
|
+
# end for dct_signature_instances
|
171
|
+
return
|
172
|
+
|
173
|
+
def __get_proposed_pipeline_config(self):
|
174
|
+
"""
|
175
|
+
Construct the proposed pipeline configuration dictionary.
|
176
|
+
|
177
|
+
Returns
|
178
|
+
-------
|
179
|
+
dict
|
180
|
+
The proposed pipeline configuration dictionary.
|
181
|
+
"""
|
182
|
+
|
183
|
+
plugin_dict = self.__construct_plugins_dictionary(skip_instances=self.proposed_remove_instances)
|
184
|
+
|
185
|
+
plugins_list = []
|
186
|
+
for signature, instances in plugin_dict.items():
|
187
|
+
plugins_list.append({
|
188
|
+
'SIGNATURE': signature,
|
189
|
+
'INSTANCES': [instance._get_proposed_config_dictionary(full=True) for instance in instances]
|
190
|
+
})
|
191
|
+
|
192
|
+
proposed_pipeline_config = {
|
193
|
+
'NAME': self.name,
|
194
|
+
'DEFAULT_PLUGIN': False,
|
195
|
+
'PLUGINS': plugins_list,
|
196
|
+
**self.config,
|
197
|
+
**(self.proposed_config or {}),
|
198
|
+
}
|
199
|
+
return proposed_pipeline_config
|
200
|
+
|
201
|
+
def __register_transactions_for_update(self, session_id: str = None, timeout: float = 0) -> list[Transaction]:
|
202
|
+
"""
|
203
|
+
Register transactions for updating the pipeline and instances configuration.
|
204
|
+
This method is called before sending an update pipeline configuration command to the Naeural edge node.
|
205
|
+
|
206
|
+
Parameters
|
207
|
+
----------
|
208
|
+
session_id : str, optional
|
209
|
+
The session id. A unique id for the session. Defaults to None.
|
210
|
+
|
211
|
+
timeout : int, optional
|
212
|
+
The timeout for the transaction. Defaults to 0.
|
213
|
+
|
214
|
+
Returns
|
215
|
+
-------
|
216
|
+
transactions : list[Transaction]
|
217
|
+
The list of transactions generated.
|
218
|
+
"""
|
219
|
+
transactions = []
|
220
|
+
|
221
|
+
# TODO: add different responses for different states of the plugin
|
222
|
+
# TODO: maybe this should be introduced as "pre-defined" business plugins, based on a schema
|
223
|
+
# and the user can specify them when creating the pipeline
|
224
|
+
|
225
|
+
for instance in self.lst_plugin_instances:
|
226
|
+
if instance._is_tainted():
|
227
|
+
transactions.append(self.session._register_transaction(
|
228
|
+
session_id=session_id,
|
229
|
+
lst_required_responses=instance._get_instance_update_required_responses(),
|
230
|
+
timeout=timeout,
|
231
|
+
on_success_callback=instance._apply_staged_config,
|
232
|
+
# TODO: if the instance was newly added, remove it from the tracked list
|
233
|
+
on_failure_callback=instance._discard_staged_config,
|
234
|
+
))
|
235
|
+
# end for register to update instances
|
236
|
+
|
237
|
+
for instance in self.proposed_remove_instances:
|
238
|
+
transactions.append(self.session._register_transaction(
|
239
|
+
session_id=session_id,
|
240
|
+
lst_required_responses=instance._get_instance_remove_required_responses(),
|
241
|
+
timeout=timeout,
|
242
|
+
on_success_callback=lambda: self.__apply_staged_remove_instance(instance),
|
243
|
+
on_failure_callback=lambda fail_reason: self.__discard_staged_remove_instance(instance, fail_reason),
|
244
|
+
))
|
245
|
+
# end for register to remove instances
|
246
|
+
|
247
|
+
if self.proposed_config is not None:
|
248
|
+
required_responses = [
|
249
|
+
PipelineOKResponse(self.node_id, self.name),
|
250
|
+
]
|
251
|
+
transactions.append(self.session._register_transaction(
|
252
|
+
session_id=session_id,
|
253
|
+
lst_required_responses=required_responses,
|
254
|
+
timeout=timeout,
|
255
|
+
on_success_callback=self.__apply_staged_config,
|
256
|
+
on_failure_callback=self.__discard_staged_config,
|
257
|
+
))
|
258
|
+
|
259
|
+
return transactions
|
260
|
+
|
261
|
+
def __register_transactions_for_delete(self, session_id: str = None, timeout: float = 0) -> list[Transaction]:
|
262
|
+
"""
|
263
|
+
Register transactions for deleting the pipeline.
|
264
|
+
This method is called before sending a delete pipeline command to the Naeural edge node.
|
265
|
+
|
266
|
+
Parameters
|
267
|
+
----------
|
268
|
+
session_id : str, optional
|
269
|
+
The session id. A unique id for the session. Defaults to None.
|
270
|
+
|
271
|
+
timeout : int, optional
|
272
|
+
The timeout for the transaction. Defaults to 0.
|
273
|
+
|
274
|
+
Returns
|
275
|
+
-------
|
276
|
+
transactions : list[Transaction]
|
277
|
+
The list of transactions generated.
|
278
|
+
"""
|
279
|
+
transactions = []
|
280
|
+
|
281
|
+
required_responses = [
|
282
|
+
PipelineArchiveResponse(self.node_id, self.name),
|
283
|
+
]
|
284
|
+
transactions.append(self.session._register_transaction(
|
285
|
+
session_id=session_id,
|
286
|
+
lst_required_responses=required_responses,
|
287
|
+
timeout=timeout,
|
288
|
+
on_success_callback=self.__set_last_operation_successful,
|
289
|
+
on_failure_callback=self.__set_last_operation_failed,
|
290
|
+
))
|
291
|
+
|
292
|
+
return transactions
|
293
|
+
|
294
|
+
def __register_transaction_for_pipeline_command(self, session_id: str = None, timeout: float = 0) -> list[Transaction]:
|
295
|
+
"""
|
296
|
+
Register a transaction for a pipeline command.
|
297
|
+
This method is called before sending a pipeline command to the Naeural edge node.
|
298
|
+
|
299
|
+
Parameters
|
300
|
+
----------
|
301
|
+
session_id : str, optional
|
302
|
+
The session id. A unique id for the session. Defaults to None.
|
303
|
+
|
304
|
+
timeout : int, optional
|
305
|
+
The timeout for the transaction. Defaults to 0.
|
306
|
+
|
307
|
+
Returns
|
308
|
+
-------
|
309
|
+
transactions : list[Transaction]
|
310
|
+
The list of transactions generated.
|
311
|
+
"""
|
312
|
+
transactions = []
|
313
|
+
|
314
|
+
# TODO: implement
|
315
|
+
self.__set_last_operation_successful()
|
316
|
+
|
317
|
+
return transactions
|
318
|
+
|
319
|
+
def __construct_plugins_list(self):
|
320
|
+
"""
|
321
|
+
Construct the plugins list that will be in the pipeline configuration dictionary.
|
322
|
+
|
323
|
+
Returns
|
324
|
+
-------
|
325
|
+
list
|
326
|
+
The plugins list that will be in the pipeline configuration dictionary.
|
327
|
+
"""
|
328
|
+
plugins = []
|
329
|
+
dct_signature_instances = {}
|
330
|
+
for instance in self.lst_plugin_instances:
|
331
|
+
signature = instance.signature
|
332
|
+
if instance.signature not in dct_signature_instances:
|
333
|
+
dct_signature_instances[instance.signature] = []
|
334
|
+
dct_signature_instances[instance.signature].append(instance)
|
335
|
+
# end for construct dct_signature_instances
|
336
|
+
|
337
|
+
for signature, instances in dct_signature_instances.items():
|
338
|
+
plugins.append({
|
339
|
+
'SIGNATURE': signature,
|
340
|
+
'INSTANCES': [instance._get_config_dictionary() for instance in instances]
|
341
|
+
})
|
342
|
+
# end for construct plugins list
|
343
|
+
return plugins
|
344
|
+
|
345
|
+
def __construct_plugins_dictionary(self, skip_instances=None):
|
346
|
+
"""
|
347
|
+
Construct the plugins dictionary that will be in the pipeline configuration dictionary.
|
348
|
+
|
349
|
+
Returns
|
350
|
+
-------
|
351
|
+
dict
|
352
|
+
The plugins dictionary that will be in the pipeline configuration dictionary.
|
353
|
+
"""
|
354
|
+
# plugins = []
|
355
|
+
skip_instances = skip_instances or []
|
356
|
+
dct_signature_instances = {}
|
357
|
+
for instance in self.lst_plugin_instances:
|
358
|
+
if instance in skip_instances:
|
359
|
+
continue
|
360
|
+
if instance.signature not in dct_signature_instances:
|
361
|
+
dct_signature_instances[instance.signature] = []
|
362
|
+
dct_signature_instances[instance.signature].append(instance)
|
363
|
+
# end for construct dct_signature_instances
|
364
|
+
|
365
|
+
return dct_signature_instances
|
366
|
+
|
367
|
+
def __send_update_config_to_box(self, session_id=None):
|
368
|
+
"""
|
369
|
+
Send an update pipeline configuration command to the Naeural edge node.
|
370
|
+
"""
|
371
|
+
self.session._send_command_update_pipeline_config(
|
372
|
+
worker=self.node_addr,
|
373
|
+
pipeline_config=self.__get_proposed_pipeline_config(),
|
374
|
+
session_id=session_id
|
375
|
+
)
|
376
|
+
return
|
377
|
+
|
378
|
+
def __batch_update_instances(self, lst_instances, session_id=None):
|
379
|
+
"""
|
380
|
+
Update the configuration of multiple instances at once.
|
381
|
+
```
|
382
|
+
|
383
|
+
Parameters
|
384
|
+
----------
|
385
|
+
lst_updates : List[Instance]
|
386
|
+
A list of instances.
|
387
|
+
"""
|
388
|
+
lst_updates = []
|
389
|
+
|
390
|
+
for instance in lst_instances:
|
391
|
+
lst_updates.append({
|
392
|
+
PAYLOAD_DATA.NAME: self.name,
|
393
|
+
PAYLOAD_DATA.SIGNATURE: instance.signature,
|
394
|
+
PAYLOAD_DATA.INSTANCE_ID: instance.instance_id,
|
395
|
+
PAYLOAD_DATA.INSTANCE_CONFIG: instance._get_proposed_config_dictionary(full=False)
|
396
|
+
})
|
397
|
+
|
398
|
+
self.session._send_command_batch_update_instance_config(
|
399
|
+
worker=self.node_addr,
|
400
|
+
lst_updates=lst_updates,
|
401
|
+
session_id=session_id
|
402
|
+
)
|
403
|
+
|
404
|
+
def __pop_ignored_keys_from_config(self, config):
|
405
|
+
"""
|
406
|
+
Pop the ignored keys from the configuration.
|
407
|
+
|
408
|
+
Parameters
|
409
|
+
----------
|
410
|
+
config : dict
|
411
|
+
The configuration dictionary.
|
412
|
+
|
413
|
+
Returns
|
414
|
+
-------
|
415
|
+
dict
|
416
|
+
The configuration dictionary without the ignored keys.
|
417
|
+
"""
|
418
|
+
ignored_keys = ["INITIATOR_ADDR", "INITIATOR_ID", "LAST_UPDATE_TIME", "MODIFIED_BY_ADDR", "MODIFIED_BY_ID"]
|
419
|
+
return {k: v for k, v in config.items() if k not in ignored_keys}
|
420
|
+
|
421
|
+
def __get_instance_object(self, signature, instance_id):
|
422
|
+
"""
|
423
|
+
Get the instance object by signature and instance id.
|
424
|
+
|
425
|
+
Parameters
|
426
|
+
----------
|
427
|
+
signature : str
|
428
|
+
The signature of the plugin.
|
429
|
+
instance_id : str
|
430
|
+
The name of the instance.
|
431
|
+
|
432
|
+
Returns
|
433
|
+
-------
|
434
|
+
Instance
|
435
|
+
The instance object.
|
436
|
+
"""
|
437
|
+
for instance in self.lst_plugin_instances:
|
438
|
+
if instance.signature == signature and instance.instance_id == instance_id:
|
439
|
+
return instance
|
440
|
+
return None
|
441
|
+
|
442
|
+
def __set_last_operation_successful(self):
|
443
|
+
"""
|
444
|
+
Set the last operation successful.
|
445
|
+
"""
|
446
|
+
self.__was_last_operation_successful = True
|
447
|
+
return
|
448
|
+
|
449
|
+
def __set_last_operation_failed(self, fail_reason):
|
450
|
+
"""
|
451
|
+
Set the last operation failed.
|
452
|
+
"""
|
453
|
+
self.__was_last_operation_successful = False
|
454
|
+
return
|
455
|
+
|
456
|
+
@staticmethod
|
457
|
+
def __custom_exec_on_data(self, instance_id, on_data_callback, data):
|
458
|
+
"""
|
459
|
+
Handle the data received from a custom execution instance. This method is called by the Session object when a message is received from a custom execution instance.
|
460
|
+
|
461
|
+
Parameters
|
462
|
+
----------
|
463
|
+
instance_id : str
|
464
|
+
The name of the instance that sent the message.
|
465
|
+
on_data_callback : Callable[[Pipeline, dict, dict], None]
|
466
|
+
The callback that handles the message. The first dict is the payload, and the second dict is the entire message.
|
467
|
+
data : dict | Payload
|
468
|
+
The payload of the message.
|
469
|
+
"""
|
470
|
+
# TODO: use formatter for this message
|
471
|
+
# TODO: expose the other fields from data
|
472
|
+
exec_data = None
|
473
|
+
|
474
|
+
exec_data = data.get('EXEC_RESULT', data.get('EXEC_INFO'))
|
475
|
+
exec_error = data.get('EXEC_ERRORS', 'no keyword error')
|
476
|
+
|
477
|
+
if exec_error is not None:
|
478
|
+
self.P("Error received from <CUSTOM_EXEC_01:{}>: {}".format(instance_id, exec_error), color="r", verbosity=1)
|
479
|
+
if exec_data is not None:
|
480
|
+
on_data_callback(self, exec_data, data)
|
481
|
+
return
|
482
|
+
|
483
|
+
def __apply_staged_remove_instance(self, instance: Instance):
|
484
|
+
"""
|
485
|
+
Remove an instance from the pipeline.
|
486
|
+
|
487
|
+
Parameters
|
488
|
+
----------
|
489
|
+
instance : Instance
|
490
|
+
The instance to be removed.
|
491
|
+
"""
|
492
|
+
instance.config = None
|
493
|
+
try:
|
494
|
+
self.__staged_remove_instances.remove(instance)
|
495
|
+
except:
|
496
|
+
self.P("Attempted to remove instance <{}:{}>, but it was not found in the staged remove list. "
|
497
|
+
"Most likely the instance deletion used `with_confirmation=False`".format(
|
498
|
+
instance.signature, instance.instance_id), color="r")
|
499
|
+
return
|
500
|
+
|
501
|
+
def __discard_staged_remove_instance(self, instance: Instance, fail_reason: str):
|
502
|
+
"""
|
503
|
+
Discard the removal of an instance from the pipeline.
|
504
|
+
|
505
|
+
Parameters
|
506
|
+
----------
|
507
|
+
instance : Instance
|
508
|
+
The instance to be removed.
|
509
|
+
"""
|
510
|
+
|
511
|
+
self.P(
|
512
|
+
f"Discarding staged removal of instance <{instance.signature}:{instance.instance_id}>. Reason: {fail_reason}", color="r")
|
513
|
+
|
514
|
+
try:
|
515
|
+
self.__staged_remove_instances.remove(instance)
|
516
|
+
except:
|
517
|
+
self.P("Attempted to remove instance <{}:{}>, but it was not found in the staged remove list. "
|
518
|
+
"Most likely the instance deletion used `with_confirmation=False`".format(
|
519
|
+
instance.signature, instance.instance_id), color="r")
|
520
|
+
|
521
|
+
self.lst_plugin_instances.append(instance)
|
522
|
+
return
|
523
|
+
|
524
|
+
def __apply_staged_config(self, verbose=False):
|
525
|
+
"""
|
526
|
+
Apply the staged configuration to the pipeline.
|
527
|
+
"""
|
528
|
+
if self.__staged_config is None:
|
529
|
+
return
|
530
|
+
|
531
|
+
if verbose:
|
532
|
+
self.P("Deployed pipeline <{}> on <{}>".format(self.name, self.node_addr), color="g")
|
533
|
+
self.__was_last_operation_successful = True
|
534
|
+
|
535
|
+
self.config = {**self.config, **self.__staged_config}
|
536
|
+
self.__staged_config = None
|
537
|
+
|
538
|
+
return
|
539
|
+
|
540
|
+
def __apply_staged_instances_config(self, verbose=False):
|
541
|
+
"""
|
542
|
+
Apply the staged configuration to the instances.
|
543
|
+
"""
|
544
|
+
for instance in self.lst_plugin_instances:
|
545
|
+
instance._apply_staged_config(verbose=verbose)
|
546
|
+
|
547
|
+
for instance in self.__staged_remove_instances:
|
548
|
+
instance.config = None
|
549
|
+
self.lst_plugin_instances.remove(instance)
|
550
|
+
|
551
|
+
self.__staged_remove_instances = []
|
552
|
+
return
|
553
|
+
|
554
|
+
def __discard_staged_config(self, fail_reason: str):
|
555
|
+
"""
|
556
|
+
Discard the staged configuration for the pipeline.
|
557
|
+
"""
|
558
|
+
|
559
|
+
self.P(f'Discarding staged configuration for pipeline <{self.name}>. Reason: {fail_reason}', color="r")
|
560
|
+
self.__was_last_operation_successful = False
|
561
|
+
|
562
|
+
self.__staged_config = None
|
563
|
+
self.__staged_remove_instances = []
|
564
|
+
return
|
565
|
+
|
566
|
+
def __stage_proposed_config(self):
|
567
|
+
"""
|
568
|
+
Stage the proposed configuration.
|
569
|
+
"""
|
570
|
+
if self.proposed_config is not None:
|
571
|
+
if self.__staged_config is not None:
|
572
|
+
raise ValueError(
|
573
|
+
"Pipeline configuration has already been staged, waiting for confirmation from Execution Engine")
|
574
|
+
|
575
|
+
self.__staged_config = self.proposed_config
|
576
|
+
self.proposed_config = None
|
577
|
+
|
578
|
+
for instance in self.lst_plugin_instances:
|
579
|
+
instance._stage_proposed_config()
|
580
|
+
|
581
|
+
self.__staged_remove_instances.extend(self.proposed_remove_instances)
|
582
|
+
self.proposed_remove_instances = []
|
583
|
+
|
584
|
+
self.__was_last_operation_successful = None
|
585
|
+
return
|
586
|
+
|
587
|
+
def __print_proposed_changes(self):
|
588
|
+
"""
|
589
|
+
Print the proposed changes to the pipeline.
|
590
|
+
"""
|
591
|
+
|
592
|
+
if self.proposed_config is not None:
|
593
|
+
self.P("Proposed changes to pipeline <{}>:".format(self.name), verbosity=1)
|
594
|
+
self.P(" - Current config: {}".format(self.config), verbosity=1)
|
595
|
+
self.P(" - New pipeline config: {}".format(self.proposed_config), verbosity=1)
|
596
|
+
|
597
|
+
if len(self.proposed_remove_instances) > 0:
|
598
|
+
self.P(
|
599
|
+
" - Remove instances: {}".format([instance.instance_id for instance in self.proposed_remove_instances]), verbosity=1)
|
600
|
+
|
601
|
+
for instance in self.lst_plugin_instances:
|
602
|
+
if instance._is_tainted():
|
603
|
+
self.P(" - Plugin <{}:{}>:".format(instance.signature, instance.instance_id), verbosity=1)
|
604
|
+
self.P(" - Current config: {}".format(instance.config), verbosity=1)
|
605
|
+
self.P(" - Proposed config: {}".format(instance.proposed_config), verbosity=1)
|
606
|
+
return
|
607
|
+
|
608
|
+
def _close(self, timeout=10):
|
609
|
+
"""
|
610
|
+
Close the pipeline.
|
611
|
+
|
612
|
+
Returns
|
613
|
+
-------
|
614
|
+
list[Transaction]
|
615
|
+
The list of transactions generated.
|
616
|
+
"""
|
617
|
+
transactions = self.__register_transactions_for_delete(timeout=timeout)
|
618
|
+
|
619
|
+
self.__was_last_operation_successful = None
|
620
|
+
|
621
|
+
self.session._send_command_archive_pipeline(
|
622
|
+
worker=self.node_addr,
|
623
|
+
pipeline_name=self.name,
|
624
|
+
)
|
625
|
+
|
626
|
+
return transactions
|
627
|
+
|
628
|
+
def _get_base64_code(self, custom_code):
|
629
|
+
"""
|
630
|
+
Get the base64 code.
|
631
|
+
|
632
|
+
Parameters
|
633
|
+
----------
|
634
|
+
custom_code : str | callable
|
635
|
+
The custom code.
|
636
|
+
|
637
|
+
Returns
|
638
|
+
-------
|
639
|
+
str
|
640
|
+
The base64 code.
|
641
|
+
"""
|
642
|
+
if custom_code is None:
|
643
|
+
return None
|
644
|
+
|
645
|
+
if isinstance(custom_code, str):
|
646
|
+
# it is a path
|
647
|
+
if os.path.exists(custom_code):
|
648
|
+
with open(custom_code, "r") as fd:
|
649
|
+
plain_code = "".join(fd.readlines())
|
650
|
+
# it is a string
|
651
|
+
else:
|
652
|
+
try:
|
653
|
+
method_name = "_DistributedCustomCodePresets__{}".format(custom_code.lower())
|
654
|
+
preset_code = getattr(DistributedCustomCodePresets, method_name)
|
655
|
+
plain_code = self.get_function_source_code(preset_code)
|
656
|
+
except:
|
657
|
+
plain_code = custom_code
|
658
|
+
elif callable(custom_code):
|
659
|
+
# we have a function
|
660
|
+
plain_code = self.get_function_source_code(custom_code)
|
661
|
+
else:
|
662
|
+
raise Exception("custom_code is not a string or a callable")
|
663
|
+
# endif get plain code
|
664
|
+
|
665
|
+
return self.code_to_base64(plain_code, verbose=False)
|
666
|
+
|
667
|
+
# Message handling
|
668
|
+
if True:
|
669
|
+
def _on_data(self, signature, instance_id, data):
|
670
|
+
"""
|
671
|
+
Handle the data received from the Naeural edge node. This method is called by the Session object when a message is received from the Naeural edge node.
|
672
|
+
This method will call all the `on_data` callbacks of the pipeline and the instance that received the message.
|
673
|
+
|
674
|
+
Parameters
|
675
|
+
----------
|
676
|
+
signature : str
|
677
|
+
The signature of the plugin that sent the message.
|
678
|
+
instance_id : str
|
679
|
+
The name of the instance that sent the message.
|
680
|
+
data : dict | Payload
|
681
|
+
The payload of the message.
|
682
|
+
"""
|
683
|
+
# call all self callbacks
|
684
|
+
for callback in self.on_data_callbacks:
|
685
|
+
callback(self, signature, instance_id, data)
|
686
|
+
|
687
|
+
# call all instance callbacks
|
688
|
+
self.__call_instance_on_data_callbacks(signature, instance_id, data)
|
689
|
+
return
|
690
|
+
|
691
|
+
def _on_notification(self, signature, instance_id, data):
|
692
|
+
"""
|
693
|
+
Handle the notification received from the Naeural edge node. This method is called by the Session object when a notification is received from the Naeural edge node.
|
694
|
+
|
695
|
+
Parameters
|
696
|
+
----------
|
697
|
+
signature : str
|
698
|
+
The signature of the plugin that sent the notification.
|
699
|
+
instance_id : str
|
700
|
+
The name of the instance that sent the notification.
|
701
|
+
data : dict | Payload
|
702
|
+
The payload of the notification.
|
703
|
+
"""
|
704
|
+
# call all self callbacks
|
705
|
+
for callback in self.on_notification_callbacks:
|
706
|
+
callback(self, data)
|
707
|
+
|
708
|
+
# call all instance callbacks
|
709
|
+
self.__call_instance_on_notification_callbacks(signature, instance_id, data)
|
710
|
+
return
|
711
|
+
|
712
|
+
def _add_on_data_callback(self, callback):
|
713
|
+
"""
|
714
|
+
Add a new callback to the list of callbacks that handle the data received from the pipeline.
|
715
|
+
|
716
|
+
Parameters
|
717
|
+
----------
|
718
|
+
callback : Callable[[Pipeline, str, str, dict], None]
|
719
|
+
The callback to add
|
720
|
+
"""
|
721
|
+
self.on_data_callbacks.append(callback)
|
722
|
+
return
|
723
|
+
|
724
|
+
def _reset_on_data_callback(self):
|
725
|
+
"""
|
726
|
+
Reset the list of callbacks that handle the data received from the pipeline.
|
727
|
+
"""
|
728
|
+
self.on_data_callbacks = []
|
729
|
+
return
|
730
|
+
|
731
|
+
def _add_on_notification_callback(self, callback):
|
732
|
+
"""
|
733
|
+
Add a new callback to the list of callbacks that handle the notifications received from the pipeline.
|
734
|
+
|
735
|
+
Parameters
|
736
|
+
----------
|
737
|
+
callback : Callable[[Pipeline, dict], None]
|
738
|
+
The callback to add
|
739
|
+
"""
|
740
|
+
self.on_notification_callbacks.append(callback)
|
741
|
+
return
|
742
|
+
|
743
|
+
def _reset_on_notification_callback(self):
|
744
|
+
"""
|
745
|
+
Reset the list of callbacks that handle the notifications received from the pipeline.
|
746
|
+
"""
|
747
|
+
self.on_notification_callbacks = []
|
748
|
+
return
|
749
|
+
|
750
|
+
def __call_instance_on_data_callbacks(self, signature, instance_id, data):
|
751
|
+
"""
|
752
|
+
Call all the `on_data` callbacks of the instance that sent the message.
|
753
|
+
|
754
|
+
Parameters
|
755
|
+
----------
|
756
|
+
signature : str
|
757
|
+
The signature of the plugin that sent the payload.
|
758
|
+
instance_id : str
|
759
|
+
The name of the instance that sent the payload.
|
760
|
+
data : dict | Payload
|
761
|
+
The payload of the payload.
|
762
|
+
"""
|
763
|
+
for instance in self.lst_plugin_instances:
|
764
|
+
if instance.signature == signature and instance.instance_id == instance_id:
|
765
|
+
instance._on_data(self, data)
|
766
|
+
return
|
767
|
+
|
768
|
+
def __call_instance_on_notification_callbacks(self, signature, instance_id, data):
|
769
|
+
"""
|
770
|
+
Call all the `on_notification` callbacks of the instance that sent the notification.
|
771
|
+
|
772
|
+
Parameters
|
773
|
+
----------
|
774
|
+
signature : str
|
775
|
+
The signature of the plugin that sent the notification.
|
776
|
+
instance_id : str
|
777
|
+
The name of the instance that sent the notification.
|
778
|
+
data : dict | Payload
|
779
|
+
The payload of the notification.
|
780
|
+
"""
|
781
|
+
for instance in self.lst_plugin_instances:
|
782
|
+
if instance.signature == signature and instance.instance_id == instance_id:
|
783
|
+
instance._on_notification(self, data)
|
784
|
+
return
|
785
|
+
|
786
|
+
# API
|
787
|
+
if True:
|
788
|
+
@property
|
789
|
+
def was_last_operation_successful(self):
|
790
|
+
"""
|
791
|
+
Return whether the last operation was successful.
|
792
|
+
|
793
|
+
Returns
|
794
|
+
-------
|
795
|
+
bool
|
796
|
+
True if the last operation was successful, False if it failed, None if the ACK has not been received yet
|
797
|
+
"""
|
798
|
+
return self.__was_last_operation_successful
|
799
|
+
|
800
|
+
def create_plugin_instance(self, *, signature, instance_id, config={}, on_data=None, on_notification=None, **kwargs) -> Instance:
|
801
|
+
"""
|
802
|
+
Create a new instance of a desired plugin, with a given configuration. This instance is attached to this pipeline,
|
803
|
+
meaning it processes data from this pipelines data source. Parameters can be passed either in the `config` dict, or as `kwargs`.
|
804
|
+
|
805
|
+
Parameters
|
806
|
+
----------
|
807
|
+
signature : str
|
808
|
+
The name of the plugin signature. This is the name of the desired overall functionality.
|
809
|
+
instance_id : str
|
810
|
+
The name of the instance. There can be multiple instances of the same plugin, mostly with different parameters
|
811
|
+
config : dict, optional
|
812
|
+
parameters used to customize the functionality. One can change the AI engine used for object detection,
|
813
|
+
or finetune alerter parameters to better fit a camera located in a low light environment.
|
814
|
+
Defaults to {}
|
815
|
+
on_data : Callable[[Pipeline, dict], None], optional
|
816
|
+
Callback that handles messages received from this instance.
|
817
|
+
As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
818
|
+
Defaults to None
|
819
|
+
on_notification : Callable[[Pipeline, dict], None], optional
|
820
|
+
Callback that handles notifications received from this instance.
|
821
|
+
As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
822
|
+
Defaults to None
|
823
|
+
|
824
|
+
Returns
|
825
|
+
-------
|
826
|
+
instance : Instance
|
827
|
+
An `Instance` object.
|
828
|
+
|
829
|
+
Raises
|
830
|
+
------
|
831
|
+
Exception
|
832
|
+
Plugin instance already exists.
|
833
|
+
"""
|
834
|
+
if isinstance(signature, str):
|
835
|
+
str_signature = signature.upper()
|
836
|
+
else:
|
837
|
+
plugin_template = signature
|
838
|
+
str_signature = plugin_template.signature.upper()
|
839
|
+
|
840
|
+
for instance in self.lst_plugin_instances:
|
841
|
+
if instance.instance_id == instance_id and instance.signature == str_signature:
|
842
|
+
raise Exception("plugin {} with instance {} already exists".format(str_signature, instance_id))
|
843
|
+
|
844
|
+
# create the new instance and add it to the list
|
845
|
+
config = {**config, **kwargs}
|
846
|
+
instance = self.__init_instance(signature, instance_id, config, on_data, on_notification, is_attached=False)
|
847
|
+
return instance
|
848
|
+
|
849
|
+
def __remove_plugin_instance(self, instance):
|
850
|
+
"""
|
851
|
+
Remove a plugin instance from this pipeline.
|
852
|
+
|
853
|
+
Parameters
|
854
|
+
----------
|
855
|
+
instance : Instance
|
856
|
+
The instance to be removed.
|
857
|
+
"""
|
858
|
+
if instance is None:
|
859
|
+
raise Exception("The provided instance is None. Please provide a valid instance")
|
860
|
+
|
861
|
+
if instance not in self.lst_plugin_instances:
|
862
|
+
raise Exception("plugin <{}/{}> does not exist on this pipeline".format(instance.signature, instance.instance_id))
|
863
|
+
|
864
|
+
self.lst_plugin_instances.remove(instance)
|
865
|
+
return
|
866
|
+
|
867
|
+
def remove_plugin_instance(self, instance):
|
868
|
+
"""
|
869
|
+
Stop a plugin instance from this pipeline.
|
870
|
+
|
871
|
+
|
872
|
+
Parameters
|
873
|
+
----------
|
874
|
+
instance : Instance
|
875
|
+
The instance to be stopped.
|
876
|
+
|
877
|
+
"""
|
878
|
+
|
879
|
+
self.__remove_plugin_instance(instance)
|
880
|
+
self.proposed_remove_instances.append(instance)
|
881
|
+
return
|
882
|
+
|
883
|
+
def create_custom_plugin_instance(self, *, instance_id, custom_code: callable, config={}, on_data=None, on_notification=None, **kwargs) -> Instance:
|
884
|
+
"""
|
885
|
+
Create a new custom execution instance, with a given configuration. This instance is attached to this pipeline,
|
886
|
+
meaning it processes data from this pipelines data source. The code used for the custom instance must be provided
|
887
|
+
either as a string, or as a path to a file. Parameters can be passed either in the `config` dict, or as kwargs.
|
888
|
+
The custom plugin instance will run periodically. If one desires to execute a custom code only once, use `wait_exec`.
|
889
|
+
|
890
|
+
Parameters
|
891
|
+
----------
|
892
|
+
instance_id : str
|
893
|
+
The name of the instance. There can be multiple instances of the same plugin, mostly with different parameters
|
894
|
+
custom_code : Callable[[CustomPluginTemplate], Any], optional
|
895
|
+
A string containing the entire code, a path to a file containing the code as a string or a function with the code.
|
896
|
+
This code will be executed remotely on an Naeural edge node. Defaults to None.
|
897
|
+
config : dict, optional
|
898
|
+
parameters used to customize the functionality. One can change the AI engine used for object detection,
|
899
|
+
or finetune alerter parameters to better fit a camera located in a low light environment.
|
900
|
+
Defaults to {}
|
901
|
+
on_data : Callable[[Pipeline, dict], None], optional
|
902
|
+
Callback that handles messages received from this instance.
|
903
|
+
As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
904
|
+
Defaults to None
|
905
|
+
on_notification : Callable[[Pipeline, dict], None], optional
|
906
|
+
Callback that handles notifications received from this instance.
|
907
|
+
As arguments, it has a reference to this Pipeline object, along with the payload itsel.
|
908
|
+
Defaults to None
|
909
|
+
|
910
|
+
Returns
|
911
|
+
-------
|
912
|
+
instance : Instance
|
913
|
+
An `Instance` object.
|
914
|
+
|
915
|
+
Raises
|
916
|
+
------
|
917
|
+
Exception
|
918
|
+
The code was not provided.
|
919
|
+
Exception
|
920
|
+
Plugin instance already exists.
|
921
|
+
"""
|
922
|
+
|
923
|
+
b64code = self._get_base64_code(custom_code)
|
924
|
+
|
925
|
+
def callback(pipeline, data): return self.__custom_exec_on_data(pipeline, instance_id, on_data, data)
|
926
|
+
callback = callback if on_data is not None else None
|
927
|
+
|
928
|
+
return self.create_plugin_instance(
|
929
|
+
signature='CUSTOM_EXEC_01',
|
930
|
+
instance_id=instance_id,
|
931
|
+
config={
|
932
|
+
'CODE': b64code,
|
933
|
+
**config
|
934
|
+
},
|
935
|
+
on_data=callback,
|
936
|
+
on_notification=on_notification,
|
937
|
+
**kwargs
|
938
|
+
)
|
939
|
+
|
940
|
+
# TODO: rename this!!!
|
941
|
+
def create_chain_dist_custom_plugin_instance(self,
|
942
|
+
*,
|
943
|
+
main_node_process_real_time_collected_data: any,
|
944
|
+
main_node_finish_condition: any,
|
945
|
+
main_node_aggregate_collected_data: any,
|
946
|
+
worker_node_code: any,
|
947
|
+
nr_remote_worker_nodes: int,
|
948
|
+
instance_id=None,
|
949
|
+
worker_node_pipeline_config={},
|
950
|
+
worker_node_plugin_signature='CUSTOM_EXEC_01',
|
951
|
+
worker_node_plugin_config={},
|
952
|
+
config={},
|
953
|
+
on_data=None,
|
954
|
+
on_notification=None,
|
955
|
+
**kwargs) -> Instance:
|
956
|
+
b64code_process_real_time_collected_data = self._get_base64_code(main_node_process_real_time_collected_data)
|
957
|
+
b64code_finish_condition = self._get_base64_code(main_node_finish_condition)
|
958
|
+
b64code_aggregate_collected_data = self._get_base64_code(main_node_aggregate_collected_data)
|
959
|
+
b64code_remote_node = self._get_base64_code(worker_node_code)
|
960
|
+
|
961
|
+
if instance_id is None:
|
962
|
+
instance_id = self.name + "_chain_dist_custom_exec_{}".format(self.log.get_unique_id())
|
963
|
+
|
964
|
+
return self.create_plugin_instance(
|
965
|
+
signature='PROCESS_REAL_TIME_COLLECTED_DATA_CUSTOM_EXEC_CHAIN_DIST',
|
966
|
+
instance_id=instance_id,
|
967
|
+
config={
|
968
|
+
'CUSTOM_CODE_PROCESS_REAL_TIME_COLLECTED_DATA': b64code_process_real_time_collected_data,
|
969
|
+
'CUSTOM_CODE_FINISH_CONDITION': b64code_finish_condition,
|
970
|
+
'CUSTOM_CODE_AGGREGATE_COLLECTED_DATA': b64code_aggregate_collected_data,
|
971
|
+
'CUSTOM_CODE_REMOTE_NODE': b64code_remote_node,
|
972
|
+
|
973
|
+
'NR_REMOTE_NODES': nr_remote_worker_nodes,
|
974
|
+
|
975
|
+
'NODE_PIPELINE_CONFIG': {
|
976
|
+
'stream_type': "Void",
|
977
|
+
**worker_node_pipeline_config
|
978
|
+
},
|
979
|
+
'NODE_PLUGIN_SIGNATURE': worker_node_plugin_signature,
|
980
|
+
'NODE_PLUGIN_CONFIG': {
|
981
|
+
**worker_node_plugin_config
|
982
|
+
},
|
983
|
+
**config
|
984
|
+
},
|
985
|
+
on_data=on_data,
|
986
|
+
on_notification=on_notification,
|
987
|
+
**kwargs
|
988
|
+
)
|
989
|
+
|
990
|
+
def deploy(self, with_confirmation=True, wait_confirmation=True, timeout=10, verbose=False):
|
991
|
+
"""
|
992
|
+
This method is used to deploy the pipeline on the Naeural edge node.
|
993
|
+
Here we collect all the proposed configurations and send them to the Naeural edge node.
|
994
|
+
All proposed configs become staged configs.
|
995
|
+
After all responses, apply the staged configs to finish the transaction.
|
996
|
+
"""
|
997
|
+
# generate a unique session id for this deploy operation
|
998
|
+
# this session id will be used to track the transactions
|
999
|
+
|
1000
|
+
# step 0: print the proposed changes
|
1001
|
+
if verbose:
|
1002
|
+
self.__print_proposed_changes()
|
1003
|
+
|
1004
|
+
# step 1: register transactions for updates
|
1005
|
+
transactions = []
|
1006
|
+
|
1007
|
+
if with_confirmation:
|
1008
|
+
transactions: list[Transaction] = self.__register_transactions_for_update(timeout=timeout)
|
1009
|
+
|
1010
|
+
# step 1: send the proposed config to the box
|
1011
|
+
pipeline_config_changed = self.proposed_config is not None
|
1012
|
+
have_to_remove_instances = len(self.proposed_remove_instances) > 0
|
1013
|
+
have_new_instances = any([instance._is_tainted() and len(instance.config) == 0
|
1014
|
+
for instance in self.lst_plugin_instances])
|
1015
|
+
if pipeline_config_changed or have_to_remove_instances or have_new_instances:
|
1016
|
+
# updated pipeline config or deleted instances
|
1017
|
+
self.__send_update_config_to_box()
|
1018
|
+
elif any([instance._is_tainted() for instance in self.lst_plugin_instances]):
|
1019
|
+
# updated instances only
|
1020
|
+
tainted_instances = [instance for instance in self.lst_plugin_instances if instance._is_tainted()]
|
1021
|
+
self.__batch_update_instances(tainted_instances)
|
1022
|
+
else:
|
1023
|
+
return
|
1024
|
+
|
1025
|
+
# step 3: stage the proposed config
|
1026
|
+
self.__stage_proposed_config()
|
1027
|
+
|
1028
|
+
# step 3: wait for the box to respond
|
1029
|
+
if with_confirmation and wait_confirmation:
|
1030
|
+
self.session.wait_for_transactions(transactions)
|
1031
|
+
|
1032
|
+
# step 4: apply the staged config
|
1033
|
+
if not with_confirmation:
|
1034
|
+
self.__apply_staged_config(verbose=verbose)
|
1035
|
+
self.__apply_staged_instances_config(verbose=verbose)
|
1036
|
+
|
1037
|
+
self.P("Pipeline <{}> deployed".format(self.name), color="g")
|
1038
|
+
|
1039
|
+
if with_confirmation and not wait_confirmation:
|
1040
|
+
return transactions
|
1041
|
+
return
|
1042
|
+
|
1043
|
+
def wait_exec(self, *, custom_code: callable, instance_config={}, timeout=10):
|
1044
|
+
"""
|
1045
|
+
Create a new REST-like custom execution instance, with a given configuration. This instance is attached to this pipeline,
|
1046
|
+
meaning it processes data from this pipelines data source. The code used for the custom instance must be provided either as a string, or as a path to a file. Parameters can be passed either in the config dict, or as kwargs.
|
1047
|
+
The REST-like custom plugin instance will execute only once. If one desires to execute a custom code periodically, use `create_custom_plugin_instance`.
|
1048
|
+
|
1049
|
+
Parameters
|
1050
|
+
----------
|
1051
|
+
custom_code : Callable[[CustomPluginTemplate], Any], optional
|
1052
|
+
A string containing the entire code, a path to a file containing the code as a string or a function with the code.
|
1053
|
+
This code will be executed remotely on an Naeural edge node. Defaults to None.
|
1054
|
+
config : dict, optional
|
1055
|
+
parameters used to customize the functionality, by default {}
|
1056
|
+
|
1057
|
+
Returns
|
1058
|
+
-------
|
1059
|
+
Tuple[Any, Any]
|
1060
|
+
a tuple containing the result of the execution and the error, if any.
|
1061
|
+
If the execution completed successfully, the `error` is None, and the `result` is the returned value of the custom code.
|
1062
|
+
|
1063
|
+
Raises
|
1064
|
+
------
|
1065
|
+
Exception
|
1066
|
+
The code was not provided.
|
1067
|
+
Exception
|
1068
|
+
Plugin instance already exists.
|
1069
|
+
"""
|
1070
|
+
|
1071
|
+
b64code = self._get_base64_code(custom_code)
|
1072
|
+
|
1073
|
+
finished = False
|
1074
|
+
result = None
|
1075
|
+
error = None
|
1076
|
+
|
1077
|
+
def on_data(pipeline, data):
|
1078
|
+
nonlocal finished
|
1079
|
+
nonlocal result
|
1080
|
+
nonlocal error
|
1081
|
+
|
1082
|
+
if 'REST_EXECUTION_RESULT' in data and 'REST_EXECUTION_ERROR' in data:
|
1083
|
+
result = data['REST_EXECUTION_RESULT']
|
1084
|
+
error = data['REST_EXECUTION_ERROR']
|
1085
|
+
finished = True
|
1086
|
+
return
|
1087
|
+
|
1088
|
+
instance_id = self.name + "_rest_custom_exec_synchronous_" + self.log.get_unique_id()
|
1089
|
+
instance_config = {
|
1090
|
+
'REQUEST': {
|
1091
|
+
'DATA': {
|
1092
|
+
'CODE': b64code,
|
1093
|
+
},
|
1094
|
+
'TIMESTAMP': self.log.time_to_str()
|
1095
|
+
},
|
1096
|
+
'RESULT_KEY': 'REST_EXECUTION_RESULT',
|
1097
|
+
'ERROR_KEY': 'REST_EXECUTION_ERROR',
|
1098
|
+
**instance_config
|
1099
|
+
}
|
1100
|
+
|
1101
|
+
prop_config = self.__get_proposed_pipeline_config()
|
1102
|
+
if prop_config['TYPE'] == 'Void':
|
1103
|
+
instance_config['ALLOW_EMPTY_INPUTS'] = True
|
1104
|
+
instance_config['RUN_WITHOUT_IMAGE'] = True
|
1105
|
+
|
1106
|
+
self.create_plugin_instance(
|
1107
|
+
signature='REST_CUSTOM_EXEC_01',
|
1108
|
+
instance_id=instance_id,
|
1109
|
+
config=instance_config,
|
1110
|
+
on_data=on_data
|
1111
|
+
)
|
1112
|
+
|
1113
|
+
self.deploy()
|
1114
|
+
|
1115
|
+
start_time = time()
|
1116
|
+
while not finished and time() - start_time < timeout:
|
1117
|
+
sleep(0.1)
|
1118
|
+
|
1119
|
+
return result, error
|
1120
|
+
|
1121
|
+
def close(self, wait_confirmation=True, timeout=10):
|
1122
|
+
"""
|
1123
|
+
Close the pipeline, stopping all the instances associated with it.
|
1124
|
+
"""
|
1125
|
+
|
1126
|
+
transactions = self._close(timeout=timeout)
|
1127
|
+
|
1128
|
+
if wait_confirmation:
|
1129
|
+
self.session.wait_for_transactions(transactions)
|
1130
|
+
else:
|
1131
|
+
return transactions
|
1132
|
+
return
|
1133
|
+
|
1134
|
+
def P(self, *args, **kwargs):
|
1135
|
+
"""
|
1136
|
+
Print info to stdout.
|
1137
|
+
"""
|
1138
|
+
return self.log.P(*args, **kwargs)
|
1139
|
+
|
1140
|
+
def D(self, *args, **kwargs):
|
1141
|
+
"""
|
1142
|
+
Call the `Logger.D` method.
|
1143
|
+
If using the default Logger, this call will print debug info to stdout if `silent` is set to `False`.
|
1144
|
+
The logger object is passed from the Session object to the Pipeline object when creating
|
1145
|
+
it with `create_pipeline` or `attach_to_pipeline`.
|
1146
|
+
"""
|
1147
|
+
return self.session.D(*args, **kwargs)
|
1148
|
+
|
1149
|
+
def attach_to_plugin_instance(self, signature, instance_id, on_data=None, on_notification=None) -> Instance:
|
1150
|
+
"""
|
1151
|
+
Attach to an existing instance on this pipeline.
|
1152
|
+
This method is useful when one wishes to attach an
|
1153
|
+
`on_data` and `on_notification` callbacks to said instance.
|
1154
|
+
|
1155
|
+
Parameters
|
1156
|
+
----------
|
1157
|
+
signature : str
|
1158
|
+
name of the plugin signature.
|
1159
|
+
instance_id : str
|
1160
|
+
name of the instance.
|
1161
|
+
on_data : Callable[[Pipeline, dict], None], optional
|
1162
|
+
Callback that handles messages received from this instance. As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
1163
|
+
Defaults to None.
|
1164
|
+
on_notification : Callable[[Pipeline, dict], None], optional
|
1165
|
+
Callback that handles notifications received from this instance. As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
1166
|
+
Defaults to None.
|
1167
|
+
|
1168
|
+
Returns
|
1169
|
+
-------
|
1170
|
+
instance : Instance
|
1171
|
+
An `Instance` object.
|
1172
|
+
|
1173
|
+
Raises
|
1174
|
+
------
|
1175
|
+
Exception
|
1176
|
+
the pipeline does not contain plugins with a given signature.
|
1177
|
+
Exception
|
1178
|
+
The pipeline does not contain the desired instance.
|
1179
|
+
"""
|
1180
|
+
|
1181
|
+
# search for the instance in the list
|
1182
|
+
plugin_template = None
|
1183
|
+
if isinstance(signature, str):
|
1184
|
+
str_signature = signature.upper()
|
1185
|
+
else:
|
1186
|
+
plugin_template = signature
|
1187
|
+
str_signature = plugin_template.signature.upper()
|
1188
|
+
found_instance = None
|
1189
|
+
for instance in self.lst_plugin_instances:
|
1190
|
+
if instance.instance_id == instance_id and instance.signature == str_signature.upper():
|
1191
|
+
found_instance = instance
|
1192
|
+
break
|
1193
|
+
|
1194
|
+
if found_instance is None:
|
1195
|
+
raise Exception(f"Unable to attach to instance. Instance <{str_signature}/{instance_id}> does not exist")
|
1196
|
+
|
1197
|
+
# add the callbacks to the session
|
1198
|
+
if on_data is not None:
|
1199
|
+
found_instance._add_on_data_callback(on_data)
|
1200
|
+
|
1201
|
+
if on_notification is not None:
|
1202
|
+
found_instance._add_on_notification_callback(on_notification)
|
1203
|
+
|
1204
|
+
if plugin_template is not None:
|
1205
|
+
found_instance.convert_to_specialized_class(plugin_template)
|
1206
|
+
|
1207
|
+
return found_instance
|
1208
|
+
|
1209
|
+
def attach_to_custom_plugin_instance(self, instance_id, on_data=None, on_notification=None) -> Instance:
|
1210
|
+
"""
|
1211
|
+
Attach to an existing custom execution instance on this pipeline.
|
1212
|
+
This method is useful when one wishes to attach an
|
1213
|
+
`on_data` and `on_notification` callbacks to said instance.
|
1214
|
+
|
1215
|
+
Parameters
|
1216
|
+
----------
|
1217
|
+
instance_id : str
|
1218
|
+
name of the instance.
|
1219
|
+
on_data : Callable[[Pipeline, str, str, dict], None], optional
|
1220
|
+
Callback that handles messages received from this instance. As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
1221
|
+
Defaults to None.
|
1222
|
+
on_notification : Callable[[Pipeline, dict], None], optional
|
1223
|
+
Callback that handles notifications received from this instance. As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
1224
|
+
Defaults to None.
|
1225
|
+
|
1226
|
+
Returns
|
1227
|
+
-------
|
1228
|
+
str
|
1229
|
+
An identifier for this instance, useful for stopping an instance.
|
1230
|
+
|
1231
|
+
Raises
|
1232
|
+
------
|
1233
|
+
Exception
|
1234
|
+
the pipeline does not contain any custom plugin.
|
1235
|
+
Exception
|
1236
|
+
The pipeline does not contain the desired instance.
|
1237
|
+
"""
|
1238
|
+
|
1239
|
+
def callback(pipeline, data): return self.__custom_exec_on_data(pipeline, instance_id, on_data, data)
|
1240
|
+
callback = callback if on_data is not None else None
|
1241
|
+
|
1242
|
+
return self.attach_to_plugin_instance("CUSTOM_EXEC_01", instance_id, callback, on_notification)
|
1243
|
+
|
1244
|
+
def detach_from_instance(self, instance: Instance):
|
1245
|
+
# search for the instance in the list
|
1246
|
+
if instance is None:
|
1247
|
+
raise Exception("The provided instance is None. Please provide a valid instance")
|
1248
|
+
|
1249
|
+
instance._reset_on_data_callback()
|
1250
|
+
instance._reset_on_notification_callback()
|
1251
|
+
return
|
1252
|
+
|
1253
|
+
def update_acquisition_parameters(self, config={}, **kwargs):
|
1254
|
+
"""
|
1255
|
+
Update the acquisition parameters of this pipeline.
|
1256
|
+
Parameters can be passed either in the `config` dict, or as `kwargs`.
|
1257
|
+
|
1258
|
+
Parameters
|
1259
|
+
----------
|
1260
|
+
config : dict, optional
|
1261
|
+
The new configuration of the acquisition source, by default {}
|
1262
|
+
"""
|
1263
|
+
if self.__staged_config is not None:
|
1264
|
+
raise ValueError("Pipeline configuration has already been staged, waiting for confirmation from Execution Engine")
|
1265
|
+
|
1266
|
+
if self.proposed_config is None:
|
1267
|
+
self.proposed_config = {}
|
1268
|
+
|
1269
|
+
self.proposed_config = {**self.proposed_config, **config, **{k.upper(): v for k, v in kwargs.items()}}
|
1270
|
+
self.proposed_config = self.__pop_ignored_keys_from_config(self.proposed_config)
|
1271
|
+
|
1272
|
+
for k, v in self.config.items():
|
1273
|
+
if k in self.proposed_config:
|
1274
|
+
if self.proposed_config[k] == v:
|
1275
|
+
del self.proposed_config[k]
|
1276
|
+
|
1277
|
+
if len(self.proposed_config) == 0:
|
1278
|
+
self.proposed_config = None
|
1279
|
+
|
1280
|
+
return
|
1281
|
+
|
1282
|
+
def send_pipeline_command(self, command, payload=None, command_params=None, wait_confirmation=True, timeout=10) -> list[Transaction]:
|
1283
|
+
"""
|
1284
|
+
Send a pipeline command to the Naeural edge node.
|
1285
|
+
This command can block until the command is confirmed by the Naeural edge node.
|
1286
|
+
|
1287
|
+
Example:
|
1288
|
+
--------
|
1289
|
+
```python
|
1290
|
+
pipeline.send_pipeline_command('START', wait_confirmation=True)
|
1291
|
+
|
1292
|
+
transactions_p1 = pipeline1.send_pipeline_command('START', wait_confirmation=False)
|
1293
|
+
transactions_p2 = pipeline2.send_pipeline_command('START', wait_confirmation=False)
|
1294
|
+
# wait for both commands to be confirmed, but after both commands are sent
|
1295
|
+
session.wait_for_transactions(transactions_p1 + transactions_p2)
|
1296
|
+
```
|
1297
|
+
|
1298
|
+
Parameters
|
1299
|
+
----------
|
1300
|
+
command : str
|
1301
|
+
The name of the command.
|
1302
|
+
payload : dict, optional
|
1303
|
+
The payload of the command, by default {}
|
1304
|
+
command_params : dict, optional
|
1305
|
+
The parameters of the command, by default {}
|
1306
|
+
wait_confirmation : bool, optional
|
1307
|
+
Whether to wait for the confirmation of the command, by default False
|
1308
|
+
timeout : int, optional
|
1309
|
+
The timeout for the transaction, by default 10
|
1310
|
+
|
1311
|
+
Returns
|
1312
|
+
-------
|
1313
|
+
list[Transaction] | None
|
1314
|
+
The list of transactions generated, or None if `wait_confirmation` is False.
|
1315
|
+
"""
|
1316
|
+
transactions = self.__register_transaction_for_pipeline_command(timeout=timeout)
|
1317
|
+
|
1318
|
+
self.__was_last_operation_successful = None
|
1319
|
+
|
1320
|
+
self.session._send_command_pipeline_command(
|
1321
|
+
worker=self.node_addr,
|
1322
|
+
pipeline_name=self.name,
|
1323
|
+
command=command,
|
1324
|
+
payload=payload,
|
1325
|
+
command_params=command_params,
|
1326
|
+
)
|
1327
|
+
|
1328
|
+
if wait_confirmation:
|
1329
|
+
self.session.wait_for_transactions(transactions)
|
1330
|
+
else:
|
1331
|
+
return transactions
|
1332
|
+
return
|
1333
|
+
|
1334
|
+
def create_or_attach_to_plugin_instance(self, *, signature, instance_id, config={}, on_data=None, on_notification=None, **kwargs) -> Instance:
|
1335
|
+
"""
|
1336
|
+
Create a new instance of a desired plugin, with a given configuration, or attach to an existing instance.
|
1337
|
+
|
1338
|
+
Parameters
|
1339
|
+
----------
|
1340
|
+
signature : str
|
1341
|
+
The name of the plugin signature. This is the name of the desired overall functionality.
|
1342
|
+
instance_id : str
|
1343
|
+
The name of the instance. There can be multiple instances of the same plugin, mostly with different parameters
|
1344
|
+
config : dict, optional
|
1345
|
+
parameters used to customize the functionality. One can change the AI engine used for object detection,
|
1346
|
+
or finetune alerter parameters to better fit a camera located in a low light environment.
|
1347
|
+
Defaults to {}
|
1348
|
+
on_data : Callable[[Pipeline, dict], None], optional
|
1349
|
+
Callback that handles messages received from this instance.
|
1350
|
+
As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
1351
|
+
Defaults to None
|
1352
|
+
on_notification : Callable[[Pipeline, dict], None], optional
|
1353
|
+
Callback that handles notifications received from this instance.
|
1354
|
+
As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
1355
|
+
Defaults to None
|
1356
|
+
|
1357
|
+
Returns
|
1358
|
+
-------
|
1359
|
+
instance : Instance
|
1360
|
+
An `Instance` object.
|
1361
|
+
"""
|
1362
|
+
try:
|
1363
|
+
instance = self.attach_to_plugin_instance(signature, instance_id, on_data, on_notification)
|
1364
|
+
instance.update_instance_config(config, **kwargs)
|
1365
|
+
except Exception as e:
|
1366
|
+
instance = self.create_plugin_instance(
|
1367
|
+
signature=signature,
|
1368
|
+
instance_id=instance_id,
|
1369
|
+
config=config,
|
1370
|
+
on_data=on_data,
|
1371
|
+
on_notification=on_notification,
|
1372
|
+
**kwargs
|
1373
|
+
)
|
1374
|
+
return instance
|
1375
|
+
|
1376
|
+
def create_or_attach_to_custom_plugin_instance(self, *, instance_id, custom_code, config={}, on_data=None, on_notification=None, **kwargs) -> Instance:
|
1377
|
+
"""
|
1378
|
+
Create a new instance of a desired plugin, with a given configuration, or attach to an existing instance.
|
1379
|
+
|
1380
|
+
Parameters
|
1381
|
+
----------
|
1382
|
+
signature : str
|
1383
|
+
The name of the plugin signature. This is the name of the desired overall functionality.
|
1384
|
+
instance_id : str
|
1385
|
+
The name of the instance. There can be multiple instances of the same plugin, mostly with different parameters
|
1386
|
+
config : dict, optional
|
1387
|
+
parameters used to customize the functionality. One can change the AI engine used for object detection,
|
1388
|
+
or finetune alerter parameters to better fit a camera located in a low light environment.
|
1389
|
+
Defaults to {}
|
1390
|
+
on_data : Callable[[Pipeline, dict], None], optional
|
1391
|
+
Callback that handles messages received from this instance.
|
1392
|
+
As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
1393
|
+
Defaults to None
|
1394
|
+
on_notification : Callable[[Pipeline, dict], None], optional
|
1395
|
+
Callback that handles notifications received from this instance.
|
1396
|
+
As arguments, it has a reference to this Pipeline object, along with the payload itself.
|
1397
|
+
Defaults to None
|
1398
|
+
|
1399
|
+
Returns
|
1400
|
+
-------
|
1401
|
+
instance : Instance
|
1402
|
+
An `Instance` object.
|
1403
|
+
"""
|
1404
|
+
|
1405
|
+
try:
|
1406
|
+
instance = self.attach_to_custom_plugin_instance(instance_id, on_data, on_notification)
|
1407
|
+
instance.update_instance_config(config, **kwargs)
|
1408
|
+
except:
|
1409
|
+
instance = self.create_custom_plugin_instance(
|
1410
|
+
instance_id=instance_id,
|
1411
|
+
custom_code=custom_code,
|
1412
|
+
config=config,
|
1413
|
+
on_data=on_data,
|
1414
|
+
on_notification=on_notification,
|
1415
|
+
**kwargs
|
1416
|
+
)
|
1417
|
+
return instance
|
1418
|
+
|
1419
|
+
def _sync_configuration_with_remote(self, config={}):
|
1420
|
+
config.pop('NAME', None)
|
1421
|
+
config.pop('TYPE', None)
|
1422
|
+
plugins = config.pop('PLUGINS', {})
|
1423
|
+
|
1424
|
+
self.config = {**self.config, **config}
|
1425
|
+
|
1426
|
+
active_plugins = []
|
1427
|
+
for dct_signature_instances in plugins:
|
1428
|
+
signature = dct_signature_instances['SIGNATURE']
|
1429
|
+
instances = dct_signature_instances['INSTANCES']
|
1430
|
+
for dct_instance in instances:
|
1431
|
+
instance_id = dct_instance.pop('INSTANCE_ID')
|
1432
|
+
active_plugins.append((signature, instance_id))
|
1433
|
+
instance_object = self.__get_instance_object(signature, instance_id)
|
1434
|
+
if instance_object is None:
|
1435
|
+
self.__init_instance(signature, instance_id, dct_instance, None, None, is_attached=True)
|
1436
|
+
else:
|
1437
|
+
instance_object._sync_configuration_with_remote(dct_instance)
|
1438
|
+
# end for dct_instance
|
1439
|
+
# end for dct_signature_instances
|
1440
|
+
|
1441
|
+
for instance in self.lst_plugin_instances:
|
1442
|
+
if (instance.signature, instance.instance_id) not in active_plugins:
|
1443
|
+
self.__remove_plugin_instance(instance)
|
1444
|
+
# end for instance
|
1445
|
+
return
|
1446
|
+
|
1447
|
+
def update_full_configuration(self, config={}):
|
1448
|
+
"""
|
1449
|
+
Update the full configuration of this pipeline.
|
1450
|
+
Parameters are passed in the `config` dict.
|
1451
|
+
We do not support kwargs yet because it makes it difficult to check priority of dictionary, merging values, etc.
|
1452
|
+
|
1453
|
+
Parameters
|
1454
|
+
----------
|
1455
|
+
config : dict, optional
|
1456
|
+
The new configuration of the pipeline, by default {}
|
1457
|
+
"""
|
1458
|
+
if self.__staged_config is not None:
|
1459
|
+
raise ValueError("Pipeline configuration has already been staged, waiting for confirmation from Execution Engine")
|
1460
|
+
|
1461
|
+
# pop the illegal to modify keys
|
1462
|
+
config.pop('NAME', None)
|
1463
|
+
config.pop('TYPE', None)
|
1464
|
+
plugins = config.pop('PLUGINS', None)
|
1465
|
+
|
1466
|
+
self.update_acquisition_parameters(config)
|
1467
|
+
|
1468
|
+
if plugins is None:
|
1469
|
+
return
|
1470
|
+
|
1471
|
+
new_plugins = []
|
1472
|
+
for dct_signature_instances in plugins:
|
1473
|
+
signature = dct_signature_instances['SIGNATURE']
|
1474
|
+
instances = dct_signature_instances['INSTANCES']
|
1475
|
+
for dct_instance in instances:
|
1476
|
+
instance_id = dct_instance.pop('INSTANCE_ID')
|
1477
|
+
new_plugins.append((signature, instance_id))
|
1478
|
+
instance_object = self.__get_instance_object(signature, instance_id)
|
1479
|
+
|
1480
|
+
if instance_object is None:
|
1481
|
+
self.create_plugin_instance(signature=signature, instance_id=instance_id, config=dct_instance)
|
1482
|
+
else:
|
1483
|
+
instance_object.update_instance_config(dct_instance)
|
1484
|
+
# end for dct_instance
|
1485
|
+
# end for dct_signature_instances
|
1486
|
+
|
1487
|
+
# now check if we have to remove any instances
|
1488
|
+
for instance in self.lst_plugin_instances:
|
1489
|
+
if (instance.signature, instance.instance_id) not in new_plugins:
|
1490
|
+
self.remove_plugin_instance(instance)
|
1491
|
+
# end for instance
|
1492
|
+
return
|
1493
|
+
|
1494
|
+
@property
|
1495
|
+
def node_id(self):
|
1496
|
+
"""
|
1497
|
+
Return the node id of the pipeline.
|
1498
|
+
"""
|
1499
|
+
return self.session.get_node_name(self.node_addr)
|