naeural-client 2.6.24__py3-none-any.whl → 2.6.26__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 CHANGED
@@ -1,4 +1,4 @@
1
- __VER__ = "2.6.24"
1
+ __VER__ = "2.6.26"
2
2
 
3
3
  if __name__ == "__main__":
4
4
  with open("pyproject.toml", "rt") as fd:
@@ -2664,6 +2664,7 @@ class GenericSession(BaseDecentrAIObject):
2664
2664
  df_only=False,
2665
2665
  debug=False,
2666
2666
  eth=False,
2667
+ all_info=False,
2667
2668
  ):
2668
2669
  """
2669
2670
  This function will return a Pandas dataframe known nodes in the network based on
@@ -2698,6 +2699,9 @@ class GenericSession(BaseDecentrAIObject):
2698
2699
  eth: bool, optional
2699
2700
  If True, will use the nodes eth addresses instead of internal. Defaults to False.
2700
2701
 
2702
+ all_info: bool, optional
2703
+ If True, will return all the information. Defaults to False.
2704
+
2701
2705
  Returns
2702
2706
  -------
2703
2707
 
@@ -2723,11 +2727,19 @@ class GenericSession(BaseDecentrAIObject):
2723
2727
  'Oracle' : PAYLOAD_DATA.NETMON_IS_SUPERVISOR,
2724
2728
  'Peered' : PAYLOAD_DATA.NETMON_WHITELIST,
2725
2729
  })
2726
- reverse_mapping = {v: k for k, v in mapping.items()}
2730
+ if all_info:
2731
+ mapping = OrderedDict({
2732
+ # we assign dummy integer values to the computed columns
2733
+ # and we will fillter them
2734
+ 'ETH Address': 1,
2735
+ **mapping
2736
+ })
2727
2737
  res = OrderedDict()
2728
2738
  for k in mapping:
2729
2739
  res[k] = []
2730
2740
 
2741
+ reverse_mapping = {v: k for k, v in mapping.items()}
2742
+
2731
2743
  result, elapsed = self.__wait_for_supervisors_net_mon_data(
2732
2744
  supervisor=supervisor,
2733
2745
  timeout=timeout,
@@ -2759,6 +2771,8 @@ class GenericSession(BaseDecentrAIObject):
2759
2771
  if supervisors_only and not is_supervisor:
2760
2772
  continue
2761
2773
  for key, column in reverse_mapping.items():
2774
+ if isinstance(key, int):
2775
+ continue
2762
2776
  val = node_info.get(key, None)
2763
2777
  if key == PAYLOAD_DATA.NETMON_LAST_REMOTE_TIME:
2764
2778
  # val hols a string '2024-12-23 23:50:16.462155' and must be converted to a datetime
@@ -2772,7 +2786,10 @@ class GenericSession(BaseDecentrAIObject):
2772
2786
  # again self.get_node_name(best_super) might not work if using the hb data
2773
2787
  best_super_alias = node_info.get(PAYLOAD_DATA.NETMON_EEID, None)
2774
2788
  val = self.bc_engine._add_prefix(val)
2775
- if eth:
2789
+ if all_info:
2790
+ val_eth = self.bc_engine.node_address_to_eth_address(val)
2791
+ res['ETH Address'].append(val_eth)
2792
+ elif eth:
2776
2793
  val = self.bc_engine.node_address_to_eth_address(val)
2777
2794
  elif key == PAYLOAD_DATA.NETMON_WHITELIST:
2778
2795
  val = client_is_allowed
naeural_client/bc/base.py CHANGED
@@ -621,6 +621,7 @@ class BaseBlockEngine:
621
621
  address = self._remove_prefix(address)
622
622
  address = BCct.ADDR_PREFIX + address
623
623
  return address
624
+
624
625
 
625
626
 
626
627
  def _pk_to_address(self, public_key):
@@ -915,6 +916,23 @@ class BaseBlockEngine:
915
916
  the address without the prefix.
916
917
  """
917
918
  return self._remove_prefix(address)
919
+
920
+
921
+ def maybe_add_prefix(self, address):
922
+ """
923
+ Adds the prefix to the address
924
+
925
+ Parameters
926
+ ----------
927
+ address : str
928
+ the text address.
929
+
930
+ Returns
931
+ -------
932
+ address : str
933
+ the address with the prefix.
934
+ """
935
+ return self._add_prefix(address)
918
936
 
919
937
 
920
938
  def dict_digest(self, dct_data, return_str=True):
@@ -1235,6 +1253,52 @@ class BaseBlockEngine:
1235
1253
  def eth_account(self):
1236
1254
  return self.__eth_account
1237
1255
 
1256
+ @staticmethod
1257
+ def is_valid_evm_address(address: str) -> bool:
1258
+ """
1259
+ Check if the input string is a valid Ethereum (EVM) address using basic heuristics.
1260
+
1261
+ Parameters
1262
+ ----------
1263
+ address : str
1264
+ The address string to verify.
1265
+
1266
+ Returns
1267
+ -------
1268
+ bool
1269
+ True if `address` meets the basic criteria for an EVM address, False otherwise.
1270
+ """
1271
+ # Basic checks:
1272
+ # A) Must start with '0x'
1273
+ # B) Must be exactly 42 characters in total
1274
+ # C) All remaining characters must be valid hexadecimal digits
1275
+ if not address.startswith("0x"):
1276
+ return False
1277
+ if len(address) != 42:
1278
+ return False
1279
+
1280
+ hex_part = address[2:]
1281
+ # Ensure all characters in the hex part are valid hex digits
1282
+ return all(c in "0123456789abcdefABCDEF" for c in hex_part)
1283
+
1284
+ @staticmethod
1285
+ def is_valid_eth_address(address: str) -> bool:
1286
+ """
1287
+ Check if the input string is a valid Ethereum (EVM) address using basic heuristics.
1288
+
1289
+ Parameters
1290
+ ----------
1291
+ address : str
1292
+ The address string to verify.
1293
+
1294
+ Returns
1295
+ -------
1296
+ bool
1297
+ True if `address` meets the basic criteria for an EVM address, False otherwise.
1298
+ """
1299
+ return BaseBlockEngine.is_valid_evm_address(address)
1300
+
1301
+
1238
1302
  ### end Ethereum
1239
1303
 
1240
1304
 
