naeural-client 2.6.14__py3-none-any.whl → 2.6.16__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/_ver.py +1 -1
- naeural_client/base/generic_session.py +251 -75
- naeural_client/bc/base.py +1 -1
- naeural_client/const/apps.py +1 -0
- naeural_client/const/base.py +3 -0
- naeural_client/default/session/mqtt_session.py +19 -4
- naeural_client-2.6.16.dist-info/METADATA +279 -0
- {naeural_client-2.6.14.dist-info → naeural_client-2.6.16.dist-info}/RECORD +11 -11
- naeural_client-2.6.14.dist-info/METADATA +0 -358
- {naeural_client-2.6.14.dist-info → naeural_client-2.6.16.dist-info}/WHEEL +0 -0
- {naeural_client-2.6.14.dist-info → naeural_client-2.6.16.dist-info}/entry_points.txt +0 -0
- {naeural_client-2.6.14.dist-info → naeural_client-2.6.16.dist-info}/licenses/LICENSE +0 -0
naeural_client/_ver.py
CHANGED
@@ -23,7 +23,7 @@ from ..bc import DefaultBlockEngine, _DotDict
|
|
23
23
|
from ..const import (
|
24
24
|
COMMANDS, ENVIRONMENT, HB, PAYLOAD_DATA, STATUS_TYPE,
|
25
25
|
PLUGIN_SIGNATURES, DEFAULT_PIPELINES,
|
26
|
-
BLOCKCHAIN_CONFIG, SESSION_CT
|
26
|
+
BLOCKCHAIN_CONFIG, SESSION_CT, NET_CONFIG
|
27
27
|
)
|
28
28
|
from ..const import comms as comm_ct
|
29
29
|
from ..io_formatter import IOFormatterWrapper
|
@@ -366,24 +366,33 @@ class GenericSession(BaseDecentrAIObject):
|
|
366
366
|
"""
|
367
367
|
# check if payload is encrypted
|
368
368
|
if dict_msg.get(PAYLOAD_DATA.EE_IS_ENCRYPTED, False):
|
369
|
-
|
370
|
-
|
369
|
+
destination = dict_msg.get(PAYLOAD_DATA.EE_DESTINATION, [])
|
370
|
+
if not isinstance(destination, list):
|
371
|
+
destination = [destination]
|
372
|
+
if self.bc_engine.contains_current_address(destination):
|
371
373
|
|
372
|
-
|
374
|
+
encrypted_data = dict_msg.get(PAYLOAD_DATA.EE_ENCRYPTED_DATA, None)
|
375
|
+
sender_addr = dict_msg.get(comm_ct.COMM_SEND_MESSAGE.K_SENDER_ADDR, None)
|
373
376
|
|
374
|
-
|
375
|
-
self.D("Cannot decrypt message, dropping..\n{}".format(str_data), verbosity=2)
|
376
|
-
return None
|
377
|
+
str_data = self.bc_engine.decrypt(encrypted_data, sender_addr)
|
377
378
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
self.P("Error while decrypting message: {}".format(e), color='r', verbosity=1)
|
382
|
-
self.D("Message: {}".format(str_data), verbosity=2)
|
383
|
-
return None
|
379
|
+
if str_data is None:
|
380
|
+
self.D("Cannot decrypt message, dropping..\n{}".format(str_data), verbosity=2)
|
381
|
+
return None
|
384
382
|
|
385
|
-
|
386
|
-
|
383
|
+
try:
|
384
|
+
dict_data = json.loads(str_data)
|
385
|
+
except Exception as e:
|
386
|
+
self.P("Error while decrypting message: {}".format(e), color='r', verbosity=1)
|
387
|
+
self.D("Message: {}".format(str_data), verbosity=2)
|
388
|
+
return None
|
389
|
+
|
390
|
+
dict_msg = {**dict_data, **dict_msg}
|
391
|
+
dict_msg.pop(PAYLOAD_DATA.EE_ENCRYPTED_DATA, None)
|
392
|
+
else:
|
393
|
+
payload_path = dict_msg.get(PAYLOAD_DATA.EE_PAYLOAD_PATH, None)
|
394
|
+
self.D(f"Message {payload_path} is encrypted but not for this address.", verbosity=2)
|
395
|
+
# endif message for us
|
387
396
|
# end if encrypted
|
388
397
|
|
389
398
|
formatter = self.formatter_wrapper \
|
@@ -498,6 +507,56 @@ class GenericSession(BaseDecentrAIObject):
|
|
498
507
|
self._dct_can_send_to_node[node_addr] = not node_secured or client_is_allowed or self.bc_engine.address == node_addr
|
499
508
|
return
|
500
509
|
|
510
|
+
def send_encrypted_payload(self, node_addr, payload, **kwargs):
|
511
|
+
"""
|
512
|
+
TODO: move in BlockChainEngine
|
513
|
+
Send an encrypted payload to a node.
|
514
|
+
|
515
|
+
Parameters
|
516
|
+
----------
|
517
|
+
node_addr : str
|
518
|
+
The address of the edge node that will receive the message.
|
519
|
+
payload : dict
|
520
|
+
The payload dict to be sent.
|
521
|
+
**kwargs : dict
|
522
|
+
Additional data to be sent to __prepare_message.
|
523
|
+
"""
|
524
|
+
msg_to_send = self.__prepare_message(
|
525
|
+
msg_data=payload,
|
526
|
+
encrypt_message=True,
|
527
|
+
destination=node_addr,
|
528
|
+
**kwargs
|
529
|
+
)
|
530
|
+
self.bc_engine.sign(msg_to_send)
|
531
|
+
if not self.silent:
|
532
|
+
self.P(f'Sending encrypted payload to <{node_addr}>', color='y')
|
533
|
+
self._send_payload(msg_to_send)
|
534
|
+
return
|
535
|
+
|
536
|
+
def __request_pipelines_from_net_config_monitor(self, node_addr):
|
537
|
+
"""
|
538
|
+
Request the pipelines for a node from the net-config monitor plugin instance.
|
539
|
+
Parameters
|
540
|
+
----------
|
541
|
+
node_addr : str
|
542
|
+
The address of the edge node that sent the message.
|
543
|
+
|
544
|
+
"""
|
545
|
+
payload = {
|
546
|
+
NET_CONFIG.NET_CONFIG_DATA: {
|
547
|
+
NET_CONFIG.OPERATION: NET_CONFIG.REQUEST_COMMAND,
|
548
|
+
NET_CONFIG.DESTINATION: node_addr,
|
549
|
+
}
|
550
|
+
}
|
551
|
+
additional_data = {
|
552
|
+
PAYLOAD_DATA.EE_PAYLOAD_PATH: [self.bc_engine.address, DEFAULT_PIPELINES.ADMIN_PIPELINE, PLUGIN_SIGNATURES.NET_CONFIG_MONITOR, None]
|
553
|
+
}
|
554
|
+
self.send_encrypted_payload(
|
555
|
+
node_addr=node_addr, payload=payload,
|
556
|
+
additional_data=additional_data
|
557
|
+
)
|
558
|
+
return
|
559
|
+
|
501
560
|
def __track_allowed_node_by_netmon(self, node_addr, dict_msg):
|
502
561
|
"""
|
503
562
|
Track if this session is allowed to send messages to node using net-mon data
|
@@ -505,7 +564,7 @@ class GenericSession(BaseDecentrAIObject):
|
|
505
564
|
Parameters
|
506
565
|
----------
|
507
566
|
node_addr : str
|
508
|
-
The address of the
|
567
|
+
The address of the edge node that sent the message.
|
509
568
|
|
510
569
|
dict_msg : dict
|
511
570
|
The message received from the communication server as a heartbeat of the object from netconfig
|
@@ -514,8 +573,12 @@ class GenericSession(BaseDecentrAIObject):
|
|
514
573
|
node_secured = dict_msg.get(PAYLOAD_DATA.NETMON_NODE_SECURED, False)
|
515
574
|
|
516
575
|
client_is_allowed = self.bc_engine.contains_current_address(node_whitelist)
|
576
|
+
can_send = not node_secured or client_is_allowed or self.bc_engine.address == node_addr
|
577
|
+
if node_addr not in self._dct_can_send_to_node and can_send:
|
578
|
+
self.__request_pipelines_from_net_config_monitor(node_addr)
|
579
|
+
# endif node seen for the first time
|
517
580
|
|
518
|
-
self._dct_can_send_to_node[node_addr] =
|
581
|
+
self._dct_can_send_to_node[node_addr] = can_send
|
519
582
|
return
|
520
583
|
|
521
584
|
|
@@ -523,6 +586,9 @@ class GenericSession(BaseDecentrAIObject):
|
|
523
586
|
"""
|
524
587
|
Given a list of pipeline configurations, create or update the pipelines for a node.
|
525
588
|
"""
|
589
|
+
new_pipelines = []
|
590
|
+
if node_addr not in self._dct_online_nodes_pipelines:
|
591
|
+
self._dct_online_nodes_pipelines[node_addr] = {}
|
526
592
|
for config in pipelines:
|
527
593
|
pipeline_name = config[PAYLOAD_DATA.NAME]
|
528
594
|
pipeline: Pipeline = self._dct_online_nodes_pipelines[node_addr].get(pipeline_name, None)
|
@@ -531,7 +597,8 @@ class GenericSession(BaseDecentrAIObject):
|
|
531
597
|
else:
|
532
598
|
self._dct_online_nodes_pipelines[node_addr][pipeline_name] = self.__create_pipeline_from_config(
|
533
599
|
node_addr, config)
|
534
|
-
|
600
|
+
new_pipelines.append(self._dct_online_nodes_pipelines[node_addr][pipeline_name])
|
601
|
+
return new_pipelines
|
535
602
|
|
536
603
|
def __on_heartbeat(self, dict_msg: dict, msg_node_addr, msg_pipeline, msg_signature, msg_instance):
|
537
604
|
"""
|
@@ -571,14 +638,11 @@ class GenericSession(BaseDecentrAIObject):
|
|
571
638
|
# as the protocol should NOT send a heartbeat with active configs to
|
572
639
|
# the entire network, only to the interested parties via net-config
|
573
640
|
|
574
|
-
# default action
|
575
|
-
if msg_node_addr not in self._dct_online_nodes_pipelines:
|
576
|
-
# this is ok here although we dont get the pipelines from the heartbeat
|
577
|
-
self._dct_online_nodes_pipelines[msg_node_addr] = {}
|
578
|
-
|
579
641
|
if len(msg_active_configs) > 0:
|
580
642
|
# this is for legacy and custom implementation where heartbeats still contain
|
581
643
|
# the pipeline configuration.
|
644
|
+
pipeline_names = [x.get(PAYLOAD_DATA.NAME, None) for x in msg_active_configs]
|
645
|
+
self.D(f'<HB>Received pipelines from <{msg_node_addr}>:{pipeline_names}', color='y')
|
582
646
|
self.__process_node_pipelines(msg_node_addr, msg_active_configs)
|
583
647
|
|
584
648
|
# TODO: move this call in `__on_message_default_callback`
|
@@ -699,8 +763,7 @@ class GenericSession(BaseDecentrAIObject):
|
|
699
763
|
# end if current_network is valid
|
700
764
|
# end if NET_MON_01
|
701
765
|
return
|
702
|
-
|
703
|
-
|
766
|
+
|
704
767
|
def __maybe_process_net_config(
|
705
768
|
self,
|
706
769
|
dict_msg: dict,
|
@@ -708,7 +771,39 @@ class GenericSession(BaseDecentrAIObject):
|
|
708
771
|
msg_signature : str,
|
709
772
|
sender_addr: str,
|
710
773
|
):
|
711
|
-
|
774
|
+
# TODO: bleo if session is in debug mode then for each net-config show what pipelines have
|
775
|
+
# been received
|
776
|
+
REQUIRED_PIPELINE = DEFAULT_PIPELINES.ADMIN_PIPELINE
|
777
|
+
REQUIRED_SIGNATURE = PLUGIN_SIGNATURES.NET_CONFIG_MONITOR
|
778
|
+
if msg_pipeline.lower() == REQUIRED_PIPELINE.lower() and msg_signature.upper() == REQUIRED_SIGNATURE.upper():
|
779
|
+
# extract data
|
780
|
+
sender_addr = dict_msg.get(PAYLOAD_DATA.EE_SENDER, None)
|
781
|
+
receiver = dict_msg.get(PAYLOAD_DATA.EE_DESTINATION, None)
|
782
|
+
if not isinstance(receiver, list):
|
783
|
+
receiver = [receiver]
|
784
|
+
path = dict_msg.get(PAYLOAD_DATA.EE_PAYLOAD_PATH, [None, None, None, None])
|
785
|
+
ee_id = dict_msg.get(PAYLOAD_DATA.EE_ID, None)
|
786
|
+
|
787
|
+
# check if I am allowed to see this payload
|
788
|
+
if not self.bc_engine.contains_current_address(receiver):
|
789
|
+
self.P(f"Received net-config from <{sender_addr}> `{ee_id}` but I am not in the receiver list: {receiver}", color='d')
|
790
|
+
return
|
791
|
+
|
792
|
+
# decrypt payload
|
793
|
+
is_encrypted = dict_msg.get(PAYLOAD_DATA.EE_IS_ENCRYPTED, False)
|
794
|
+
if not is_encrypted:
|
795
|
+
self.P(f"Received net-config from <{sender_addr}> `{ee_id}` but it is not encrypted", color='r')
|
796
|
+
return
|
797
|
+
net_config_data = dict_msg.get(NET_CONFIG.NET_CONFIG_DATA, {})
|
798
|
+
received_pipelines = net_config_data.get('PIPELINES', [])
|
799
|
+
self.D(f"Received {len(received_pipelines)} pipelines from <{sender_addr}>")
|
800
|
+
new_pipelines = self.__process_node_pipelines(sender_addr, received_pipelines)
|
801
|
+
pipeline_names = [x.name for x in new_pipelines]
|
802
|
+
self.P(f'<NETCFG>Received pipelines from <{sender_addr}>:{pipeline_names}', color='y')
|
803
|
+
self.D(f'[DEBUG][NETCFG]Received pipelines from <{sender_addr}>:\n{new_pipelines}', color='y')
|
804
|
+
|
805
|
+
# load with same method as a hb
|
806
|
+
return True
|
712
807
|
|
713
808
|
|
714
809
|
# TODO: maybe convert dict_msg to Payload object
|
@@ -908,15 +1003,38 @@ class GenericSession(BaseDecentrAIObject):
|
|
908
1003
|
"""
|
909
1004
|
raise NotImplementedError
|
910
1005
|
|
911
|
-
def
|
1006
|
+
def _send_raw_message(self, to, msg, communicator='default'):
|
912
1007
|
"""
|
913
|
-
Send a
|
1008
|
+
Send a message to a node.
|
914
1009
|
|
915
1010
|
Parameters
|
916
1011
|
----------
|
917
1012
|
to : str
|
918
|
-
The name of the Naeural Edge Protocol edge node that will receive the
|
919
|
-
|
1013
|
+
The name of the Naeural Edge Protocol edge node that will receive the message.
|
1014
|
+
msg : dict or str
|
1015
|
+
The message to send.
|
1016
|
+
"""
|
1017
|
+
raise NotImplementedError
|
1018
|
+
|
1019
|
+
def _send_command(self, to, command):
|
1020
|
+
"""
|
1021
|
+
Send a command to a node.
|
1022
|
+
|
1023
|
+
Parametersc
|
1024
|
+
----------
|
1025
|
+
to : str
|
1026
|
+
The name of the Naeural Edge Protocol edge node that will receive the command.
|
1027
|
+
command : str or dict
|
1028
|
+
The command to send.
|
1029
|
+
"""
|
1030
|
+
raise NotImplementedError
|
1031
|
+
|
1032
|
+
def _send_payload(self, payload):
|
1033
|
+
"""
|
1034
|
+
Send a payload to a node.
|
1035
|
+
Parameters
|
1036
|
+
----------
|
1037
|
+
payload : dict or str
|
920
1038
|
The payload to send.
|
921
1039
|
"""
|
922
1040
|
raise NotImplementedError
|
@@ -1241,6 +1359,68 @@ class GenericSession(BaseDecentrAIObject):
|
|
1241
1359
|
result = aliases.get(node, None)
|
1242
1360
|
return result
|
1243
1361
|
|
1362
|
+
def __prepare_message(
|
1363
|
+
self, msg_data, encrypt_message: bool = True,
|
1364
|
+
destination: str = None, destination_id: str = None,
|
1365
|
+
session_id: str = None, additional_data: dict = None
|
1366
|
+
):
|
1367
|
+
"""
|
1368
|
+
Prepare and maybe encrypt a message for sending.
|
1369
|
+
Parameters
|
1370
|
+
----------
|
1371
|
+
msg_data : dict
|
1372
|
+
The message to send.
|
1373
|
+
encrypt_message : bool
|
1374
|
+
If True, will encrypt the message.
|
1375
|
+
destination : str, optional
|
1376
|
+
The destination address, by default None
|
1377
|
+
destination_id : str, optional
|
1378
|
+
The destination id, by default None
|
1379
|
+
additional_data : dict, optional
|
1380
|
+
Additional data to send, by default None
|
1381
|
+
This has to be dict!
|
1382
|
+
|
1383
|
+
Returns
|
1384
|
+
-------
|
1385
|
+
msg_to_send : dict
|
1386
|
+
The message to send.
|
1387
|
+
"""
|
1388
|
+
if destination is None and destination_id is not None:
|
1389
|
+
# Initial code `str_enc_data = self.bc_engine.encrypt(str_data, destination_id)` could not work under any
|
1390
|
+
# circumstances due to the fact that encrypt requires the public key of the receiver not the alias
|
1391
|
+
# of the receiver. The code below is a workaround to encrypt the message
|
1392
|
+
# TODO: furthermore the code will be migrated to the use of the address of the worker
|
1393
|
+
destination = self.get_addr_by_name(destination_id)
|
1394
|
+
assert destination is not None, f"Unknown address for id: {destination_id}"
|
1395
|
+
# endif only destination_id provided
|
1396
|
+
|
1397
|
+
# This part is duplicated with the creation of payloads
|
1398
|
+
if encrypt_message and destination is not None:
|
1399
|
+
str_data = json.dumps(msg_data)
|
1400
|
+
str_enc_data = self.bc_engine.encrypt(str_data, destination)
|
1401
|
+
msg_data = {
|
1402
|
+
comm_ct.COMM_SEND_MESSAGE.K_EE_IS_ENCRYPTED: True,
|
1403
|
+
comm_ct.COMM_SEND_MESSAGE.K_EE_ENCRYPTED_DATA: str_enc_data,
|
1404
|
+
}
|
1405
|
+
else:
|
1406
|
+
msg_data[comm_ct.COMM_SEND_MESSAGE.K_EE_IS_ENCRYPTED] = False
|
1407
|
+
if encrypt_message:
|
1408
|
+
msg_data[comm_ct.COMM_SEND_MESSAGE.K_EE_ENCRYPTED_DATA] = "Error! No receiver address found!"
|
1409
|
+
# endif encrypt_message and destination available
|
1410
|
+
msg_to_send = {
|
1411
|
+
**msg_data,
|
1412
|
+
PAYLOAD_DATA.EE_DESTINATION: destination,
|
1413
|
+
comm_ct.COMM_SEND_MESSAGE.K_EE_ID: destination_id,
|
1414
|
+
comm_ct.COMM_SEND_MESSAGE.K_SESSION_ID: session_id or self.name,
|
1415
|
+
comm_ct.COMM_SEND_MESSAGE.K_INITIATOR_ID: self.name,
|
1416
|
+
comm_ct.COMM_SEND_MESSAGE.K_SENDER_ADDR: self.bc_engine.address,
|
1417
|
+
comm_ct.COMM_SEND_MESSAGE.K_TIME: dt.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
|
1418
|
+
}
|
1419
|
+
if additional_data is not None and isinstance(additional_data, dict):
|
1420
|
+
msg_to_send.update(additional_data)
|
1421
|
+
# endif additional_data provided
|
1422
|
+
return msg_to_send
|
1423
|
+
|
1244
1424
|
def _send_command_to_box(self, command, worker, payload, show_command=True, session_id=None, **kwargs):
|
1245
1425
|
"""
|
1246
1426
|
Send a command to a node.
|
@@ -1273,37 +1453,12 @@ class GenericSession(BaseDecentrAIObject):
|
|
1273
1453
|
comm_ct.COMM_SEND_MESSAGE.K_PAYLOAD: payload,
|
1274
1454
|
}
|
1275
1455
|
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
# circumstances due to the fact that encrypt requires the public key of the receiver not the alias
|
1283
|
-
# of the receiver. The code below is a workaround to encrypt the message
|
1284
|
-
# TODO: furthermore the code will be migrated to the use of the address of the worker
|
1285
|
-
worker_addr = self.get_addr_by_name(worker)
|
1286
|
-
assert worker_addr is not None, f"Unknown worker address: {worker} - {worker_addr}"
|
1287
|
-
|
1288
|
-
str_enc_data = self.bc_engine.encrypt(str_data, worker_addr)
|
1289
|
-
critical_data = {
|
1290
|
-
comm_ct.COMM_SEND_MESSAGE.K_EE_IS_ENCRYPTED: True,
|
1291
|
-
comm_ct.COMM_SEND_MESSAGE.K_EE_ENCRYPTED_DATA: str_enc_data,
|
1292
|
-
}
|
1293
|
-
else:
|
1294
|
-
critical_data[comm_ct.COMM_SEND_MESSAGE.K_EE_IS_ENCRYPTED] = False
|
1295
|
-
if encrypt_payload:
|
1296
|
-
critical_data[comm_ct.COMM_SEND_MESSAGE.K_EE_ENCRYPTED_DATA] = "Error! No receiver address found!"
|
1297
|
-
|
1298
|
-
# endif
|
1299
|
-
msg_to_send = {
|
1300
|
-
**critical_data,
|
1301
|
-
comm_ct.COMM_SEND_MESSAGE.K_EE_ID: worker,
|
1302
|
-
comm_ct.COMM_SEND_MESSAGE.K_SESSION_ID: session_id or self.name,
|
1303
|
-
comm_ct.COMM_SEND_MESSAGE.K_INITIATOR_ID: self.name,
|
1304
|
-
comm_ct.COMM_SEND_MESSAGE.K_SENDER_ADDR: self.bc_engine.address,
|
1305
|
-
comm_ct.COMM_SEND_MESSAGE.K_TIME: dt.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
|
1306
|
-
}
|
1456
|
+
msg_to_send = self.__prepare_message(
|
1457
|
+
msg_data=critical_data,
|
1458
|
+
encrypt_message=self.encrypt_comms,
|
1459
|
+
destination_id=worker,
|
1460
|
+
session_id=session_id,
|
1461
|
+
)
|
1307
1462
|
self.bc_engine.sign(msg_to_send, use_digest=True)
|
1308
1463
|
if show_command:
|
1309
1464
|
self.P(
|
@@ -1311,7 +1466,7 @@ class GenericSession(BaseDecentrAIObject):
|
|
1311
1466
|
color='y',
|
1312
1467
|
verbosity=1
|
1313
1468
|
)
|
1314
|
-
self.
|
1469
|
+
self._send_command(worker, msg_to_send)
|
1315
1470
|
return
|
1316
1471
|
|
1317
1472
|
def _send_command_create_pipeline(self, worker, pipeline_config, **kwargs):
|
@@ -2470,6 +2625,35 @@ class GenericSession(BaseDecentrAIObject):
|
|
2470
2625
|
def client_address(self):
|
2471
2626
|
return self.get_client_address()
|
2472
2627
|
|
2628
|
+
def __wait_for_supervisors_net_mon_data(
|
2629
|
+
self,
|
2630
|
+
supervisor=None,
|
2631
|
+
timeout=10,
|
2632
|
+
min_supervisors=2
|
2633
|
+
):
|
2634
|
+
# the following loop will wait for the desired number of supervisors to appear online
|
2635
|
+
# for the current session
|
2636
|
+
start = tm()
|
2637
|
+
result = False
|
2638
|
+
while (tm() - start) < timeout:
|
2639
|
+
if supervisor is not None:
|
2640
|
+
if supervisor in self.__current_network_statuses:
|
2641
|
+
result = True
|
2642
|
+
break
|
2643
|
+
elif len(self.__current_network_statuses) >= min_supervisors:
|
2644
|
+
result = True
|
2645
|
+
break
|
2646
|
+
sleep(0.1)
|
2647
|
+
elapsed = tm() - start
|
2648
|
+
# end while
|
2649
|
+
# done waiting for supervisors
|
2650
|
+
return result, elapsed
|
2651
|
+
|
2652
|
+
|
2653
|
+
def get_all_nodes_pipelines(self):
|
2654
|
+
# TODO: Bleo inject this function in __on_hb and maybe_process_net_config and dump result
|
2655
|
+
return self._dct_online_nodes_pipelines
|
2656
|
+
|
2473
2657
|
def get_network_known_nodes(
|
2474
2658
|
self,
|
2475
2659
|
timeout=10,
|
@@ -2540,19 +2724,11 @@ class GenericSession(BaseDecentrAIObject):
|
|
2540
2724
|
for k in mapping:
|
2541
2725
|
res[k] = []
|
2542
2726
|
|
2543
|
-
|
2544
|
-
|
2545
|
-
|
2546
|
-
|
2547
|
-
|
2548
|
-
if supervisor in self.__current_network_statuses:
|
2549
|
-
break
|
2550
|
-
elif len(self.__current_network_statuses) >= min_supervisors:
|
2551
|
-
break
|
2552
|
-
sleep(0.1)
|
2553
|
-
elapsed = tm() - start
|
2554
|
-
# end while
|
2555
|
-
# done waiting for supervisors
|
2727
|
+
result, elapsed = self.__wait_for_supervisors_net_mon_data(
|
2728
|
+
supervisor=supervisor,
|
2729
|
+
timeout=timeout,
|
2730
|
+
min_supervisors=min_supervisors,
|
2731
|
+
)
|
2556
2732
|
best_super = 'ERROR'
|
2557
2733
|
best_super_alias = 'ERROR'
|
2558
2734
|
|
naeural_client/bc/base.py
CHANGED
naeural_client/const/apps.py
CHANGED
@@ -10,6 +10,7 @@ class PLUGIN_SIGNATURES:
|
|
10
10
|
CHAIN_DIST_CUSTOM_JOB_01 = 'PROCESS_REAL_TIME_COLLECTED_DATA_CUSTOM_EXEC_CHAIN_DIST'
|
11
11
|
TELEGRAM_BASIC_BOT_01 = 'TELEGRAM_BASIC_BOT_01'
|
12
12
|
TELEGRAM_CONVERSATIONAL_BOT_01 = 'TELEGRAM_CONVERSATIONAL_BOT_01'
|
13
|
+
NET_CONFIG_MONITOR = 'NET_CONFIG_MONITOR'
|
13
14
|
# INSERT_NEW_PLUGIN_HERE
|
14
15
|
|
15
16
|
|
naeural_client/const/base.py
CHANGED
@@ -18,6 +18,9 @@ DAUTH_VARS = [DAUTH_NONCE, BCctbase.SIGN, BCctbase.SENDER, BCctbase.HASH]
|
|
18
18
|
|
19
19
|
ETH_ENABLED_ENV_KEY = 'EE_ETH_ENABLED'
|
20
20
|
|
21
|
+
EE_EPOCH_INTERVALS_KEY = 'EE_EPOCH_INTERVALS'
|
22
|
+
EE_EPOCH_INTERVAL_SECONDS_KEY = 'EE_EPOCH_INTERVAL_SECONDS'
|
23
|
+
|
21
24
|
|
22
25
|
class LocalInfo:
|
23
26
|
LOCAL_INFO_FILE = 'local_info.json'
|
@@ -10,7 +10,7 @@ class MqttSession(GenericSession):
|
|
10
10
|
self._default_communicator = MQTTWrapper(
|
11
11
|
log=self.log,
|
12
12
|
config=self._config,
|
13
|
-
send_channel_name=comm_ct.
|
13
|
+
send_channel_name=comm_ct.COMMUNICATION_PAYLOADS_CHANNEL,
|
14
14
|
recv_channel_name=comm_ct.COMMUNICATION_PAYLOADS_CHANNEL,
|
15
15
|
comm_type=comm_ct.COMMUNICATION_DEFAULT,
|
16
16
|
recv_buff=self._payload_messages,
|
@@ -21,6 +21,7 @@ class MqttSession(GenericSession):
|
|
21
21
|
self._heartbeats_communicator = MQTTWrapper(
|
22
22
|
log=self.log,
|
23
23
|
config=self._config,
|
24
|
+
send_channel_name=comm_ct.COMMUNICATION_CONFIG_CHANNEL,
|
24
25
|
recv_channel_name=comm_ct.COMMUNICATION_CTRL_CHANNEL,
|
25
26
|
comm_type=comm_ct.COMMUNICATION_HEARTBEATS,
|
26
27
|
recv_buff=self._hb_messages,
|
@@ -37,6 +38,11 @@ class MqttSession(GenericSession):
|
|
37
38
|
connection_name=self.name,
|
38
39
|
verbosity=self._verbosity,
|
39
40
|
)
|
41
|
+
self.__communicators = {
|
42
|
+
'default': self._default_communicator,
|
43
|
+
'heartbeats': self._heartbeats_communicator,
|
44
|
+
'notifications': self._notifications_communicator,
|
45
|
+
}
|
40
46
|
return super(MqttSession, self).startup()
|
41
47
|
|
42
48
|
@property
|
@@ -64,9 +70,18 @@ class MqttSession(GenericSession):
|
|
64
70
|
self._notifications_communicator.release()
|
65
71
|
return
|
66
72
|
|
67
|
-
def
|
73
|
+
def _send_raw_message(self, to, msg, communicator='default'):
|
68
74
|
payload = json.dumps(msg)
|
75
|
+
communicator_obj = self.__communicators.get(communicator, self._default_communicator)
|
76
|
+
communicator_obj._send_to = to
|
77
|
+
communicator_obj.send(payload)
|
78
|
+
return
|
69
79
|
|
70
|
-
|
71
|
-
|
80
|
+
def _send_payload(self, payload):
|
81
|
+
# `to` parameter will be added after migrating to segregated payloads.
|
82
|
+
self._send_raw_message(to=None, msg=payload, communicator='default')
|
72
83
|
return
|
84
|
+
|
85
|
+
def _send_command(self, to, command):
|
86
|
+
self._send_raw_message(to, command, communicator='heartbeats')
|
87
|
+
return
|
@@ -0,0 +1,279 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: naeural_client
|
3
|
+
Version: 2.6.16
|
4
|
+
Summary: `naeural_client` is the Python SDK required for client app development for the Naeural Edge Protocol Edge Protocol framework
|
5
|
+
Project-URL: Homepage, https://github.com/NaeuralEdgeProtocol/naeural_client
|
6
|
+
Project-URL: Bug Tracker, https://github.com/NaeuralEdgeProtocol/naeural_client/issues
|
7
|
+
Author-email: Andrei Ionut Damian <andrei.damian@me.com>, Cristan Bleotiu <cristibleotiu@gmail.com>, Stefan Saraev <saraevstefan@gmail.com>
|
8
|
+
License-File: LICENSE
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
10
|
+
Classifier: Operating System :: OS Independent
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
12
|
+
Requires-Python: >=3.8
|
13
|
+
Requires-Dist: cryptography>=39.0.0
|
14
|
+
Requires-Dist: numpy
|
15
|
+
Requires-Dist: paho-mqtt
|
16
|
+
Requires-Dist: pandas
|
17
|
+
Requires-Dist: pika
|
18
|
+
Requires-Dist: pyaml
|
19
|
+
Requires-Dist: pyopenssl>=23.0.0
|
20
|
+
Requires-Dist: python-dateutil
|
21
|
+
Requires-Dist: web3
|
22
|
+
Description-Content-Type: text/markdown
|
23
|
+
|
24
|
+
# Ratio1 SDK
|
25
|
+
|
26
|
+
Welcome to the **Ratio1 SDK** repository, formerly known as the **naeural_client SDK**. The Ratio1 SDK is a crucial component of the Ratio1 ecosystem, designed to facilitate interactions, development, and deployment of jobs within the Ratio1 network. By enabling low-code development, the SDK allows developers to build and deploy end-to-end AI (and beyond) cooperative application pipelines seamlessly within the Ratio1 Edge Nodes ecosystem.
|
27
|
+
|
28
|
+
## Overview
|
29
|
+
|
30
|
+
The **Ratio1 SDK** is engineered to enhance the Ratio1 protocol and ecosystem, aiming to improve the functionality and performance of the Ratio1 Edge Node through dedicated research and community contributions. This SDK serves as an essential tool for developers looking to integrate their applications with the Ratio1 network, enabling them to leverage the decentralized, secure, and privacy-preserving capabilities of Ratio1 Edge Nodes.
|
31
|
+
|
32
|
+
Key functionalities of the Ratio1 SDK include:
|
33
|
+
|
34
|
+
- **Job Interactions**: Facilitate the development and management of computation tasks within the Ratio1 network.
|
35
|
+
- **Development Tools**: Provide low-code solutions for creating and deploying AI-driven application pipelines.
|
36
|
+
- **Ecosystem Integration**: Seamlessly integrate with Ratio1 Edge Nodes to utilize their computational resources effectively.
|
37
|
+
- **Collaboration and Deployment**: Enable cooperative application development and deployment across multiple edge nodes within the Ratio1 ecosystem.
|
38
|
+
|
39
|
+
Unlike the Ratio1 Core Packages, which are intended solely for protocol and ecosystem enhancements and are not meant for standalone installation, the Ratio1 SDK is designed for both client-side development and sending workloads to Ratio1 Edge Nodes, making it an indispensable tool for developers within the ecosystem.
|
40
|
+
|
41
|
+
## Dependencies
|
42
|
+
|
43
|
+
The Ratio1 SDK relies on several key packages to function effectively. These dependencies are automatically managed when installing the SDK via pip:
|
44
|
+
|
45
|
+
- `pika`
|
46
|
+
- `paho-mqtt`
|
47
|
+
- `numpy`
|
48
|
+
- `pyopenssl>=23.0.0`
|
49
|
+
- `cryptography>=39.0.0`
|
50
|
+
- `python-dateutil`
|
51
|
+
- `pyaml`
|
52
|
+
|
53
|
+
## Installation
|
54
|
+
|
55
|
+
Installing the Ratio1 SDK is straightforward and is intended for development and integration into your projects. Use the following pip commands to install the SDK:
|
56
|
+
|
57
|
+
### Standard Installation
|
58
|
+
|
59
|
+
To install the Ratio1 SDK, run:
|
60
|
+
|
61
|
+
```shell
|
62
|
+
pip install ratio1_sdk --upgrade
|
63
|
+
```
|
64
|
+
|
65
|
+
### Development Installation
|
66
|
+
|
67
|
+
For development purposes, you can clone the repository and set up the SDK in an editable mode:
|
68
|
+
|
69
|
+
```shell
|
70
|
+
git clone https://github.com/Ratio1/ratio1_sdk
|
71
|
+
cd ratio1_sdk
|
72
|
+
pip install -e .
|
73
|
+
```
|
74
|
+
|
75
|
+
This allows you to make modifications to the SDK and have them reflected immediately without reinstalling.
|
76
|
+
|
77
|
+
## Documentation
|
78
|
+
|
79
|
+
Comprehensive documentation for the Ratio1 SDK is currently a work in progress. Minimal documentation is available here, with detailed code examples located in the `tutorials` folder within the project's repository. We encourage developers to explore these examples to understand the SDK's capabilities and integration methods.
|
80
|
+
|
81
|
+
## Quick Start Guides
|
82
|
+
|
83
|
+
Starting with version 2.6+, the Ratio1 SDK automatically performs self-configuration using **dAuth**—the Ratio1 decentralized self-authentication system. To begin integrating with the Ratio1 network, follow these steps:
|
84
|
+
|
85
|
+
### 1. Start a Local Edge Node
|
86
|
+
|
87
|
+
Launch a local Ratio1 Edge Node using Docker:
|
88
|
+
|
89
|
+
```bash
|
90
|
+
docker run -d --name=local_node ratio1/edge_node:develop
|
91
|
+
```
|
92
|
+
|
93
|
+
After a few seconds, the node will be online. Retrieve the node's address by running:
|
94
|
+
|
95
|
+
```bash
|
96
|
+
docker exec local_node get_node_info
|
97
|
+
```
|
98
|
+
|
99
|
+
The output will resemble:
|
100
|
+
|
101
|
+
```json
|
102
|
+
{
|
103
|
+
"address": "0xai_AtMvIwaEPi5M8cnkdbaZ3tbUhCzKbGKEYuZ1xFtCjT_6",
|
104
|
+
"alias": "6dd74472642e",
|
105
|
+
"eth_address": "0x98FE7c0d8CeC2E97B932D2bDC1bb73B395C9Dfd7"
|
106
|
+
}
|
107
|
+
```
|
108
|
+
|
109
|
+
### 2. Develop and Deploy Jobs
|
110
|
+
|
111
|
+
Use the SDK to develop and send workloads to the Edge Nodes. Below are examples of both local and remote execution.
|
112
|
+
|
113
|
+
## Examples
|
114
|
+
|
115
|
+
### Local Execution
|
116
|
+
|
117
|
+
This example demonstrates how to find all 168 prime numbers in the interval 1 - 1000 using local execution. The code leverages multiple threads to perform prime number generation efficiently.
|
118
|
+
|
119
|
+
```python
|
120
|
+
import numpy as np
|
121
|
+
from concurrent.futures import ThreadPoolExecutor
|
122
|
+
|
123
|
+
def local_brute_force_prime_number_generator():
|
124
|
+
def is_prime(n):
|
125
|
+
if n <= 1:
|
126
|
+
return False
|
127
|
+
for i in range(2, int(np.sqrt(n)) + 1):
|
128
|
+
if n % i == 0:
|
129
|
+
return False
|
130
|
+
return True
|
131
|
+
|
132
|
+
random_numbers = np.random.randint(1, 1000, 20)
|
133
|
+
|
134
|
+
thread_pool = ThreadPoolExecutor(max_workers=4)
|
135
|
+
are_primes = list(thread_pool.map(is_prime, random_numbers))
|
136
|
+
|
137
|
+
prime_numbers = []
|
138
|
+
for i in range(len(random_numbers)):
|
139
|
+
if are_primes[i]:
|
140
|
+
prime_numbers.append(random_numbers[i])
|
141
|
+
|
142
|
+
return prime_numbers
|
143
|
+
|
144
|
+
if __name__ == "__main__":
|
145
|
+
found_so_far = []
|
146
|
+
print_step = 0
|
147
|
+
|
148
|
+
while len(found_so_far) < 168:
|
149
|
+
# Compute a batch of prime numbers
|
150
|
+
prime_numbers = local_brute_force_prime_number_generator()
|
151
|
+
|
152
|
+
# Keep only the new prime numbers
|
153
|
+
for prime_number in prime_numbers:
|
154
|
+
if prime_number not in found_so_far:
|
155
|
+
found_so_far.append(prime_number)
|
156
|
+
|
157
|
+
# Show progress
|
158
|
+
if print_step % 50 == 0:
|
159
|
+
print("Found so far: {}: {}\n".format(len(found_so_far), sorted(found_so_far)))
|
160
|
+
|
161
|
+
print_step += 1
|
162
|
+
|
163
|
+
# Show final result
|
164
|
+
print("Found so far: {}: {}\n".format(len(found_so_far), sorted(found_so_far)))
|
165
|
+
```
|
166
|
+
|
167
|
+
### Remote Execution
|
168
|
+
|
169
|
+
To accelerate prime number discovery, this example demonstrates deploying the task across multiple edge nodes within the Ratio1 network. Minimal code changes are required to transition from local to remote execution.
|
170
|
+
|
171
|
+
#### 1. Modify the Prime Number Generator
|
172
|
+
|
173
|
+
```python
|
174
|
+
from ratio1_sdk import CustomPluginTemplate
|
175
|
+
|
176
|
+
def remote_brute_force_prime_number_generator(plugin: CustomPluginTemplate):
|
177
|
+
def is_prime(n):
|
178
|
+
if n <= 1:
|
179
|
+
return False
|
180
|
+
for i in range(2, int(plugin.np.sqrt(n)) + 1):
|
181
|
+
if n % i == 0:
|
182
|
+
return False
|
183
|
+
return True
|
184
|
+
|
185
|
+
random_numbers = plugin.np.random.randint(1, 1000, 20)
|
186
|
+
are_primes = plugin.threadapi_map(is_prime, random_numbers, n_threads=4)
|
187
|
+
|
188
|
+
prime_numbers = []
|
189
|
+
for i in range(len(random_numbers)):
|
190
|
+
if are_primes[i]:
|
191
|
+
prime_numbers.append(random_numbers[i])
|
192
|
+
|
193
|
+
return prime_numbers
|
194
|
+
```
|
195
|
+
|
196
|
+
#### 2. Connect to the Network and Select a Node
|
197
|
+
|
198
|
+
```python
|
199
|
+
from ratio1_sdk import Session
|
200
|
+
from time import sleep
|
201
|
+
|
202
|
+
def on_heartbeat(session: Session, node: str, heartbeat: dict):
|
203
|
+
session.P("{} is online".format(node))
|
204
|
+
return
|
205
|
+
|
206
|
+
if __name__ == '__main__':
|
207
|
+
session = Session(
|
208
|
+
on_heartbeat=on_heartbeat
|
209
|
+
)
|
210
|
+
|
211
|
+
# Run the program for 15 seconds to detect online nodes
|
212
|
+
sleep(15)
|
213
|
+
|
214
|
+
# Retrieve and select an online node
|
215
|
+
node = "0xai_A8SY7lEqBtf5XaGyB6ipdk5C30vSf3HK4xELp3iplwLe" # naeural-1
|
216
|
+
```
|
217
|
+
|
218
|
+
#### 3. Deploy the Distributed Job
|
219
|
+
|
220
|
+
```python
|
221
|
+
from ratio1_sdk import DistributedCustomCodePresets as Presets
|
222
|
+
|
223
|
+
_, _ = session.create_chain_dist_custom_job(
|
224
|
+
node=node,
|
225
|
+
main_node_process_real_time_collected_data=Presets.PROCESS_REAL_TIME_COLLECTED_DATA__KEEP_UNIQUES_IN_AGGREGATED_COLLECTED_DATA,
|
226
|
+
main_node_finish_condition=Presets.FINISH_CONDITION___AGGREGATED_DATA_MORE_THAN_X,
|
227
|
+
main_node_finish_condition_kwargs={"X": 167},
|
228
|
+
main_node_aggregate_collected_data=Presets.AGGREGATE_COLLECTED_DATA___AGGREGATE_COLLECTED_DATA,
|
229
|
+
nr_remote_worker_nodes=2,
|
230
|
+
worker_node_code=remote_brute_force_prime_number_generator,
|
231
|
+
on_data=locally_process_partial_results,
|
232
|
+
deploy=True
|
233
|
+
)
|
234
|
+
```
|
235
|
+
|
236
|
+
#### 4. Close the Session Upon Completion
|
237
|
+
|
238
|
+
```python
|
239
|
+
# Wait until the finished flag is set to True
|
240
|
+
session.run(wait=lambda: not finished, close_pipelines=True)
|
241
|
+
```
|
242
|
+
|
243
|
+
## Project Financing Disclaimer
|
244
|
+
|
245
|
+
This project incorporates open-source components developed with the support of financing grants **SMIS 143488** and **SMIS 156084**, provided by the Romanian Competitiveness Operational Programme. We extend our sincere gratitude for this support, which has been instrumental in advancing our work and enabling us to share these resources with the community.
|
246
|
+
|
247
|
+
The content and information within this repository are solely the responsibility of the authors and do not necessarily reflect the views of the funding agencies. The grants have specifically supported certain aspects of this open-source project, facilitating broader dissemination and collaborative development.
|
248
|
+
|
249
|
+
For any inquiries regarding the funding and its impact on this project, please contact the authors directly.
|
250
|
+
|
251
|
+
## License
|
252
|
+
|
253
|
+
This project is licensed under the **Apache 2.0 License**. For more details, please refer to the [LICENSE](LICENSE) file.
|
254
|
+
|
255
|
+
## Contact
|
256
|
+
|
257
|
+
For more information, visit our website at [https://ratio1.ai](https://ratio1.ai) or reach out to us via email at [support@ratio1.ai](mailto:support@ratio1.ai).
|
258
|
+
|
259
|
+
## Citation
|
260
|
+
|
261
|
+
If you use the Ratio1 SDK in your research or projects, please cite it as follows:
|
262
|
+
|
263
|
+
```bibtex
|
264
|
+
@misc{Ratio1SDK,
|
265
|
+
author = {Ratio1.AI},
|
266
|
+
title = {Ratio1 SDK},
|
267
|
+
year = {2024-2025},
|
268
|
+
howpublished = {\url{https://github.com/NaeuralEdgeProtocol/naeural_client}},
|
269
|
+
}
|
270
|
+
```
|
271
|
+
|
272
|
+
```bibtex
|
273
|
+
@misc{Ratio1EdgeNode,
|
274
|
+
author = {Ratio1.AI},
|
275
|
+
title = {Ratio1: Edge Node},
|
276
|
+
year = {2024-2025},
|
277
|
+
howpublished = {\url{https://github.com/NaeuralEdgeProtocol/edge_node}},
|
278
|
+
}
|
279
|
+
```
|
@@ -1,10 +1,10 @@
|
|
1
1
|
naeural_client/__init__.py,sha256=YimqgDbjLuywsf8zCWE0EaUXH4MBUrqLxt0TDV558hQ,632
|
2
|
-
naeural_client/_ver.py,sha256=
|
2
|
+
naeural_client/_ver.py,sha256=eM7t0xRbVJaaGjrh0O9tmQF7K_usWv_3wk3hVZ27_Qs,331
|
3
3
|
naeural_client/base_decentra_object.py,sha256=C4iwZTkhKNBS4VHlJs5DfElRYLo4Q9l1V1DNVSk1fyQ,4412
|
4
4
|
naeural_client/plugins_manager_mixin.py,sha256=X1JdGLDz0gN1rPnTN_5mJXR8JmqoBFQISJXmPR9yvCo,11106
|
5
5
|
naeural_client/base/__init__.py,sha256=hACh83_cIv7-PwYMM3bQm2IBmNqiHw-3PAfDfAEKz9A,259
|
6
6
|
naeural_client/base/distributed_custom_code_presets.py,sha256=cvz5R88P6Z5V61Ce1vHVVh8bOkgXd6gve_vdESDNAsg,2544
|
7
|
-
naeural_client/base/generic_session.py,sha256=
|
7
|
+
naeural_client/base/generic_session.py,sha256=Vw08ywfW03aN0_5kmXOF5-VkRthl7FwAXqJu3M3NDMs,104275
|
8
8
|
naeural_client/base/instance.py,sha256=kcZJmjLBtx8Bjj_ysIOx1JmLA-qSpG7E28j5rq6IYus,20444
|
9
9
|
naeural_client/base/pipeline.py,sha256=b4uNHrEIOlAtw4PGUx20dxwBhDck5__SrVXaHcSi8ZA,58251
|
10
10
|
naeural_client/base/plugin_template.py,sha256=qGaXByd_JZFpjvH9GXNbT7KaitRxIJB6-1IhbKrZjq4,138123
|
@@ -14,7 +14,7 @@ naeural_client/base/webapp_pipeline.py,sha256=QmPLVmhP0CPdi0YuvbZEH4APYz2Amtw3gy
|
|
14
14
|
naeural_client/base/payload/__init__.py,sha256=y8fBI8tG2ObNfaXFWjyWZXwu878FRYj_I8GIbHT4GKE,29
|
15
15
|
naeural_client/base/payload/payload.py,sha256=x-au7l67Z_vfn_4R2C_pjZCaFuUVXHngJiGOfIAYVdE,2690
|
16
16
|
naeural_client/bc/__init__.py,sha256=FQj23D1PrY06NUOARiKQi4cdj0-VxnoYgYDEht8lpr8,158
|
17
|
-
naeural_client/bc/base.py,sha256=
|
17
|
+
naeural_client/bc/base.py,sha256=KC-wtymP9tEvXGG6wfiRirgshjW7_y58ylWjbe_lUWo,36160
|
18
18
|
naeural_client/bc/chain.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
naeural_client/bc/ec.py,sha256=qI8l7YqiS4MNftlx-tF7IZUswrSeQc7KMn5OZ0fEaJs,23370
|
20
20
|
naeural_client/certs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -31,8 +31,8 @@ naeural_client/comm/amqp_wrapper.py,sha256=hzj6ih07DnLQy2VSfA88giDIFHaCp9uSdGLTA
|
|
31
31
|
naeural_client/comm/mqtt_wrapper.py,sha256=Ig3bFZkCbWd4y_Whn2PPa91Z3aLgNbNPau6Tn5yLPZ8,16167
|
32
32
|
naeural_client/const/README.md,sha256=6OHesr-f5NBuuJGryEoi_TCu2XdlhfQYlDKx_IJoXeg,177
|
33
33
|
naeural_client/const/__init__.py,sha256=MM6Zib6i7M2qWcMkLtLx14zqU-lE-u2uPHjNvbh2jAM,478
|
34
|
-
naeural_client/const/apps.py,sha256=
|
35
|
-
naeural_client/const/base.py,sha256=
|
34
|
+
naeural_client/const/apps.py,sha256=ePBiJXLuPfFOKuw-LJrT9OWbaodU7QApfDurIPNDoB4,655
|
35
|
+
naeural_client/const/base.py,sha256=1ScfnfNd5xeWo7tkkDgQR95vliTpcax025Naj0Lqw38,4574
|
36
36
|
naeural_client/const/comms.py,sha256=La6JXWHexH8CfcBCKyT4fCIoeaoZlcm7KtZ57ab4ZgU,2201
|
37
37
|
naeural_client/const/environment.py,sha256=iytmTDgbOjvORPwHQmc0K0r-xJx7dnnzNnqAJJiFCDA,870
|
38
38
|
naeural_client/const/formatter.py,sha256=AW3bWlqf39uaqV4BBUuW95qKYfF2OkkU4f9hy3kSVhM,200
|
@@ -47,7 +47,7 @@ naeural_client/default/instance/net_mon_01_plugin.py,sha256=u85i2AiYHkLJnam0wOx-
|
|
47
47
|
naeural_client/default/instance/telegram_basic_bot_01_plugin.py,sha256=P_W2K_ng7svbhoIoYGvkGxa4v9UUoxuOAAgtoM36wDo,161
|
48
48
|
naeural_client/default/instance/telegram_conversational_bot_01_plugin.py,sha256=XkyE-uNbtFnTSGA1nOf-XNmUipAUjhg0xXV1glRb7gc,179
|
49
49
|
naeural_client/default/instance/view_scene_01_plugin.py,sha256=5kMhd23kL5AYCdOJzrdCqi2ohoQNvmpv8oE6hWQtUWk,720
|
50
|
-
naeural_client/default/session/mqtt_session.py,sha256=
|
50
|
+
naeural_client/default/session/mqtt_session.py,sha256=QdL0LPCqV3Fw0VSNgnsXWKJoxnkl17BtSYvEmhNU_zM,3079
|
51
51
|
naeural_client/io_formatter/__init__.py,sha256=_wy7c-Z9kgb26jN7uNTDq88G7xZ3wI_ObuQd3QWNPkQ,85
|
52
52
|
naeural_client/io_formatter/io_formatter_manager.py,sha256=MiZ70cGVD6hR99QiEk9wvJ_vADxpI-y2bTb0ITlMNI0,3451
|
53
53
|
naeural_client/io_formatter/base/__init__.py,sha256=VmLHY35NCRR46lgjhcrwtFxmJIVOjX2ADV9wOXt9CKA,41
|
@@ -81,8 +81,8 @@ naeural_client/utils/__init__.py,sha256=mAnke3-MeRzz3nhQvhuHqLnpaaCSmDxicd7Ck9uw
|
|
81
81
|
naeural_client/utils/comm_utils.py,sha256=4cS9llRr_pK_3rNgDcRMCQwYPO0kcNU7AdWy_LtMyCY,1072
|
82
82
|
naeural_client/utils/config.py,sha256=v7xHikr6Z5Sbvf3opYeMhYzGWD2pe0HlRwa-aGJzUh8,6323
|
83
83
|
naeural_client/utils/dotenv.py,sha256=_AgSo35n7EnQv5yDyu7C7i0kHragLJoCGydHjvOkrYY,2008
|
84
|
-
naeural_client-2.6.
|
85
|
-
naeural_client-2.6.
|
86
|
-
naeural_client-2.6.
|
87
|
-
naeural_client-2.6.
|
88
|
-
naeural_client-2.6.
|
84
|
+
naeural_client-2.6.16.dist-info/METADATA,sha256=PaCUsM0cAtvH9ZUTzfwNxZTumPu4np7e3inODMj9WLA,10362
|
85
|
+
naeural_client-2.6.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
86
|
+
naeural_client-2.6.16.dist-info/entry_points.txt,sha256=PNdyotDaQBAslZREx5luVyj0kqpQnwNACwkFNTPIHU4,55
|
87
|
+
naeural_client-2.6.16.dist-info/licenses/LICENSE,sha256=cvOsJVslde4oIaTCadabXnPqZmzcBO2f2zwXZRmJEbE,11311
|
88
|
+
naeural_client-2.6.16.dist-info/RECORD,,
|
@@ -1,358 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: naeural_client
|
3
|
-
Version: 2.6.14
|
4
|
-
Summary: `naeural_client` is the Python SDK required for client app development for the Naeural Edge Protocol Edge Protocol framework
|
5
|
-
Project-URL: Homepage, https://github.com/NaeuralEdgeProtocol/naeural_client
|
6
|
-
Project-URL: Bug Tracker, https://github.com/NaeuralEdgeProtocol/naeural_client/issues
|
7
|
-
Author-email: Andrei Ionut Damian <andrei.damian@me.com>, Cristan Bleotiu <cristibleotiu@gmail.com>, Stefan Saraev <saraevstefan@gmail.com>
|
8
|
-
License-File: LICENSE
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
10
|
-
Classifier: Operating System :: OS Independent
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
12
|
-
Requires-Python: >=3.8
|
13
|
-
Requires-Dist: cryptography>=39.0.0
|
14
|
-
Requires-Dist: numpy
|
15
|
-
Requires-Dist: paho-mqtt
|
16
|
-
Requires-Dist: pandas
|
17
|
-
Requires-Dist: pika
|
18
|
-
Requires-Dist: pyaml
|
19
|
-
Requires-Dist: pyopenssl>=23.0.0
|
20
|
-
Requires-Dist: python-dateutil
|
21
|
-
Requires-Dist: web3
|
22
|
-
Description-Content-Type: text/markdown
|
23
|
-
|
24
|
-
# Ratio1 SDK (naeural_client SDK)
|
25
|
-
|
26
|
-
This is the Python SDK package that allows interactions, development and deployment of jobs in Ratio1 ecosystem formely known as Naeural Edge Protocol network. The SDK enables low-code development and deployment of end-to-end AI (and not only) cooperative application pipelines within the Ratio1 Edge Nodes ecosystem.
|
27
|
-
|
28
|
-
## Dependencies
|
29
|
-
|
30
|
-
This packet depends and will automatically install the following packets: `pika`, `paho-mqtt`, `numpy`, `pyopenssl>=23.0.0`, `cryptography>=39.0.0`, `python-dateutil`, `pyaml`.
|
31
|
-
|
32
|
-
## Installation
|
33
|
-
|
34
|
-
```shell
|
35
|
-
pip install naeural_client --upgrade
|
36
|
-
```
|
37
|
-
|
38
|
-
### Development installation
|
39
|
-
|
40
|
-
```shell
|
41
|
-
git clone https://github.com/NaeuralEdgeProtocol/naeural_client
|
42
|
-
pip install -e .
|
43
|
-
```
|
44
|
-
|
45
|
-
## Documentation
|
46
|
-
|
47
|
-
Minimal documentation will be presented here. The complete documentation is
|
48
|
-
Work in Progress.
|
49
|
-
|
50
|
-
Code examples are located in the `tutorials` folder in the project's repository.
|
51
|
-
|
52
|
-
## Quick start guides
|
53
|
-
|
54
|
-
Starting with version 2.6+ the SDK will automatically perform self-configuration using the dAuth - the Ratio1 decentralized self-authentication system.
|
55
|
-
|
56
|
-
In order to start a local edge node you just need to run:
|
57
|
-
|
58
|
-
```bash
|
59
|
-
docker run -d --name=local_node naeural/edge_node:develop
|
60
|
-
```
|
61
|
-
after a few seconds the node will be online and you can get the node's address by running:
|
62
|
-
|
63
|
-
```bash
|
64
|
-
docker exec local_node get_node_info
|
65
|
-
```
|
66
|
-
|
67
|
-
The output will be similar to:
|
68
|
-
|
69
|
-
```json
|
70
|
-
{
|
71
|
-
"address": "0xai_AtMvIwaEPi5M8cnkdbaZ3tbUhCzKbGKEYuZ1xFtCjT_6",
|
72
|
-
"alias": "6dd74472642e",
|
73
|
-
"eth_address": "0x98FE7c0d8CeC2E97B932D2bDC1bb73B395C9Dfd7"
|
74
|
-
}
|
75
|
-
```
|
76
|
-
|
77
|
-
|
78
|
-
## Some Examples
|
79
|
-
|
80
|
-
### Local Execution
|
81
|
-
|
82
|
-
We want to find all $168$ prime numbers in the interval $1$ - $1000$. For this we can run the following code on our local machine.
|
83
|
-
|
84
|
-
This code has segments running on multiple threads using a ThreadPool.
|
85
|
-
|
86
|
-
```python
|
87
|
-
import numpy as np
|
88
|
-
from concurrent.futures import ThreadPoolExecutor
|
89
|
-
|
90
|
-
|
91
|
-
def local_brute_force_prime_number_generator():
|
92
|
-
def is_prime(n):
|
93
|
-
if n <= 1:
|
94
|
-
return False
|
95
|
-
for i in range(2, int(np.sqrt(n)) + 1):
|
96
|
-
if n % i == 0:
|
97
|
-
return False
|
98
|
-
return True
|
99
|
-
|
100
|
-
random_numbers = np.random.randint(1, 1000, 20)
|
101
|
-
|
102
|
-
thread_pool = ThreadPoolExecutor(max_workers=4)
|
103
|
-
are_primes = list(thread_pool.map(is_prime, random_numbers))
|
104
|
-
|
105
|
-
prime_numbers = []
|
106
|
-
for i in range(len(random_numbers)):
|
107
|
-
if are_primes[i]:
|
108
|
-
prime_numbers.append(random_numbers[i])
|
109
|
-
|
110
|
-
return prime_numbers
|
111
|
-
|
112
|
-
|
113
|
-
if __name__ == "__main__":
|
114
|
-
found_so_far = []
|
115
|
-
|
116
|
-
print_step = 0
|
117
|
-
|
118
|
-
while len(found_so_far) < 168:
|
119
|
-
# compute a batch of prime numbers
|
120
|
-
prime_numbers = local_brute_force_prime_number_generator()
|
121
|
-
|
122
|
-
# keep only the new prime numbers
|
123
|
-
for prime_number in prime_numbers:
|
124
|
-
if prime_number not in found_so_far:
|
125
|
-
found_so_far.append(prime_number)
|
126
|
-
# end for
|
127
|
-
|
128
|
-
# show progress
|
129
|
-
if print_step % 50 == 0:
|
130
|
-
print("Found so far: {}: {}\n".format(len(found_so_far), sorted(found_so_far)))
|
131
|
-
|
132
|
-
print_step += 1
|
133
|
-
# end while
|
134
|
-
|
135
|
-
# show final result
|
136
|
-
print("Found so far: {}: {}\n".format(len(found_so_far), sorted(found_so_far)))
|
137
|
-
```
|
138
|
-
|
139
|
-
We can see that we have a `local_brute_force_prime_number_generator` method which will generate a random sample of $20$ numbers that will be checked if they are prime or not.
|
140
|
-
|
141
|
-
The rest of the code handles how the numbers generated with this method are kept.
|
142
|
-
Because we want to find $168$ unique numbers, we append to the list of found primes only the numbers that are not present yet.
|
143
|
-
|
144
|
-
At the end, we want to show a list of all the numbers found.
|
145
|
-
|
146
|
-
### Remote Execution
|
147
|
-
|
148
|
-
For this example we would like to use multiple edge nodes to find the prime numbers faster.
|
149
|
-
|
150
|
-
To execute this code on our network, a series of changes must be made to the `local_brute_force_prime_number_generator` method.
|
151
|
-
These changes are the only ones a developer has to do to deploy his own custom code on the network.
|
152
|
-
|
153
|
-
For this, we will create a new method, `remote_brute_force_prime_number_generator`, which will use the exposed edge node API methods.
|
154
|
-
|
155
|
-
```python
|
156
|
-
from naeural_client import CustomPluginTemplate
|
157
|
-
|
158
|
-
# through the `plugin` object we get access to the edge node API
|
159
|
-
# the CustomPluginTemplate class acts as a documentation for all the available methods and attributes
|
160
|
-
# since we do not allow imports in the custom code due to security reasons, the `plugin` object
|
161
|
-
# exposes common modules to the user
|
162
|
-
def remote_brute_force_prime_number_generator(plugin: CustomPluginTemplate):
|
163
|
-
def is_prime(n):
|
164
|
-
if n <= 1:
|
165
|
-
return False
|
166
|
-
# we use the `plugin.np` instead of the `np` module
|
167
|
-
for i in range(2, int(plugin.np.sqrt(n)) + 1):
|
168
|
-
if n % i == 0:
|
169
|
-
return False
|
170
|
-
return True
|
171
|
-
|
172
|
-
# we use the `plugin.np` instead of the `np` module
|
173
|
-
random_numbers = plugin.np.random.randint(1, 1000, 20)
|
174
|
-
|
175
|
-
# we use the `plugin.threadapi_map` instead of the `ThreadPoolExecutor.map`
|
176
|
-
are_primes = plugin.threadapi_map(is_prime, random_numbers, n_threads=4)
|
177
|
-
|
178
|
-
prime_numbers = []
|
179
|
-
for i in range(len(random_numbers)):
|
180
|
-
if are_primes[i]:
|
181
|
-
prime_numbers.append(random_numbers[i])
|
182
|
-
|
183
|
-
return prime_numbers
|
184
|
-
```
|
185
|
-
|
186
|
-
This are all the changes we have to do to deploy this code in the network.
|
187
|
-
|
188
|
-
Now lets connect to the network and see what nodes are online.
|
189
|
-
We will use the `on_heartbeat` callback to print the nodes.
|
190
|
-
|
191
|
-
```python
|
192
|
-
from naeural_client import Session
|
193
|
-
from time import sleep
|
194
|
-
|
195
|
-
def on_heartbeat(session: Session, node: str, heartbeat: dict):
|
196
|
-
# the `.P` method is used to print messages in the console and store them in the log file
|
197
|
-
session.P("{} is online".format(node))
|
198
|
-
return
|
199
|
-
|
200
|
-
|
201
|
-
if __name__ == '__main__':
|
202
|
-
# create a session
|
203
|
-
# the network credentials are read from the .env file automatically
|
204
|
-
session = Session(
|
205
|
-
on_heartbeat=on_heartbeat
|
206
|
-
)
|
207
|
-
|
208
|
-
# run the program for 15 seconds to show all the nodes that are online
|
209
|
-
sleep(15)
|
210
|
-
|
211
|
-
```
|
212
|
-
|
213
|
-
Next we will select an online node. This node will be our entrypoint in the network.
|
214
|
-
|
215
|
-
The available nodes in our test net are:
|
216
|
-
|
217
|
-
```
|
218
|
-
0xai_A8SY7lEqBtf5XaGyB6ipdk5C30vSf3HK4xELp3iplwLe naeural-1
|
219
|
-
0xai_Amfnbt3N-qg2-qGtywZIPQBTVlAnoADVRmSAsdDhlQ-6 naeural-2
|
220
|
-
0xai_ApltAljEgWk3g8x2QcSa0sS3hT1P4dyCchd04zFSMy5e naeural-3
|
221
|
-
```
|
222
|
-
|
223
|
-
We will send a task to this node. Since we want to distribute the task of finding prime numbers to multiple nodes, this selected node will handle distribution of tasks and collection of the results.
|
224
|
-
|
225
|
-
```python
|
226
|
-
node = "0xai_A8SY7lEqBtf5XaGyB6ipdk5C30vSf3HK4xELp3iplwLe" # naeural-1
|
227
|
-
|
228
|
-
# we usually wait for the node to be online before sending the task
|
229
|
-
# but in this case we are sure that the node is online because we
|
230
|
-
# have received heartbeats from it during the sleep period
|
231
|
-
|
232
|
-
# session.wait_for_node(node)
|
233
|
-
```
|
234
|
-
|
235
|
-
Our selected node will periodically output partial results with the prime numbers found so far by the worker nodes. We want to consume these results.
|
236
|
-
|
237
|
-
Thus, we need to implement a callback method that will handle this.
|
238
|
-
|
239
|
-
```python
|
240
|
-
from naeural_client import Pipeline
|
241
|
-
|
242
|
-
# a flag used to close the session when the task is finished
|
243
|
-
finished = False
|
244
|
-
|
245
|
-
def locally_process_partial_results(pipeline: Pipeline, full_payload):
|
246
|
-
global finished
|
247
|
-
found_so_far = full_payload.get("DATA")
|
248
|
-
|
249
|
-
if found_so_far:
|
250
|
-
pipeline.P("Found so far: {}: {}\n\n".format(len(found_so_far), sorted(found_so_far)))
|
251
|
-
|
252
|
-
progress = full_payload.get("PROGRESS")
|
253
|
-
if progress == 100:
|
254
|
-
pipeline.P("FINISHED\n\n")
|
255
|
-
finished = True
|
256
|
-
|
257
|
-
return
|
258
|
-
```
|
259
|
-
|
260
|
-
Now we are ready to deploy our job to the network.
|
261
|
-
|
262
|
-
```python
|
263
|
-
from naeural_client import DistributedCustomCodePresets as Presets
|
264
|
-
|
265
|
-
_, _ = session.create_chain_dist_custom_job(
|
266
|
-
# this is the main node, our entrypoint
|
267
|
-
node=node,
|
268
|
-
|
269
|
-
# this function is executed on the main node
|
270
|
-
# this handles what we want to do with primes found by a worker node after an iteration
|
271
|
-
# we want to store only the unique prime numbers
|
272
|
-
# we cam either write a custom code to pass here or we can use a preset
|
273
|
-
main_node_process_real_time_collected_data=Presets.PROCESS_REAL_TIME_COLLECTED_DATA__KEEP_UNIQUES_IN_AGGREGATED_COLLECTED_DATA,
|
274
|
-
|
275
|
-
# this function is executed on the main node
|
276
|
-
# this handles the finish condition of our distributed job
|
277
|
-
# we want to finish when we have found 168 prime numbers
|
278
|
-
# so more than 167 prime numbers
|
279
|
-
# we cam either write a custom code to pass here or we can use a preset
|
280
|
-
main_node_finish_condition=Presets.FINISH_CONDITION___AGGREGATED_DATA_MORE_THAN_X,
|
281
|
-
main_node_finish_condition_kwargs={
|
282
|
-
"X": 167
|
283
|
-
},
|
284
|
-
|
285
|
-
# this function is executed on the main node
|
286
|
-
# this handles the final processing of the results
|
287
|
-
# this function prepares data for the final result of the distributed job
|
288
|
-
# we want to aggregate all the prime numbers found by the worker nodes in a single list
|
289
|
-
# we cam either write a custom code to pass here or we can use a preset
|
290
|
-
main_node_aggregate_collected_data=Presets.AGGREGATE_COLLECTED_DATA___AGGREGATE_COLLECTED_DATA,
|
291
|
-
|
292
|
-
# how many worker nodes we want to use for this task
|
293
|
-
nr_remote_worker_nodes=2,
|
294
|
-
|
295
|
-
# this is the function that will be executed on the worker nodes
|
296
|
-
# this function generates prime numbers using brute force
|
297
|
-
# we simply pass the function reference
|
298
|
-
worker_node_code=remote_brute_force_prime_number_generator,
|
299
|
-
|
300
|
-
# this is the function that will be executed on the client
|
301
|
-
# this is the callback function that processes the partial results
|
302
|
-
# in our case we want to print the partial results
|
303
|
-
on_data=locally_process_partial_results,
|
304
|
-
|
305
|
-
# we want to deploy the job immediately
|
306
|
-
deploy=True
|
307
|
-
)
|
308
|
-
```
|
309
|
-
|
310
|
-
Last but not least, we want to close the session when the distributed job finished.
|
311
|
-
|
312
|
-
```python
|
313
|
-
# we wait until the finished flag is set to True
|
314
|
-
# we want to release the resources allocated on the selected node when the job is finished
|
315
|
-
session.run(wait=lambda: not finished, close_pipelines=True)
|
316
|
-
```
|
317
|
-
|
318
|
-
|
319
|
-
# Project Financing Disclaimer
|
320
|
-
|
321
|
-
This project includes open-source components that have been developed with the support of financing grants SMIS 143488 and SMIS 156084, provided by the Romanian Competitiveness Operational Programme. We are grateful for this support, which has enabled us to advance our work and share these resources with the community.
|
322
|
-
|
323
|
-
The content and information provided within this repository are solely the responsibility of the authors and do not necessarily reflect the views of the funding agencies. The funding received under these grants has been instrumental in supporting specific parts of this open source project, allowing for broader dissemination and collaborative development.
|
324
|
-
|
325
|
-
For any inquiries related to the funding and its impact on this project, please contact the authors directly.
|
326
|
-
|
327
|
-
|
328
|
-
# Citation
|
329
|
-
|
330
|
-
```bibtex
|
331
|
-
@misc{naeural_client,
|
332
|
-
author = {Stefan Saraev, Andrei Damian},
|
333
|
-
title = {naeural_client: Python SDK for Naeural Edge Protocol Edge Protocol},
|
334
|
-
year = {2024},
|
335
|
-
howpublished = {\url{https://github.com/Naeural Edge ProtocolEdgeProtocol/naeural_client}},
|
336
|
-
}
|
337
|
-
```
|
338
|
-
|
339
|
-
```bibtex
|
340
|
-
@misc{project_funding_acknowledgment1,
|
341
|
-
author = {Damian, Bleotiu, Saraev, Constantinescu},
|
342
|
-
title = {SOLIS – Sistem Omogen multi-Locație cu funcționalități Inteligente și Sustenabile”
|
343
|
-
SMIS 143488},
|
344
|
-
howpublished = {\url{https://github.com/Naeural Edge ProtocolEdgeProtocol/}},
|
345
|
-
note = {This project includes open-source components developed with support from the Romanian Competitiveness Operational Programme under grants SMIS 143488. The content is solely the responsibility of the authors and does not necessarily reflect the views of the funding agencies.},
|
346
|
-
year = {2021-2022}
|
347
|
-
}
|
348
|
-
```
|
349
|
-
|
350
|
-
```bibtex
|
351
|
-
@misc{project_funding_acknowledgment2,
|
352
|
-
author = {Damian, Bleotiu, Saraev, Constantinescu, Milik, Lupaescu},
|
353
|
-
title = {ReDeN – Rețea Descentralizată Neurală SMIS 156084},
|
354
|
-
howpublished = {\url{https://github.com/Naeural Edge ProtocolEdgeProtocol/}},
|
355
|
-
note = {This project includes open-source components developed with support from the Romanian Competitiveness Operational Programme under grants SMIS 143488. The content is solely the responsibility of the authors and does not necessarily reflect the views of the funding agencies.},
|
356
|
-
year = {2023-2024}
|
357
|
-
}
|
358
|
-
```
|
File without changes
|
File without changes
|
File without changes
|