@@ -1,7 +1,8 @@
1
1
  from naeural_client.cli.nodes import (
2
2
  get_nodes, get_supervisors,
3
- restart_node, shutdown_node
3
+ restart_node, shutdown_node,
4
4
  )
5
+ from naeural_client.cli.oracles import get_availability
5
6
  from naeural_client.utils.config import show_config, reset_config, show_address
6
7
 
7
8
 
@@ -17,12 +18,25 @@ CLI_COMMANDS = {
17
18
  "--online" : "Get only online nodes as seen by a active supervisor (flag)", # DONE
18
19
  "--peered": "Get only peered nodes - ie nodes that can be used by current client address (flag)", # DONE
19
20
  "--supervisor" : "Use a specific supervisor node",
20
- "--eth": "Use a specific node (flag)",
21
+ "--eth" : "Use a specific node (flag)",
22
+ "--wide" : "Display all available information (flag)",
21
23
  }
22
24
  },
23
25
  "supervisors": {
24
26
  "func": get_supervisors, # DONE
25
27
  },
28
+ "avail": {
29
+ "func": get_availability,
30
+ "params": {
31
+ ### use "(flag)" at the end of the description to indicate a boolean flag
32
+ ### otherwise it will be treated as a str parameter
33
+ "node": "The ETH address of the node to be checked via the oracle network.",
34
+ "--start": "The start epoch number to check the availability from",
35
+ "--end": "The end epoch number to check the availability to",
36
+ "--json": "Enable full JSON oracle network output (flag)",
37
+ "--rounds": "The number of rounds to check the availability for testing purposes (default=1)",
38
+ }
39
+ }
26
40
  },
27
41
  "config": {
28
42
  "show": {
@@ -11,6 +11,7 @@ def _get_netstats(
11
11
  supervisors_only=False,
12
12
  return_session=False,
13
13
  eth=False,
14
+ all_info=False
14
15
  ):
15
16
  t1 = time()
16
17
  from naeural_client import Session
@@ -19,6 +20,7 @@ def _get_netstats(
19
20
  online_only=online_only, allowed_only=allowed_only, supervisor=supervisor,
20
21
  supervisors_only=supervisors_only,
21
22
  eth=eth,
23
+ all_info=all_info,
22
24
  )
23
25
  df = dct_info[SESSION_CT.NETSTATS_REPORT]
24
26
  supervisor = dct_info[SESSION_CT.NETSTATS_REPORTER]
@@ -31,6 +33,7 @@ def _get_netstats(
31
33
  return df, supervisor, super_alias, nr_supers, elapsed
32
34
 
33
35
 
36
+
34
37
  def get_nodes(args):
35
38
  """
36
39
  This function is used to get the information about the nodes and it will perform the following:
@@ -40,7 +43,8 @@ def get_nodes(args):
40
43
  3. Wait for the second net mon message via Session and show progress.
41
44
  4. Get the active nodes union via Session and display the nodes marking those peered vs non-peered.
42
45
  """
43
- supervisor_addr = args.supervisor
46
+ supervisor_addr = args.supervisor
47
+ wide = args.wide
44
48
  if args.verbose:
45
49
  log_with_color(f"Getting nodes from supervisor <{supervisor_addr}>...", color='b')
46
50
 
@@ -50,6 +54,7 @@ def get_nodes(args):
50
54
  allowed_only=args.peered,
51
55
  supervisor=supervisor_addr,
52
56
  eth=args.eth,
57
+ all_info=wide,
53
58
  )
54
59
  df, supervisor, super_alias, nr_supers, elapsed = res
55
60
  if args.online:
@@ -150,4 +155,5 @@ def shutdown_node(args):
150
155
  node = args.node
151
156
  log_with_color(f"Attempting to shutdown node <{node}>", color='b')
152
157
  _send_command_to_node(args, COMMANDS.SHUTDOWN)
153
- return
158
+ return
159
+
@@ -0,0 +1,165 @@
1
+ """
2
+
3
+
4
+ NOTE: if any of oracles return data["result"]["oracle"]["manager"]["valid"] != True then
5
+ - ommit that oracle from the list of oracles shown
6
+ - display red warning containing the issue and the "certainty"
7
+
8
+ >nepctl get avail 0x693369781001bAC65F653856d0C00fA62129F407 --start 4 --end 6 --rounds 8
9
+
10
+ Availability of node <0x693369781001bAC65F653856d0C00fA62129F407> from epoch 4 to epoch 6 on 8 rounds:
11
+ Oracle #1:
12
+ Address: 0xai_AleLPKqUHV-iPc-76-rUvDkRWW4dFMIGKW1xFVcy65nH
13
+ ETH Addr: 0xE486F0d594e9F26931fC10c29E6409AEBb7b5144
14
+ Alias: nen-aid01
15
+ Responses: 3
16
+ Epochs: [ 4, 5, 6]
17
+ Avails: [ 3, 254, 127]
18
+ Cartainty: [0.99, 0.99, 0.99]
19
+ Oracle #2:
20
+ Address: 0xai_Amfnbt3N-qg2-qGtywZIPQBTVlAnoADVRmSAsdDhlQ-6
21
+ ETH Addr: 0x129a21A78EBBA79aE78B8f11d5B57102950c1Fc0
22
+ Alias: nen-2
23
+ Responses: 2
24
+ Epochs: [ 4, 5, 6]
25
+ Avails: [ 3, 254, 127]
26
+ Cartainty: [0.99, 0.99, 0.99]
27
+ Oracle #3: [RED due to not data["result"]["oracle"]["manager"]["valid"]]
28
+ WARNING: Oracle returned invalid data due to uncertainity
29
+ Address: 0xai_A-Bn9grkqH1GUMTZUqHNzpX5DA6PqducH9_JKAlBx6YL
30
+ ETH Addr: 0x93B04EF1152D81A0847C2272860a8a5C70280E14
31
+ Alias: nen-aid02
32
+ Responses: 3
33
+ Epochs: [ 4, 5, 6]
34
+ Avails: [ 3, 0, 127]
35
+ Cartainty: [0.99, 0.41, 0.99]
36
+
37
+
38
+ >nepctl get avail 0x693369781001bAC65F653856d0C00fA62129F407 --start 4 --end 6 --rounds 8
39
+
40
+ Availability of node <0x693369781001bAC65F653856d0C00fA62129F407> from epoch 4 to epoch 6 on 8 rounds:
41
+ Oracle #1:
42
+ Address: 0xai_AleLPKqUHV-iPc-76-rUvDkRWW4dFMIGKW1xFVcy65nH
43
+ ETH Addr: 0xE486F0d594e9F26931fC10c29E6409AEBb7b5144
44
+ Alias: nen-aid01
45
+ Responses: 3
46
+ Avails: [3, 254, 127]
47
+ Oracle #2:
48
+ Address: 0xai_Amfnbt3N-qg2-qGtywZIPQBTVlAnoADVRmSAsdDhlQ-6
49
+ ETH Addr: 0x129a21A78EBBA79aE78B8f11d5B57102950c1Fc0
50
+ Alias: nen-2
51
+ Responses: 3
52
+ Avails: [3, 254, 127]
53
+ Oracle #3:
54
+ Address: 0xai_A-Bn9grkqH1GUMTZUqHNzpX5DA6PqducH9_JKAlBx6YL
55
+ ETH Addr: 0x93B04EF1152D81A0847C2272860a8a5C70280E14
56
+ Alias: nen-aid02
57
+ Responses: 3
58
+ Avails: [3, 254, 127]
59
+
60
+
61
+
62
+ >nepctl get avail 0x693369781001bAC65F653856d0C00fA62129F407 --start 4 --end 6 --rounds 8
63
+ [if same oracle returns different avail dump the two confligting json with RED and stop command]
64
+
65
+ >nepctl get avail 0x693369781001bAC65F653856d0C00fA62129F407 --start 4 --end 6 --full
66
+ [just dump json]
67
+
68
+
69
+ >nepctl get avail 0x693369781001bAC65F653856d0C00fA62129F407 --start 4 --end 6
70
+ Availability of node <0x693369781001bAC65F653856d0C00fA62129F407> from epoch 4 to epoch 6:
71
+ Oracle address: 0xE486F0d594e9F26931fC10c29E6409AEBb7b5144
72
+ Oracle alias: nen-aid01
73
+ Oracle report:
74
+ - Epoch #4: 127 ( 50%)
75
+ - Epoch #5: 254 (100%)
76
+ - Epoch #6: 3 ( 1%)
77
+
78
+ >nepctl get avail 0x693369781001bAC65F653856d0C00fA62129F407 # assuming current epoch is 10
79
+ Availability of node <0x693369781001bAC65F653856d0C00fA62129F407> from epoch 1 to epoch 9:
80
+ Oracle address: 0xE486F0d594e9F26931fC10c29E6409AEBb7b5144
81
+ Oracle alias: nen-aid01
82
+ Oracle report:
83
+ - Epoch #1: 127 ( 50%)
84
+ - Epoch #2: 127 ( 50%)
85
+ - Epoch #3: 127 ( 50%)
86
+ - Epoch #4: 127 ( 50%)
87
+ - Epoch #5: 254 (100%)
88
+ - Epoch #6: 3 ( 1%)
89
+ - Epoch #7: 64 ( 25%)
90
+ - Epoch #8: 33 ( 13%)
91
+ - Epoch #9: 254 (100%)
92
+
93
+
94
+ TODO: (future)
95
+ - check ETH signature of the oracle data
96
+
97
+ """
98
+ import requests
99
+
100
+ from naeural_client.utils.config import log_with_color
101
+ from naeural_client import Logger
102
+ from naeural_client.bc import DefaultBlockEngine
103
+ from naeural_client.utils.oracle_sync.multiple_requests import oracle_tester_init, handle_command_results
104
+
105
+ """
106
+ TODOs:
107
+ - test NEPCTL command in CLI too
108
+ - check ETH signature of the oracle data
109
+ """
110
+
111
+
112
+ def _check_response(data):
113
+ res = True
114
+ log = Logger("NEPCTL", base_folder=".", app_folder="_local_cache", silent=True)
115
+ bc = DefaultBlockEngine(name='test', log=log)
116
+ print(bc.address)
117
+ return res
118
+
119
+ def get_availability(args):
120
+ """
121
+ This function is used to get the availability of the node.
122
+
123
+ Parameters
124
+ ----------
125
+ args : argparse.Namespace
126
+ Arguments passed to the function.
127
+ """
128
+
129
+ node = args.node
130
+ start = args.start or 1
131
+ end = args.end
132
+ full = args.json
133
+ rounds = args.rounds or 1
134
+ if str(rounds).isnumeric() and int(rounds) > 0:
135
+ rounds = int(rounds)
136
+ else:
137
+ log_with_color("`rounds` must be a positive integer. Setting rounds to 1.", color='r')
138
+ rounds = 1
139
+ # endif rounds
140
+
141
+ if isinstance(rounds, int) and rounds > 10:
142
+ log_with_color("Rounds exceed the maximum limit of 10. Setting rounds to 10.", color='r')
143
+ rounds = min(rounds, 10)
144
+
145
+ if full:
146
+ if rounds > 1:
147
+ log_with_color("Cannot generate full JSON oracle network output in 'rounds' mode.", color='r')
148
+ full = False
149
+ # endif full
150
+
151
+ tester = oracle_tester_init()
152
+ log_with_color("Checking {}availability of node <{}> from {} to {}.".format(
153
+ "(DEBUG MODE) " if rounds > 1 else "", node, start,
154
+ end if end else "last epoch"
155
+ ), color='b'
156
+ )
157
+ res = tester.execute_command(
158
+ node_eth_addr=node,
159
+ start=start,
160
+ end=end,
161
+ rounds=rounds,
162
+ debug=full
163
+ )
164
+ handle_command_results(res)
165
+ return
@@ -0,0 +1,585 @@
1
+ """
2
+ TODO: add signature check for the oracle data
3
+
4
+ """
5
+
6
+ import requests
7
+ import time
8
+ import json
9
+ from collections import defaultdict
10
+
11
+ from naeural_client import Logger
12
+ from naeural_client.bc import DefaultBlockEngine
13
+ from naeural_client.utils.config import log_with_color
14
+
15
+
16
+ class OracleTesterConstants:
17
+ BASE_URL = "https://naeural-oracle.ngrok.app"
18
+ TEST_ENDPOINT = "/node_epochs_range"
19
+ CURRENT_EPOCH_ENDPOINT = "/current_epoch"
20
+ ACTIVE_NODES_ENDPOINT = "/active_nodes_list"
21
+ DEFAULT_INTERVAL_SECONDS = 5
22
+ DEFAULT_COMMAND_INTERVAL_SECONDS = 1
23
+ MAX_REQUEST_ROUNDS = 10
24
+ FREQUENCY = "frequency"
25
+ ORACLE_DATA = "oracle_data"
26
+
27
+
28
+ ct = OracleTesterConstants
29
+
30
+
31
+ class OracleTester:
32
+ def __init__(
33
+ self, bce, log,
34
+ max_requests_rounds=ct.MAX_REQUEST_ROUNDS,
35
+ interval_seconds=ct.DEFAULT_INTERVAL_SECONDS
36
+ ):
37
+ self.bc = bce
38
+ self.log = log
39
+ self.BASE_URL = ct.BASE_URL
40
+ self.TEST_ENDPOINT = ct.TEST_ENDPOINT
41
+ self.CURRENT_EPOCH_ENDPOINT = ct.CURRENT_EPOCH_ENDPOINT
42
+ self.ACTIVE_NODES_ENDPOINT = ct.ACTIVE_NODES_ENDPOINT
43
+ self.request_rounds = 0
44
+ self.max_request_rounds = max_requests_rounds
45
+ self.interval_seconds = interval_seconds
46
+
47
+ self.node_addr_to_alias = {}
48
+ self.alias_to_node_addr = {}
49
+ self.node_eth_addr_to_alias = {}
50
+ self.alias_to_node_eth_addr = {}
51
+ self.node_addr_to_node_eth_addr = {}
52
+ self.node_eth_addr_to_node_addr = {}
53
+ return
54
+
55
+ """UTILS"""
56
+ if True:
57
+ def maybe_register_node(self, node_addr: str, eth_address: str, alias: str = None):
58
+ if node_addr is None:
59
+ return
60
+ self.node_addr_to_alias[node_addr] = alias
61
+ self.node_eth_addr_to_alias[eth_address] = alias
62
+
63
+ self.node_addr_to_node_eth_addr[node_addr] = eth_address
64
+ self.node_eth_addr_to_node_addr[eth_address] = node_addr
65
+
66
+ if alias is None:
67
+ return
68
+
69
+ self.alias_to_node_addr[alias] = node_addr
70
+ self.alias_to_node_eth_addr[alias] = eth_address
71
+ return
72
+
73
+ def make_request(self, request_url, request_kwargs=None, debug=False):
74
+ request_kwargs = request_kwargs or {}
75
+ try:
76
+ if debug:
77
+ self.P(f"Making request to {request_url} with kwargs: {request_kwargs}")
78
+ response = requests.get(request_url, params=request_kwargs)
79
+ response.raise_for_status() # Raise an HTTPError if the status is not 2xx
80
+ return response.json() # Assuming the response is JSON
81
+ except requests.RequestException as e:
82
+ self.P(f"Request failed: {e}")
83
+ return None
84
+
85
+ def done(self, rounds=None):
86
+ if rounds is not None:
87
+ return self.request_rounds >= rounds
88
+ return self.request_rounds >= self.max_request_rounds
89
+
90
+ def P(self, msg, **kwargs):
91
+ self.log.P(msg, **kwargs)
92
+ return
93
+
94
+ def frequency_dict_to_str(self, freq_dict):
95
+ return '\n'.join([
96
+ f"\t{self.node_addr_to_alias.get(node_addr)} ({node_addr}): {freq}"
97
+ for node_addr, freq in freq_dict.items()
98
+ ])
99
+ """END UTILS"""
100
+
101
+ """RESPONSE HANDLING"""
102
+ if True:
103
+ def compute_epochs_availability_and_certainty(self, result: dict):
104
+ """
105
+ Compute the availability and certainty for a given result extracted from the response.
106
+ Parameters
107
+ ----------
108
+ result : dict
109
+ The result extracted from the response.
110
+
111
+ Returns
112
+ -------
113
+ tuple
114
+ A tuple containing the availability and certainty values.
115
+ """
116
+ epoch_ids = result.get("epochs")
117
+ epoch_vals = result.get("epochs_vals")
118
+ dict_certainty = result.get("oracle", {}).get("manager", {}).get("certainty", {})
119
+ is_valid = result.get("oracle", {}).get("manager", {}).get("valid", False)
120
+
121
+ current_epochs, current_avails, current_cert = [], [], []
122
+ for epoch_id, epoch_val in zip(epoch_ids, epoch_vals):
123
+ current_epochs.append(epoch_id)
124
+ current_avails.append(epoch_val)
125
+ current_cert.append(dict_certainty.get(str(epoch_id), 0))
126
+ # endfor epochs
127
+ return current_epochs, current_avails, current_cert, is_valid
128
+
129
+ def handle_server_data(
130
+ self, oracle_stats_dict: dict,
131
+ sender: str, sender_eth_addr: str, sender_node_alias: str,
132
+ result: dict
133
+ ):
134
+ """
135
+ Handle the data received from the oracle server.
136
+
137
+ Parameters
138
+ ----------
139
+ oracle_stats_dict : dict
140
+ The dictionary containing the oracle data received so far.
141
+ sender : str
142
+ The address of the sender.
143
+ sender_eth_addr : str
144
+ The ETH address of the sender.
145
+ sender_node_alias : str
146
+ The alias of the sender.
147
+ result : dict
148
+ The result extracted from the response.
149
+ """
150
+ if sender not in oracle_stats_dict:
151
+ oracle_stats_dict[sender] = {
152
+ 'addr': sender,
153
+ 'eth_addr': sender_eth_addr,
154
+ 'alias': sender_node_alias,
155
+ 'errors': []
156
+ }
157
+ # endif first time for this sender
158
+ current_stats = oracle_stats_dict[sender]
159
+ current_epochs, current_avails, current_certs, is_valid = self.compute_epochs_availability_and_certainty(result)
160
+ stats_epochs = current_stats.get("epochs", None)
161
+ stats_avails = current_stats.get("avails", None)
162
+ stats_certs = current_stats.get("certs", None)
163
+ stats_is_valid = current_stats.get("is_valid", None)
164
+ mismatches = []
165
+ if stats_epochs is not None and current_epochs != stats_epochs:
166
+ mismatches.append(f"epochs: {current_epochs} != {stats_epochs}")
167
+ # endif check for mismatch
168
+ if stats_avails is not None and current_avails != stats_avails:
169
+ mismatches.append(f"avails: {current_avails} != {stats_avails}")
170
+ # endif check for mismatch
171
+ if stats_certs is not None and current_certs != stats_certs:
172
+ mismatches.append(f"certainty: {current_certs} != {stats_certs}")
173
+ # endif check for mismatch
174
+ if stats_is_valid is not None and is_valid != stats_is_valid:
175
+ mismatches.append(f"validity: {is_valid} != {stats_is_valid}")
176
+ # endif check for mismatch
177
+
178
+ if len(mismatches) > 0:
179
+ current_stats["errors"].append(f"Round {self.request_rounds}: {', '.join(mismatches)}")
180
+ else:
181
+ # Valid data received
182
+ if stats_epochs is None:
183
+ current_stats["epochs"] = current_epochs
184
+ if stats_avails is None:
185
+ current_stats["avails"] = current_avails
186
+ if stats_certs is None:
187
+ current_stats["certs"] = current_certs
188
+ if stats_is_valid is None:
189
+ current_stats["is_valid"] = is_valid
190
+ # endif valid data received
191
+ return
192
+
193
+ def add_to_stats(
194
+ self,
195
+ stats_dict: dict,
196
+ response: dict,
197
+ node_data: dict,
198
+ debug=False
199
+ ):
200
+ node_eth_addr = node_data["eth_address"]
201
+ node_addr = node_data.get("address")
202
+ node_alias = node_data.get("alias")
203
+ self.maybe_register_node(node_addr=node_addr, eth_address=node_eth_addr, alias=node_alias)
204
+ result = response.get("result")
205
+ if not result:
206
+ return
207
+ sender = result.get("EE_SENDER")
208
+ sender_eth_addr = result.get("EE_ETH_SENDER")
209
+ sender_node_alias = result.get("server_alias")
210
+ self.maybe_register_node(node_addr=sender, eth_address=sender_eth_addr, alias=sender_node_alias)
211
+ if node_eth_addr not in stats_dict:
212
+ stats_dict[node_eth_addr] = {
213
+ **node_data,
214
+ ct.FREQUENCY: {},
215
+ ct.ORACLE_DATA: {}
216
+ }
217
+ # endif first time for this node
218
+ stats_dict[node_eth_addr][ct.FREQUENCY][sender] = stats_dict[node_eth_addr][ct.FREQUENCY].get(sender, 0) + 1
219
+
220
+ self.handle_server_data(
221
+ oracle_stats_dict=stats_dict[node_eth_addr][ct.ORACLE_DATA],
222
+ sender=sender,
223
+ sender_eth_addr=sender_eth_addr,
224
+ sender_node_alias=sender_node_alias,
225
+ result=result
226
+ )
227
+
228
+ return stats_dict
229
+ """END RESPONSE HANDLING"""
230
+
231
+ def gather(self, nodes, request_kwargs=None, debug=False, rounds=None):
232
+ """
233
+ Gather data from the oracle server for the given nodes.
234
+
235
+ Parameters
236
+ ----------
237
+ nodes : list[dict] or list[str]
238
+ The list of nodes for which to gather data. Each node can be a dictionary containing the
239
+ address, eth_address, and alias of the node or a string containing the eth_address of the node.
240
+ Either way, the eth_address is required.
241
+ request_kwargs : dict
242
+ The request kwargs to be used for the request. Default None.
243
+ debug : bool
244
+ Whether to enable debug mode or not. If enabled the function will exit after one request round.
245
+
246
+ Returns
247
+ -------
248
+ tuple
249
+ A tuple containing the responses and the stats dictionary.
250
+ """
251
+ responses = []
252
+ request_kwargs = request_kwargs or {}
253
+ stats_dict = {}
254
+ self.request_rounds = 0
255
+ while not self.done(rounds):
256
+ try:
257
+ self.P(f'Starting request round {self.request_rounds + 1} for {len(nodes)} nodes...')
258
+ current_url = self.BASE_URL + self.TEST_ENDPOINT
259
+ # TODO: maybe shuffle the nodes list in order to avoid
260
+ # the same order of requests in each round
261
+ # relevant if the number of nodes is divisible by the number of oracles.
262
+ for node_data in nodes:
263
+ if isinstance(node_data, str):
264
+ node_data = {"eth_address": node_data}
265
+ # endif only eth address provided
266
+ eth_addr = node_data.get("eth_address", "N/A")
267
+ node_alias = node_data.get("alias", eth_addr)
268
+ node_addr = node_data.get("address", eth_addr)
269
+ self.P(f'\tRequesting data for {node_alias}...')
270
+ current_kwargs = {
271
+ "eth_node_addr": eth_addr,
272
+ **request_kwargs
273
+ }
274
+ response = self.make_request(current_url, request_kwargs=current_kwargs, debug=debug)
275
+ if response:
276
+ responses.append(response)
277
+ str_sender = response.get("node_addr")
278
+ self.P(f"Received response from {str_sender} with keys: {response.get('result').keys()}")
279
+ stats_dict = self.add_to_stats(
280
+ stats_dict=stats_dict,
281
+ response=response,
282
+ node_data=node_data,
283
+ debug=debug
284
+ )
285
+ if debug:
286
+ self.P(f'Full response: {response}')
287
+ else:
288
+ self.P(f"Request failed for {node_data['alias']}")
289
+ # endfor nodes
290
+ except Exception as e:
291
+ self.P(f"Request failed: {e}")
292
+ self.request_rounds += 1
293
+ if debug:
294
+ self.P(f'Debug mode was enabled. Exiting after one request round.')
295
+ break
296
+ time.sleep(self.interval_seconds)
297
+ # endwhile
298
+ self.P(f'Finished gathering data for {len(nodes)} nodes and {self.max_request_rounds}.')
299
+ return responses, stats_dict
300
+
301
+ def get_current_epoch(self):
302
+ epoch_url = self.BASE_URL + self.CURRENT_EPOCH_ENDPOINT
303
+ response = self.make_request(epoch_url)
304
+ if response:
305
+ return response.get("result", {}).get("current_epoch", 1)
306
+ return None
307
+
308
+ def get_active_nodes(self):
309
+ active_nodes_url = self.BASE_URL + self.ACTIVE_NODES_ENDPOINT
310
+ response = self.make_request(active_nodes_url)
311
+ result = []
312
+ if response:
313
+ nodes = response.get("result", {}).get("nodes", {})
314
+ for node_addr, node_data in nodes.items():
315
+ eth_addr = node_data.get("eth_addr", None)
316
+ alias = node_data.get("alias", None)
317
+ if eth_addr is not None:
318
+ result.append({
319
+ "address": node_addr,
320
+ "eth_address": eth_addr,
321
+ "alias": alias
322
+ })
323
+ # endif eth address is not None
324
+ # endfor each node
325
+ # endif response
326
+ return result
327
+
328
+ """LOGGING"""
329
+ if True:
330
+ def check_data(self, oracle_data: dict, node_eth_addr: str):
331
+ valid, msg = True, ""
332
+ if oracle_data is None:
333
+ # If oracle_data is None, either the address was invalid or the servers are down.
334
+ # Will attempt request for current epoch to check the servers status.
335
+ current_epoch = self.get_current_epoch()
336
+ valid = False
337
+ if current_epoch is None:
338
+ msg = f"Failed to get the current epoch. No oracle available. Please try again later."
339
+ else:
340
+ msg = f"No data available for {node_eth_addr}. Please check the address or contact support."
341
+ # endif servers available
342
+ # endif oracle_data is None
343
+ return valid, msg
344
+
345
+ def get_availability_str_for_one_round(
346
+ self, node_eth_addr: str, start: int, end: int, stats_dict: dict
347
+ ):
348
+ oracle_data = stats_dict.get(node_eth_addr, {}).get(ct.ORACLE_DATA, None)
349
+ valid, msg = self.check_data(oracle_data, node_eth_addr)
350
+ if not valid:
351
+ return msg
352
+
353
+ msg = f'Availability for <{node_eth_addr}> from epoch {start} to epoch {end}:\n'
354
+ sender_addr = list(oracle_data.keys())[0]
355
+ sender_data = oracle_data[sender_addr]
356
+ oracle_addr = sender_data.get("addr", None)
357
+ oracle_addr_eth = sender_data.get("eth_addr", None)
358
+ oracle_alias = sender_data.get("alias", None)
359
+ msg += f' Oracle address: {oracle_addr}\n'
360
+ msg += f' Oracle ETH addr: {oracle_addr_eth}\n'
361
+ msg += f' Oracle alias: {oracle_alias}\n'
362
+ msg += f' Oracle responses:\n'
363
+ epochs = sender_data.get("epochs", None)
364
+ avails = sender_data.get("avails", None)
365
+ certs = sender_data.get("certs", None)
366
+ if epochs is None or avails is None or certs is None:
367
+ msg = f"No data available for {node_eth_addr}. Please check the address or contact support."
368
+ else:
369
+ for epoch, avail, cert in zip(epochs, avails, certs):
370
+ msg += f" - Epoch {f'#{epoch}':>4}: {avail:3} ({cert * 100:5.1f}%)\n"
371
+ # endif data available
372
+ return msg
373
+
374
+ def get_availability_str_for_multiple_rounds(
375
+ self,
376
+ node_eth_addr: str, start: int, end: int, stats_dict: dict, rounds: int
377
+ ):
378
+ oracle_data = stats_dict.get(node_eth_addr, {}).get(ct.ORACLE_DATA, None)
379
+ valid, msg = self.check_data(oracle_data, node_eth_addr)
380
+ if not valid:
381
+ return [msg]
382
+ msg_list = [f'Availability for <{node_eth_addr}> from epoch {start} to epoch {end} on {rounds} rounds:\n']
383
+ frequencies = stats_dict.get(node_eth_addr, {}).get(ct.FREQUENCY, {})
384
+ it = 0
385
+ for sender, sender_data in oracle_data.items():
386
+ curr_msg = f'\tOracle #{it + 1}:\n'
387
+ is_valid = sender_data.get("is_valid", False)
388
+ if len(sender_data["errors"]) > 0:
389
+ is_valid = False
390
+ curr_msg += f'\t\t Error!! Same oracle returned different data in different rounds:\n'
391
+ for error in sender_data["errors"]:
392
+ curr_msg += f'\t\t\t {error}\n'
393
+ # endfor errors
394
+ else:
395
+ # No errors
396
+ if not is_valid:
397
+ curr_msg += '\t\t WARNING: Oracle returned invalid data due to uncertainity\n'
398
+ # endif uncertainty
399
+ # endif errors
400
+ color = None if is_valid else 'r'
401
+ str_epochs = ' '.join([f'{epoch:4}' for epoch in sender_data["epochs"]])
402
+ str_avails = ' '.join([f'{avail:4}' for avail in sender_data["avails"]])
403
+ str_certs = ' '.join([f'{cert:4.2f}' for cert in sender_data["certs"]])
404
+ curr_msg += f'\t\t Address: {sender_data["addr"]}\n'
405
+ curr_msg += f'\t\t ETH Addr: {sender_data["eth_addr"]}\n'
406
+ curr_msg += f'\t\t Alias: {sender_data["alias"]}\n'
407
+ curr_msg += f'\t\t Responses: {frequencies.get(sender, 0)}\n'
408
+ curr_msg += f'\t\t Epochs: {str_epochs}\n'
409
+ curr_msg += f'\t\t Avails: {str_avails}\n'
410
+ curr_msg += f'\t\t Certainty: {str_certs}\n'
411
+ msg_list.append((curr_msg, color))
412
+ it += 1
413
+ # endfor oracles
414
+ return msg_list
415
+ """END LOGGING"""
416
+
417
+ def execute_command(
418
+ self, node_eth_addr: str, start: int = 1,
419
+ end: int = None, debug: bool = False,
420
+ rounds: int = 1
421
+ ):
422
+ """
423
+ Execute the command to get the availability of the node on a given epochs interval.
424
+ This can also be used to get the JSON response from the server(with the use of debug=True).
425
+ In case of debug mode, the rounds parameter is ignored(will default to 1).
426
+ In case of multiple rounds, a delay of interval_seconds will be added between each request.
427
+ Parameters
428
+ ----------
429
+ node_eth_addr : str
430
+ The ETH address of the node for which the command will be executed.
431
+ start : int
432
+ The starting epoch for the request.
433
+ end : int
434
+ The ending epoch for the request. If None, the current epoch will be used.
435
+ debug : bool
436
+ Whether to enable debug mode or not. If enabled the rounds parameter is ignored and the
437
+ function will return the JSON response from the server.
438
+ rounds : int
439
+ The number of rounds to be executed. Default 1.
440
+
441
+ Returns
442
+ -------
443
+ dict:
444
+ The JSON response from the server if in debug mode.
445
+ The stats summary if not in debug mode.
446
+ """
447
+ rounds = min(rounds, self.max_request_rounds)
448
+ if end is None:
449
+ current_epoch = self.get_current_epoch()
450
+ if current_epoch is None:
451
+ self.P(f"Failed to get the current epoch. No oracle available. Please try again later.", show=True)
452
+ return
453
+ self.P(f'No end epoch provided. Using current epoch: {current_epoch}', show=True)
454
+ end = self.get_current_epoch() - 1
455
+ # endif end is None
456
+ request_kwargs = {
457
+ "start_epoch": start,
458
+ "end_epoch": end
459
+ }
460
+ responses, stats = self.gather(
461
+ nodes=[node_eth_addr],
462
+ request_kwargs=request_kwargs,
463
+ rounds=1 if debug else rounds,
464
+ )
465
+ if debug:
466
+ return responses[0]
467
+ if rounds > 1:
468
+ return self.get_availability_str_for_multiple_rounds(
469
+ node_eth_addr=node_eth_addr, start=start, end=end, stats_dict=stats, rounds=rounds
470
+ )
471
+
472
+ return self.get_availability_str_for_one_round(
473
+ node_eth_addr=node_eth_addr, start=start, end=end, stats_dict=stats
474
+ )
475
+
476
+ # endclass OracleTester
477
+
478
+ def handle_command_results(res):
479
+ if isinstance(res, dict):
480
+ log_with_color(json.dumps(res, indent=2), color='w')
481
+ elif isinstance(res, list):
482
+ for msg_data in res:
483
+ if isinstance(msg_data, tuple):
484
+ log_with_color(msg_data[0], color=msg_data[1])
485
+ else:
486
+ log_with_color(msg_data, color='w')
487
+ # endfor each message
488
+ else:
489
+ log_with_color(res, color='w')
490
+ return
491
+
492
+ def oracle_tester_init(silent=True, **kwargs):
493
+ log = Logger("R1CTL", base_folder=".", app_folder="_local_cache", silent=silent)
494
+ bc = DefaultBlockEngine(name='R1CTL', log=log)
495
+ tester = OracleTester(
496
+ bce=bc,
497
+ log=log,
498
+ **kwargs
499
+ )
500
+ return tester
501
+
502
+ def test_commands():
503
+ tester = oracle_tester_init()
504
+ start = 78
505
+ end = 85
506
+ node_eth_addr = "<node_eth_address>"
507
+
508
+ # Single round
509
+ tester.P(f'Test single round: Epochs {start} to {end}', show=True)
510
+ res = tester.execute_command(node_eth_addr=node_eth_addr, start=start, end=end)
511
+ handle_command_results(res)
512
+
513
+ # Multiple rounds
514
+ tester.P(f'Test multiple rounds: Epochs {start} to {end}', show=True)
515
+ res = tester.execute_command(node_eth_addr=node_eth_addr, start=start, end=end, rounds=5)
516
+ handle_command_results(res)
517
+
518
+ # Debug mode
519
+ tester.P(f'Test debug mode: Epochs {start} to {end}', show=True)
520
+ res = tester.execute_command(node_eth_addr=node_eth_addr, start=80, end=85, debug=True)
521
+ handle_command_results(res)
522
+ return
523
+
524
+ def oracle_test(N=10):
525
+ import random
526
+
527
+ random.seed(42)
528
+
529
+ tester = oracle_tester_init(
530
+ silent=True,
531
+ interval_seconds=0.2,
532
+ )
533
+ current_epoch = tester.get_current_epoch()
534
+ if current_epoch is None:
535
+ current_epoch = 96
536
+ nodes = tester.get_active_nodes()
537
+ rounds = 5
538
+ max_epochs = 10
539
+ max_nodes = 5
540
+
541
+ for i in range(N):
542
+ node_list = random.sample(nodes, min(len(nodes), max_nodes))
543
+ start = random.randint(1, current_epoch - 1)
544
+ end = random.randint(start, current_epoch - 1)
545
+ end = min(end, start + max_epochs - 1)
546
+
547
+ tester.P(f'Test {i + 1}/{N}: Epochs {start} to {end} with {rounds} rounds:', show=True)
548
+ res = tester.gather(
549
+ nodes=node_list,
550
+ request_kwargs={
551
+ "start_epoch": start,
552
+ "end_epoch": end
553
+ },
554
+ rounds=rounds
555
+ )
556
+
557
+ for node_data in node_list:
558
+ msg_list = tester.get_availability_str_for_multiple_rounds(
559
+ node_eth_addr=node_data['eth_address'], start=start, end=end, stats_dict=res[1], rounds=rounds
560
+ )
561
+ for msg_data in msg_list:
562
+ if isinstance(msg_data, tuple):
563
+ tester.P(msg_data[0], color=msg_data[1], show=True)
564
+ else:
565
+ tester.P(msg_data, show=True)
566
+ # endfor each message
567
+ # endfor each node
568
+ tester.P(f'Finished test {i + 1}/{N}', show=True)
569
+ # endfor each test
570
+ return
571
+
572
+ # Main loop
573
+ def main():
574
+ TEST_COMMANDS = False
575
+ TEST_ORACLE = True
576
+ if TEST_COMMANDS:
577
+ test_commands()
578
+
579
+ if TEST_ORACLE:
580
+ oracle_test(5)
581
+ return
582
+
583
+
584
+ if __name__ == "__main__":
585
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: naeural_client
3
- Version: 2.6.24
3
+ Version: 2.6.26
4
4
  Summary: `naeural_client` is the Python SDK required for client app development for the Naeural Edge Protocol Edge Protocol framework
5
5
  Project-URL: Homepage, https://github.com/NaeuralEdgeProtocol/naeural_client
6
6
  Project-URL: Bug Tracker, https://github.com/NaeuralEdgeProtocol/naeural_client/issues
@@ -38,6 +38,12 @@ Key functionalities of the Ratio1 SDK include:
38
38
 
39
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
40
 
41
+ ## The `nepctl` CLI Tool
42
+
43
+ Our SDK has a CLI tool called `nepctl` that allows you to interact with the Ratio1 network. You can use it to query nodes, configure the client, and manage nodes directly from the terminal. The `nepctl` tool is a powerful utility that simplifies network interactions and provides a seamless experience for developers.
44
+
45
+ For more information on the `nepctl` CLI tool, please refer to the [nepctl](nepctl.md) documentation.
46
+
41
47
  ## Dependencies
42
48
 
43
49
  The Ratio1 SDK relies on several key packages to function effectively. These dependencies are automatically managed when installing the SDK via pip:
@@ -1,10 +1,10 @@
1
1
  naeural_client/__init__.py,sha256=YimqgDbjLuywsf8zCWE0EaUXH4MBUrqLxt0TDV558hQ,632
2
- naeural_client/_ver.py,sha256=sWoUittCitgXSrjKA9XzssfGD3VSmC7d_xj1j4vyhww,331
2
+ naeural_client/_ver.py,sha256=v41SsIkuU8mwgkOit-MvKm8l1U2wH1sGEPmEk-6Qf6Q,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=IrZqKNGrlyuNnncKzJkCzvreSFwRgalMH-LpLHZ-54Q,104513
7
+ naeural_client/base/generic_session.py,sha256=joF01ZoIt6QCSg7hDkg5M8ZCmKubpbQVh-plqi6GA9E,105079
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,15 +14,16 @@ 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=hiKibexlNpZWYfFjKH-Jdwe79p4rM6j4e0M3AYPZCNs,36169
17
+ naeural_client/bc/base.py,sha256=NafeKetxDoG86-gNls3FPN1M09nzRUP9unCUzEcXlrk,37706
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
21
21
  naeural_client/certs/r9092118.ala.eu-central-1.emqxsl.com.crt,sha256=y-6io0tseyx9-a4Pmde1z1gPULtJNSYUpG_YFkYaMKU,1337
22
22
  naeural_client/cli/README.md,sha256=WPdI_EjzAbUW1aPyj1sSR8rLydcJKZtoiaEtklQrjHo,74
23
23
  naeural_client/cli/cli.py,sha256=c0ZavVwArQEGv8tueTnz3aGKTuUe6Sqmm5oTUFUVIUE,3816
24
- naeural_client/cli/cli_commands.py,sha256=rORdskjcuWkoJ0VJH8Ednfp-b2_SX-xZ6B-cO6KLAPQ,2012
25
- naeural_client/cli/nodes.py,sha256=SYWe58F7ygxr2EpEJjh6b_VDvEEEnxwiw7SmAqQggn8,4829
24
+ naeural_client/cli/cli_commands.py,sha256=Gfims_TqaBi2QWM-6yxd_B_CETF8mmt7oEDPTg59lcI,2821
25
+ naeural_client/cli/nodes.py,sha256=QXSniYy3Phr6buoVV-HUDRYuF5YQDUgf6nT6EzDyMJw,4909
26
+ naeural_client/cli/oracles.py,sha256=g8h8kl3Mu6gU7JCs4a6WSDpvuVDGJgTHJbYJy7Iriw0,5210
26
27
  naeural_client/code_cheker/__init__.py,sha256=pwkdeZGVL16ZA4Qf2mRahEhoOvKhL7FyuQbMFLr1E5M,33
27
28
  naeural_client/code_cheker/base.py,sha256=lT5DRIFO5rqzsMNCmdMRfkAeevmezozehyfgmhnKpuI,19074
28
29
  naeural_client/code_cheker/checker.py,sha256=QWupeM7ToancVIq1tRUxRNUrI8B5l5eoY0kDU4-O5aE,7365
@@ -81,8 +82,9 @@ naeural_client/utils/__init__.py,sha256=mAnke3-MeRzz3nhQvhuHqLnpaaCSmDxicd7Ck9uw
81
82
  naeural_client/utils/comm_utils.py,sha256=4cS9llRr_pK_3rNgDcRMCQwYPO0kcNU7AdWy_LtMyCY,1072
82
83
  naeural_client/utils/config.py,sha256=v7xHikr6Z5Sbvf3opYeMhYzGWD2pe0HlRwa-aGJzUh8,6323
83
84
  naeural_client/utils/dotenv.py,sha256=_AgSo35n7EnQv5yDyu7C7i0kHragLJoCGydHjvOkrYY,2008
84
- naeural_client-2.6.24.dist-info/METADATA,sha256=-GGBISabxTk3Z4p7zbfqAlYRm4K7eAmqa-9q5tl7q6U,11906
85
- naeural_client-2.6.24.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
86
- naeural_client-2.6.24.dist-info/entry_points.txt,sha256=PNdyotDaQBAslZREx5luVyj0kqpQnwNACwkFNTPIHU4,55
87
- naeural_client-2.6.24.dist-info/licenses/LICENSE,sha256=cvOsJVslde4oIaTCadabXnPqZmzcBO2f2zwXZRmJEbE,11311
88
- naeural_client-2.6.24.dist-info/RECORD,,
85
+ naeural_client/utils/oracle_sync/multiple_requests.py,sha256=GLzROGZ0gI4d1PVWgW_JBUYZjEL4LqZvHvwelxDiPW4,20892
86
+ naeural_client-2.6.26.dist-info/METADATA,sha256=jvfB6kdLKoCkEHGyjhFJnTfmrH9Ya8J4fu4Z68ldclE,12354
87
+ naeural_client-2.6.26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
88
+ naeural_client-2.6.26.dist-info/entry_points.txt,sha256=PNdyotDaQBAslZREx5luVyj0kqpQnwNACwkFNTPIHU4,55
89
+ naeural_client-2.6.26.dist-info/licenses/LICENSE,sha256=cvOsJVslde4oIaTCadabXnPqZmzcBO2f2zwXZRmJEbE,11311
90
+ naeural_client-2.6.26.dist-info/RECORD,,