naeural-client 2.2.6__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
naeural_client/_ver.py CHANGED
@@ -1,4 +1,4 @@
1
- __VER__ = "2.2.6"
1
+ __VER__ = "2.3.0"
2
2
 
3
3
  if __name__ == "__main__":
4
4
  with open("pyproject.toml", "rt") as fd:
@@ -17,9 +17,12 @@ from ..utils import load_dotenv
17
17
  from .payload import Payload
18
18
  from .pipeline import Pipeline
19
19
  from .transaction import Transaction
20
+ from ..utils.config import load_user_defined_config, get_user_config_file
20
21
 
21
22
  # TODO: add support for remaining commands from EE
22
23
 
24
+ DEBUG_MQTT_SERVER = "r9092118.ala.eu-central-1.emqxsl.com"
25
+
23
26
 
24
27
  class GenericSession(BaseDecentrAIObject):
25
28
  """
@@ -59,7 +62,8 @@ class GenericSession(BaseDecentrAIObject):
59
62
  on_payload=None,
60
63
  on_notification=None,
61
64
  on_heartbeat=None,
62
- silent=True,
65
+ debug_silent=True,
66
+ silent=False,
63
67
  verbosity=1,
64
68
  dotenv_path=None,
65
69
  show_commands=False,
@@ -120,8 +124,14 @@ class GenericSession(BaseDecentrAIObject):
120
124
  Callback that handles heartbeats received from this network.
121
125
  As arguments, it has a reference to this Session object, the node name and the heartbeat payload.
122
126
  Defaults to None.
123
- silent : bool, optional
127
+
128
+ debug_silent : bool, optional
124
129
  This flag will disable debug logs, set to 'False` for a more verbose log, by default True
130
+
131
+ silent : bool, optional
132
+ This flag will disable all logs, set to 'False` for a more verbose log, by default False
133
+ The logs will still be recored in the log file even if this flag is set to True.
134
+
125
135
  dotenv_path : str, optional
126
136
  Path to the .env file, by default None. If None, the path will be searched in the current working directory and in the directories of the files from the call stack.
127
137
  root_topic : str, optional
@@ -142,6 +152,7 @@ class GenericSession(BaseDecentrAIObject):
142
152
 
143
153
  self.log = log
144
154
  self.name = name
155
+ self.silent = silent
145
156
 
146
157
  self._verbosity = verbosity
147
158
  self.encrypt_comms = encrypt_comms
@@ -158,7 +169,10 @@ class GenericSession(BaseDecentrAIObject):
158
169
  pwd = pwd or kwargs.get('password', kwargs.get('pass', None))
159
170
  user = user or kwargs.get('username', None)
160
171
  host = host or kwargs.get('hostname', None)
172
+
173
+ ## now we prepare config via ~/.naeural/config or .env
161
174
  self.__fill_config(host, port, user, pwd, secured, dotenv_path)
175
+ ## end config
162
176
 
163
177
  self.custom_on_payload = on_payload
164
178
  self.custom_on_heartbeat = on_heartbeat
@@ -180,15 +194,30 @@ class GenericSession(BaseDecentrAIObject):
180
194
  self.__open_transactions_lock = Lock()
181
195
 
182
196
  self.__create_user_callback_threads()
183
- super(GenericSession, self).__init__(log=log, DEBUG=not silent, create_logger=True)
197
+ super(GenericSession, self).__init__(
198
+ log=log,
199
+ DEBUG=not debug_silent,
200
+ create_logger=True,
201
+ silent=self.silent,
202
+ )
184
203
  return
185
204
 
186
205
  def startup(self):
187
- self.__start_blockchain(self.__bc_engine, self.__blockchain_config)
206
+ # start the blockchain engine assuming config is already set
207
+ self.__start_blockchain(
208
+ self.__bc_engine, self.__blockchain_config,
209
+ user_config=self.__user_config_loaded,
210
+ )
211
+ # end bc_engine
188
212
  self.formatter_wrapper = IOFormatterWrapper(self.log, plugin_search_locations=self.__formatter_plugins_locations)
189
213
 
190
214
  self._connect()
191
215
 
216
+ self.P("Created {} comms session '{}' from <{}> SDKv{}.".format(
217
+ "decrypted" if not self.encrypt_comms else "encrypted",
218
+ self.name, self.bc_engine.address, self.log.version
219
+ ))
220
+
192
221
  if not self.encrypt_comms:
193
222
  self.P(
194
223
  "Warning: Emitted messages will not be encrypted.\n"
@@ -546,7 +575,7 @@ class GenericSession(BaseDecentrAIObject):
546
575
 
547
576
  # Main loop
548
577
  if True:
549
- def __start_blockchain(self, bc_engine, blockchain_config):
578
+ def __start_blockchain(self, bc_engine, blockchain_config, user_config=False):
550
579
  if bc_engine is not None:
551
580
  self.bc_engine = bc_engine
552
581
  return
@@ -557,6 +586,7 @@ class GenericSession(BaseDecentrAIObject):
557
586
  name=self.name,
558
587
  config=blockchain_config,
559
588
  verbosity=self._verbosity,
589
+ user_config=user_config,
560
590
  )
561
591
  except:
562
592
  raise ValueError("Failure in private blockchain setup:\n{}".format(traceback.format_exc()))
@@ -780,15 +810,19 @@ class GenericSession(BaseDecentrAIObject):
780
810
  host : str
781
811
  The hostname of the server.
782
812
  Can be retrieved from the environment variables AIXP_HOSTNAME, AIXP_HOST
813
+
783
814
  port : int
784
815
  The port.
785
816
  Can be retrieved from the environment variable AIXP_PORT
817
+
786
818
  user : str
787
819
  The user name.
788
820
  Can be retrieved from the environment variables AIXP_USERNAME, AIXP_USER
821
+
789
822
  pwd : str
790
823
  The password.
791
824
  Can be retrieved from the environment variables AIXP_PASSWORD, AIXP_PASS, AIXP_PWD
825
+
792
826
  dotenv_path : str, optional
793
827
  Path to the .env file, by default None. If None, the path will be searched in the current working directory and in the directories of the files from the call stack.
794
828
 
@@ -798,11 +832,21 @@ class GenericSession(BaseDecentrAIObject):
798
832
  Missing credentials
799
833
  """
800
834
 
801
- # this method will search for the credentials in the environment variables
802
- # the path to env file, if not specified, will be search in the following order:
803
- # 1. current working directory
804
- # 2-N. directories of the files from the call stack
805
- load_dotenv(dotenv_path=dotenv_path, verbose=False)
835
+ # if the ~/.naeural/config file exists, load the credentials from there else try to load them from .env
836
+ if not load_user_defined_config():
837
+ # this method will search for the credentials in the environment variables
838
+ # the path to env file, if not specified, will be search in the following order:
839
+ # 1. current working directory
840
+ # 2-N. directories of the files from the call stack
841
+ load_dotenv(dotenv_path=dotenv_path, verbose=False)
842
+ if not self.silent:
843
+ print("Loaded credentials from environment variables.", flush=True)
844
+ self.__user_config_loaded = False
845
+ else:
846
+ if not self.silent:
847
+ print(f"Loaded credentials from `{get_user_config_file()}`.", flush=True)
848
+ self.__user_config_loaded = True
849
+ # endif config loading from ~ or ./.env
806
850
 
807
851
  possible_user_values = [
808
852
  user,
@@ -849,7 +893,7 @@ class GenericSession(BaseDecentrAIObject):
849
893
  os.getenv(ENVIRONMENT.EE_HOST),
850
894
  os.getenv(ENVIRONMENT.EE_MQTT_HOST),
851
895
  self._config.get(comm_ct.HOST),
852
- "r9092118.ala.eu-central-1.emqxsl.com",
896
+ DEBUG_MQTT_SERVER,
853
897
  ]
854
898
 
855
899
  host = next((x for x in possible_host_values if x is not None), None)
@@ -24,6 +24,7 @@ class BaseDecentrAIObject(object):
24
24
  show_prefixes=False,
25
25
  prefix_log=None,
26
26
  log_at_startup=False,
27
+ silent=False,
27
28
  **kwargs):
28
29
 
29
30
  super(BaseDecentrAIObject, self).__init__()
@@ -32,7 +33,13 @@ class BaseDecentrAIObject(object):
32
33
  if not create_logger:
33
34
  raise ValueError("Logger object is invalid: {}".format(log))
34
35
  else:
35
- log = Logger("DEF", DEBUG=DEBUG, base_folder='.', app_folder='_local_cache')
36
+ log = Logger(
37
+ "DEF",
38
+ DEBUG=DEBUG,
39
+ base_folder='.',
40
+ app_folder='_local_cache',
41
+ silent=silent,
42
+ )
36
43
  # endif
37
44
 
38
45
  self.log = log
@@ -79,7 +86,6 @@ class BaseDecentrAIObject(object):
79
86
  msg = "{} {}".format(self.prefix_log, s)
80
87
  # endif
81
88
  # endif
82
-
83
89
  _r = self.log.P(msg, show_time=t, color=color, **kwargs)
84
90
  return _r
85
91
 
naeural_client/bc/base.py CHANGED
@@ -12,6 +12,8 @@ from copy import deepcopy
12
12
 
13
13
  from cryptography.hazmat.primitives import serialization
14
14
 
15
+ from ..utils.config import get_user_folder
16
+
15
17
 
16
18
  class BCct:
17
19
  SIGN = 'EE_SIGN'
@@ -21,6 +23,7 @@ class BCct:
21
23
  ADDR_PREFIX_OLD = "aixp_"
22
24
  ADDR_PREFIX = "0xai_"
23
25
 
26
+ K_USER_CONFIG_PEM_FILE = 'NAEURAL_PEM_FILE'
24
27
  K_PEM_FILE = 'PEM_FILE'
25
28
  K_PASSWORD = 'PASSWORD'
26
29
  K_PEM_LOCATION = 'PEM_LOCATION'
@@ -270,7 +273,7 @@ class BaseBlockEngine:
270
273
  _lock: Lock = Lock()
271
274
  __instances = {}
272
275
 
273
- def __new__(cls, name, log, config, ensure_ascii_payloads=False, verbosity=1):
276
+ def __new__(cls, name, log, config, ensure_ascii_payloads=False, verbosity=1, user_config=False):
274
277
  with cls._lock:
275
278
  if name not in cls.__instances:
276
279
  instance = super(BaseBlockEngine, cls).__new__(cls)
@@ -278,6 +281,7 @@ class BaseBlockEngine:
278
281
  name=name, log=log, config=config,
279
282
  ensure_ascii_payloads=ensure_ascii_payloads,
280
283
  verbosity=verbosity,
284
+ user_config=user_config,
281
285
  )
282
286
  cls.__instances[name] = instance
283
287
  else:
@@ -291,6 +295,7 @@ class BaseBlockEngine:
291
295
  log=None,
292
296
  ensure_ascii_payloads=False,
293
297
  verbosity=1,
298
+ user_config=False,
294
299
  ):
295
300
 
296
301
  self.__name = name
@@ -304,11 +309,15 @@ class BaseBlockEngine:
304
309
  self.__config = config
305
310
  self.__ensure_ascii_payloads = ensure_ascii_payloads
306
311
 
307
- pem_name = config.get(BCct.K_PEM_FILE, '_pk.pem')
308
- pem_folder = config.get(BCct.K_PEM_LOCATION, 'data')
309
- pem_fn = os.path.join(log.get_target_folder(pem_folder), pem_name)
312
+ if user_config:
313
+ user_folder = get_user_folder()
314
+ pem_fn = str(user_folder / '_naeural.pem')
315
+ else:
316
+ pem_name = config.get(BCct.K_PEM_FILE, '_pk.pem')
317
+ pem_folder = config.get(BCct.K_PEM_LOCATION, 'data')
318
+ pem_fn = os.path.join(log.get_target_folder(pem_folder), pem_name)
319
+ #endif pem is defined in ~/.naeural/config
310
320
  self.__pem_file = pem_fn
311
-
312
321
  self._init()
313
322
  return
314
323
 
@@ -353,9 +362,13 @@ class BaseBlockEngine:
353
362
  password=self.__password,
354
363
  fn=self.__pem_file,
355
364
  )
356
- self.__public_key = self._get_pk(private_key=self.__private_key)
365
+ self.__public_key = self._get_pk(private_key=self.__private_key)
357
366
  self.__address = self._pk_to_address(self.__public_key)
358
- self.P("Current address: {}".format(self.address), boxed=True, verbosity=1)
367
+ ### Ethereum
368
+ self.__eth_address = self._get_eth_address()
369
+ self.__eth_account = self._get_eth_account()
370
+ ### end Ethereum
371
+ self.P("Address: {} / ETH: {}".format(self.address, self.eth_address), boxed=True, verbosity=1)
359
372
  self.P("Allowed list of senders: {}".format(self.allowed_list), verbosity=1)
360
373
  return
361
374
 
@@ -363,6 +376,11 @@ class BaseBlockEngine:
363
376
  def private_key(self):
364
377
  return self.__private_key
365
378
 
379
+
380
+ @property
381
+ def public_key(self):
382
+ return self.private_key.public_key()
383
+
366
384
 
367
385
  @staticmethod
368
386
  def _compute_hash(data : bytes, method='SHA256'):
@@ -717,6 +735,32 @@ class BaseBlockEngine:
717
735
 
718
736
  """
719
737
  raise NotImplementedError()
738
+
739
+
740
+ def _get_eth_address(self):
741
+ """
742
+ Returns the Ethereum address for the current pk
743
+
744
+ Returns
745
+ -------
746
+ eth_address : str
747
+ the Ethereum address.
748
+
749
+ """
750
+ raise NotImplementedError()
751
+
752
+
753
+ def _get_eth_acccount(self):
754
+ """
755
+ Returns the Ethereum account for the current sk
756
+
757
+ Returns
758
+ -------
759
+ eth_account : str
760
+ the Ethereum account.
761
+
762
+ """
763
+ raise NotImplementedError()
720
764
 
721
765
 
722
766
 
@@ -1045,3 +1089,15 @@ class BaseBlockEngine:
1045
1089
  """
1046
1090
  raise NotImplementedError()
1047
1091
 
1092
+
1093
+ ### Ethereum
1094
+
1095
+ @property
1096
+ def eth_address(self):
1097
+ return self.__eth_address
1098
+
1099
+ @property
1100
+ def eth_account(self):
1101
+ return self.__eth_account
1102
+
1103
+ ### end Ethereum
naeural_client/bc/ec.py CHANGED
@@ -11,6 +11,11 @@ from cryptography.hazmat.backends import default_backend
11
11
  from cryptography.hazmat.primitives.ciphers.aead import AESGCM
12
12
  from cryptography.hazmat.primitives.kdf.hkdf import HKDF
13
13
 
14
+ from web3 import Web3
15
+ from eth_account import Account
16
+ from eth_utils import keccak, to_checksum_address
17
+ from eth_account.messages import encode_defunct
18
+
14
19
  from .base import BaseBlockEngine, VerifyMessage, BCct
15
20
 
16
21
 
@@ -18,6 +23,7 @@ from .base import BaseBlockEngine, VerifyMessage, BCct
18
23
  class BaseBCEllipticCurveEngine(BaseBlockEngine):
19
24
  MAX_ADDRESS_VALUE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
20
25
 
26
+
21
27
  def _get_pk(self, private_key : ec.EllipticCurvePrivateKey) -> ec.EllipticCurvePublicKey:
22
28
  """
23
29
  Simple wrapper to generate pk from sk
@@ -233,6 +239,7 @@ class BaseBCEllipticCurveEngine(BaseBlockEngine):
233
239
  data=bpublic_key
234
240
  )
235
241
  return public_key
242
+
236
243
 
237
244
  def __derive_shared_key(self, peer_public_key : str, info : str = BCct.DEFAULT_INFO, debug : bool = False):
238
245
  """
@@ -266,6 +273,7 @@ class BaseBCEllipticCurveEngine(BaseBlockEngine):
266
273
  if debug:
267
274
  print('derived-shared_key: ', base64.b64encode(derived_key))
268
275
  return derived_key
276
+
269
277
 
270
278
  def encrypt(
271
279
  self,
@@ -380,5 +388,136 @@ class BaseBCEllipticCurveEngine(BaseBlockEngine):
380
388
 
381
389
  result = plaintext.decode()
382
390
  except Exception as exc:
391
+ if debug:
392
+ self.P("Error decrypting from <{}> (compressed_flag `{}`): {}".format(
393
+ sender_address, compressed_flag, exc), color='r'
394
+ )
383
395
  result = None
384
- return result
396
+ return result
397
+
398
+
399
+
400
+ ### ETH
401
+
402
+ def _get_eth_address(self, pk=None):
403
+ if pk is None:
404
+ pk = self.public_key
405
+ raw_public_key = pk.public_numbers()
406
+
407
+ # Compute Ethereum-compatible address
408
+ x = raw_public_key.x.to_bytes(32, 'big')
409
+ y = raw_public_key.y.to_bytes(32, 'big')
410
+ uncompressed_key = b'\x04' + x + y
411
+ keccak_hash = keccak(uncompressed_key[1:]) # Remove 0x04 prefix
412
+ eth_address = "0x" + keccak_hash[-20:].hex()
413
+ eth_address = to_checksum_address(eth_address)
414
+ return eth_address
415
+
416
+ def _get_eth_account(self):
417
+ private_key_bytes = self.private_key.private_numbers().private_value.to_bytes(32, 'big')
418
+ return Account.from_key(private_key_bytes)
419
+
420
+
421
+ def node_address_to_eth_address(self, address):
422
+ """
423
+ Converts a node address to an Ethereum address.
424
+
425
+ Parameters
426
+ ----------
427
+ address : str
428
+ The node address convert.
429
+
430
+ Returns
431
+ -------
432
+ str
433
+ The Ethereum address.
434
+ """
435
+ public_key = self._address_to_pk(address)
436
+ return self._get_eth_address(pk=public_key)
437
+
438
+
439
+
440
+ def eth_hash_message(self, types, values, as_hex=False):
441
+ """
442
+ Hashes a message using the keccak256 algorithm.
443
+
444
+ Parameters
445
+ ----------
446
+ types : list
447
+ The types of the values.
448
+
449
+ values : list of any
450
+ The values to hash.
451
+
452
+ Returns
453
+ -------
454
+ bytes
455
+ The hash of the message in hexadecimal format.
456
+ """
457
+ message = Web3.solidity_keccak(types, values)
458
+ if as_hex:
459
+ return message.hex()
460
+ return message
461
+
462
+
463
+ def eth_sign_message(self, types, values):
464
+ """
465
+ Signs a message using the private key.
466
+
467
+ Parameters
468
+ ----------
469
+ types : list
470
+ The types of the values.
471
+
472
+ values : list of any
473
+ The values to sign.
474
+
475
+ Returns
476
+ -------
477
+ str
478
+ The signature of the message.
479
+ """
480
+ message_hash = self.eth_hash_message(types, values, as_hex=False)
481
+ signable_message = encode_defunct(primitive=message_hash)
482
+ signed_message = Account.sign_message(signable_message, private_key=self.eth_account.key)
483
+ return {
484
+ "message_hash": message_hash.hex(),
485
+ "r": hex(signed_message.r),
486
+ "s": hex(signed_message.s),
487
+ "v": signed_message.v,
488
+ "signature": signed_message.signature.hex(),
489
+ "signed_message": signed_message.message_hash.hex(),
490
+ "sender" : self.eth_address,
491
+ }
492
+
493
+ def eth_sign_node_epochs(self, node, epochs, epochs_vals, signature_only=True):
494
+ """
495
+ Signs the node availability
496
+
497
+ Parameters
498
+ ----------
499
+ node : str
500
+ The node address.
501
+
502
+ epochs : list of int
503
+ The epochs to sign.
504
+
505
+ epochs_vals : list of int
506
+ The values for each epoch.
507
+
508
+ signature_only : bool, optional
509
+ Whether to return only the signature. The default is True.
510
+
511
+ Returns
512
+ -------
513
+ str
514
+ The signature of the message.
515
+ """
516
+ types = ["string", "uint256[]", "uint256[]"]
517
+ values = [node, epochs, epochs_vals]
518
+ result = self.eth_sign_message(types, values)
519
+ if signature_only:
520
+ return result["signature"]
521
+ return result
522
+
523
+
@@ -0,0 +1,7 @@
1
+ # The Naeural Edge Protocol CLI
2
+
3
+ ## Develop
4
+
5
+ ```bash
6
+ pip install -e .
7
+ ```
@@ -0,0 +1,43 @@
1
+ import argparse
2
+
3
+ from naeural_client.utils.config import maybe_init_config
4
+ from naeural_client.cli.cli_commands import CLI_COMMANDS
5
+
6
+ def build_parser():
7
+ """
8
+ Dynamically builds the argument parser based on CLI_COMMANDS.
9
+
10
+ Returns
11
+ -------
12
+ argparse.ArgumentParser
13
+ Configured argument parser.
14
+ """
15
+ parser = argparse.ArgumentParser(description="nepctl - CLI for Naeural Edge Protocol SDK package")
16
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
17
+
18
+ # Iterate over top-level commands
19
+ for command, subcommands in CLI_COMMANDS.items():
20
+ command_parser = subparsers.add_parser(command, help=f"{command} commands")
21
+ if isinstance(subcommands, dict): # Nested subcommands
22
+ command_subparsers = command_parser.add_subparsers(dest="subcommand")
23
+ for subcommand, func in subcommands.items():
24
+ subcommand_parser = command_subparsers.add_parser(
25
+ subcommand, help=f"{subcommand} command"
26
+ )
27
+ subcommand_parser.set_defaults(func=func)
28
+ else:
29
+ command_parser.set_defaults(func=subcommands)
30
+
31
+ return parser
32
+
33
+ def main():
34
+ maybe_init_config()
35
+ parser = build_parser()
36
+ args = parser.parse_args()
37
+ if hasattr(args, "func"):
38
+ args.func() # Call the dynamically loaded function
39
+ else:
40
+ parser.print_help()
41
+
42
+ if __name__ == "__main__":
43
+ main()
@@ -0,0 +1,15 @@
1
+ from naeural_client.cli.nodes import get_nodes, get_supervisors
2
+ from naeural_client.utils.config import show_config, reset_config
3
+
4
+
5
+ # Define the available commands
6
+ CLI_COMMANDS = {
7
+ "get": {
8
+ "nodes": get_nodes,
9
+ "supervisors": get_supervisors,
10
+ },
11
+ "config": {
12
+ "show": show_config,
13
+ "reset": reset_config,
14
+ },
15
+ }
@@ -0,0 +1,22 @@
1
+
2
+
3
+
4
+ def get_nodes():
5
+ """
6
+ This function is used to get the information about the nodes and it will perform the following:
7
+
8
+ 1. Create a Session object.
9
+ 2. Wait for the first net mon message via Session and show progress.
10
+ 3. Wait for the second net mon message via Session and show progress.
11
+ 4. Get the active nodes union via Session and display the nodes marking those peered vs non-peered.
12
+ """
13
+ print("Getting nodes information")
14
+ return
15
+
16
+
17
+ def get_supervisors():
18
+ """
19
+ This function is used to get the information about the supervisors.
20
+ """
21
+ print("Getting supervisors information")
22
+ return
@@ -78,7 +78,8 @@ class BaseLogger(object):
78
78
  DEBUG=True,
79
79
  data_config_subfolder=None,
80
80
  check_additional_configs=False,
81
- append_spaces=True,
81
+ append_spaces=True,
82
+ silent=False,
82
83
  default_color='n',
83
84
  ):
84
85
 
@@ -91,6 +92,7 @@ class BaseLogger(object):
91
92
  self.no_folders_no_save = no_folders_no_save
92
93
  self.max_lines = max_lines
93
94
  self.HTML = HTML
95
+ self.silent = silent
94
96
  self.DEBUG = DEBUG
95
97
  self.log_suffix = lib_name
96
98
  self.default_color = default_color
@@ -145,13 +147,15 @@ class BaseLogger(object):
145
147
 
146
148
  # START: bundling -- se also properties
147
149
  if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
150
+ if not self.silent:
148
151
  print(' Running in a PyInstaller bundle')
149
- self.__is_bundled = True
150
- self.__bundle_path = sys._MEIPASS
152
+ self.__is_bundled = True
153
+ self.__bundle_path = sys._MEIPASS
151
154
  else:
155
+ if not self.silent:
152
156
  print(' Running in a normal Python process')
153
- self.__is_bundled = False
154
- self.__bundle_path = None
157
+ self.__is_bundled = False
158
+ self.__bundle_path = None
155
159
  # END: bundling -- se also properties
156
160
 
157
161
  self.analyze_processor_platform()
@@ -187,7 +191,7 @@ class BaseLogger(object):
187
191
  color='green',
188
192
  boxed=True,
189
193
  )
190
- self.verbose_log(" Timezone: {}.".format(self.timezone),color='green')
194
+ self.P(" Timezone: {}.".format(self.timezone),color='green')
191
195
 
192
196
 
193
197
  if self.DEBUG:
@@ -634,10 +638,10 @@ class BaseLogger(object):
634
638
 
635
639
 
636
640
 
637
- def _logger(self, logstr, show=True, noprefix=False, show_time=False, color=None):
641
+ def _logger(self, logstr, show=None, noprefix=False, show_time=False, color=None):
638
642
  """
639
643
  log processing method
640
- """
644
+ """
641
645
  with self.managed_lock_logger():
642
646
  # now that we have locking in place we no longer need to cancel in-thread logging
643
647
  # if not self.is_main_thread:
@@ -722,7 +726,9 @@ class BaseLogger(object):
722
726
  # endfor
723
727
  return
724
728
 
725
- def _add_log(self, logstr, show=True, noprefix=False, show_time=False, color=None):
729
+ def _add_log(self, logstr, show=None, noprefix=False, show_time=False, color=None):
730
+ if show is None:
731
+ show = not self.silent
726
732
  if type(logstr) != str:
727
733
  logstr = str(logstr)
728
734
  if logstr == "":
@@ -741,23 +747,24 @@ class BaseLogger(object):
741
747
  logstr = logstr[1:]
742
748
  prefix = "\n" + prefix
743
749
  res_log = logstr
744
- if len(logstr) == 0 or logstr[0] != '[':
750
+ if (len(logstr) == 0 or logstr[0] != '[') and not noprefix:
745
751
  prefix = prefix + ' '
746
752
  logstr = prefix + logstr
747
753
  if show_time:
748
754
  logstr += " [{:.2f}s]".format(elapsed)
749
755
  print_logstr = logstr
756
+ if color is None:
757
+ color = self.default_color
758
+ if not self.__first_print and show:
759
+ if not self.silent:
760
+ BaseLogger.print_color("<Logging with default color: {}>".format(color), color=color)
761
+ self.__first_print = True
750
762
  if show:
751
763
  if self.append_spaces:
752
764
  spaces = " " * max(60 - len(print_logstr), 0)
753
765
  else:
754
766
  spaces = ''
755
767
  print_logstr = print_logstr + spaces
756
- if color is None:
757
- color = self.default_color
758
- if not self.__first_print:
759
- BaseLogger.print_color("<Logging with default color: {}>".format(color), color=color)
760
- self.__first_print = True
761
768
  #endif use default color
762
769
  BaseLogger.print_color(print_logstr, color=color)
763
770
  if color.lower()[0] in ['e', 'r']:
@@ -841,16 +848,36 @@ class BaseLogger(object):
841
848
  )
842
849
  return
843
850
 
844
- def verbose_log(self, str_msg, show_time=False, noprefix=False, color=None):
851
+ def verbose_log(self, str_msg, show_time=False, noprefix=False, color=None, show=None):
845
852
  return self._logger(
846
853
  str_msg,
847
- show=True,
854
+ show=show,
848
855
  show_time=show_time,
849
856
  noprefix=noprefix, color=color
850
857
  )
851
858
 
852
- def P(self, str_msg, show_time=False, noprefix=False, color=None, boxed=False, **kwargs):
853
- return self.p(str_msg, show_time=show_time, noprefix=noprefix, color=color, boxed=boxed, **kwargs)
859
+ def P(
860
+ self,
861
+ str_msg,
862
+ show_time=False,
863
+ noprefix=False,
864
+ color=None,
865
+ boxed=False,
866
+ show=None,
867
+ **kwargs
868
+ ):
869
+ """
870
+ Will print & log a message. If show is None it will be set to not self.silent.
871
+ """
872
+ return self.p(
873
+ str_msg,
874
+ show_time=show_time,
875
+ noprefix=noprefix,
876
+ color=color,
877
+ boxed=boxed,
878
+ show=show,
879
+ **kwargs
880
+ )
854
881
 
855
882
  def D(self, str_msg, show_time=False, noprefix=False, color=None, **kwargs):
856
883
  if False:
@@ -891,17 +918,18 @@ class BaseLogger(object):
891
918
 
892
919
  return msg
893
920
 
894
- def p(self, str_msg, show_time=False, noprefix=False, color=None, boxed=False, **kwargs):
921
+ def p(self, str_msg, show_time=False, noprefix=False, color=None, boxed=False, show=None, **kwargs):
895
922
  if boxed:
896
923
  msg = self.__convert_to_box(str_msg, **kwargs)
897
- self._logger(msg, show=True, noprefix=noprefix, color=color)
924
+ self._logger(msg, show=show, noprefix=noprefix, color=color)
898
925
  else:
899
926
  return self._logger(
900
927
  str_msg,
901
- show=True,
928
+ show=show,
902
929
  show_time=show_time,
903
930
  noprefix=noprefix, color=color
904
931
  )
932
+
905
933
 
906
934
  def Pmd(self, s=''):
907
935
  print_func = None
@@ -955,10 +983,10 @@ class BaseLogger(object):
955
983
  if type(str_text) != str:
956
984
  str_text = str(str_text)
957
985
  str_final = str_msg + "\n" + textwrap.indent(str_text, n * " ")
958
- self._logger(str_final, show=True, show_time=False)
986
+ self._logger(str_final, show=None, show_time=False)
959
987
  return
960
988
 
961
- def log(self, str_msg, show=False, show_time=False, color=None):
989
+ def log(self, str_msg, show=None, show_time=False, color=None):
962
990
  return self._logger(str_msg, show=show, show_time=show_time, color=color)
963
991
 
964
992
  def _generate_log_path(self):
@@ -1037,7 +1065,8 @@ class BaseLogger(object):
1037
1065
  self._app_folder = self.config_data["APP_FOLDER"]
1038
1066
  #endif no defaults for base/app folders
1039
1067
 
1040
- print("Loaded config [{}]".format(config_file), flush=True)
1068
+ if not self.silent:
1069
+ print("Loaded config [{}]".format(config_file), flush=True)
1041
1070
  self.config_file = config_file
1042
1071
  else:
1043
1072
  self.config_data = {
@@ -1045,6 +1074,8 @@ class BaseLogger(object):
1045
1074
  'APP_FOLDER' : self._app_folder
1046
1075
  }
1047
1076
  self.config_file = "default_config.txt"
1077
+ if not self.silent:
1078
+ print("No config file provided. Using default config.", flush=True)
1048
1079
  #endif have or not config file
1049
1080
 
1050
1081
  self.config_data = {
@@ -1053,13 +1084,18 @@ class BaseLogger(object):
1053
1084
  }
1054
1085
 
1055
1086
  matches = self.replace_secrets(self.config_data)
1056
- print(" Config modified with following env vars: {}".format(matches))
1087
+ if not self.silent:
1088
+ if len(matches) > 0:
1089
+ print(" Config modified with following env vars: {}".format(matches))
1090
+ else:
1091
+ print(" No secrets/template found in config")
1057
1092
 
1058
1093
  self._base_folder = self.expand_tilda(self._base_folder)
1059
1094
  self._base_folder = self._get_cloud_base_folder(self._base_folder)
1060
1095
  self._root_folder = os.path.abspath(self._base_folder)
1061
1096
  self._base_folder = os.path.join(self._base_folder, self._app_folder)
1062
- print("BASE: {}".format(self._base_folder), flush=True)
1097
+ if not self.silent:
1098
+ print("BASE: {}".format(self._base_folder), flush=True)
1063
1099
 
1064
1100
  self._normalize_path_sep()
1065
1101
 
@@ -41,6 +41,7 @@ class Logger(
41
41
  max_lines=None,
42
42
  HTML=False,
43
43
  DEBUG=True,
44
+ silent=False,
44
45
  data_config_subfolder=None,
45
46
  check_additional_configs=False,
46
47
  default_color='n',
@@ -58,6 +59,7 @@ class Logger(
58
59
  max_lines=max_lines,
59
60
  HTML=HTML,
60
61
  DEBUG=DEBUG,
62
+ silent=silent,
61
63
  data_config_subfolder=data_config_subfolder,
62
64
  check_additional_configs=check_additional_configs,
63
65
  default_color=default_color,
@@ -0,0 +1,102 @@
1
+ import os
2
+ from pathlib import Path
3
+ import shutil
4
+
5
+ ENV_TEMPLATE = """
6
+
7
+ EE_MQTT_HOST=r9092118.ala.eu-central-1.emqxsl.com
8
+ EE_MQTT_PORT=8883
9
+ EE_MQTT_USER=
10
+ EE_MQTT=
11
+
12
+ EE_SECURED=true
13
+
14
+ TARGET_NODE=
15
+ """
16
+
17
+ def get_user_folder():
18
+ """
19
+ Returns the user folder.
20
+ """
21
+ return Path.home() / ".naeural"
22
+
23
+ def get_user_config_file():
24
+ """
25
+ Returns the user configuration file.
26
+ """
27
+ return get_user_folder() / "config"
28
+
29
+ def reset_config():
30
+ """
31
+ Resets the configuration by creating a ~/.naeural folder and populating
32
+ ~/.naeural/config with values from a local .env file, if it exists.
33
+ """
34
+ # Define the target config folder and file
35
+ config_dir = get_user_folder()
36
+ config_file = get_user_config_file()
37
+
38
+ # Create the ~/.naeural folder if it doesn't exist
39
+ config_dir.mkdir(parents=True, exist_ok=True)
40
+
41
+ # Check if the current folder has a .env file
42
+ current_env_file = Path(".env")
43
+ if current_env_file.exists():
44
+ # Copy .env content to ~/.naeural/config
45
+ shutil.copy(current_env_file, config_file)
46
+ print(f"Configuration has been reset using {current_env_file} into {config_file}")
47
+ else:
48
+ # Create an empty config file
49
+ with config_file.open("wt") as file:
50
+ file.write(ENV_TEMPLATE)
51
+ print(f"Configuration has been reset to default in {config_file}:\n{ENV_TEMPLATE}")
52
+
53
+
54
+ def show_config():
55
+ """
56
+ Displays the current configuration from ~/.naeural/config.
57
+ """
58
+ config_file = get_user_config_file()
59
+
60
+ if config_file.exists():
61
+ print(f"Current configuration ({config_file}):")
62
+ with config_file.open("r") as file:
63
+ print(file.read())
64
+ else:
65
+ print(f"No configuration found at {config_file}. Please run `reset_config` first.")
66
+
67
+
68
+ def load_user_defined_config(verbose=False):
69
+ """
70
+ Loads the ~/.naeural/config file into the current environment.
71
+ """
72
+ config_file = get_user_config_file()
73
+ result = False
74
+
75
+ if config_file.exists():
76
+ with config_file.open("r") as file:
77
+ for line in file:
78
+ # Ignore comments and empty lines
79
+ if line.strip() and not line.strip().startswith("#"):
80
+ key, value = line.strip().split("=", 1)
81
+ value = value.strip()
82
+ # if at least one key-value pair is found, set the result to True
83
+ if value != "":
84
+ result = True
85
+ os.environ[key.strip()] = value
86
+ if verbose:
87
+ print(f"Configuration from {config_file} has been loaded into the environment.")
88
+ else:
89
+ if verbose:
90
+ print(f"No configuration file found at {config_file}. Please run `reset_config` first.")
91
+ return result
92
+
93
+
94
+ def maybe_init_config():
95
+ """
96
+ Initializes the configuration if it doesn't exist yet.
97
+ """
98
+ config_file = get_user_config_file()
99
+
100
+ if not config_file.exists():
101
+ reset_config()
102
+ load_user_defined_config()
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: naeural_client
3
- Version: 2.2.6
3
+ Version: 2.3.0
4
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/Naeural Edge ProtocolEdgeProtocol/naeural_client
6
- Project-URL: Bug Tracker, https://github.com/Naeural Edge ProtocolEdgeProtocol/naeural_client/issues
7
- Author-email: Stefan Saraev <saraevstefan@gmail.com>, Andrei Ionut Damian <andrei.damian@me.com>, Cristan Bleotiu <cristibleotiu@gmail.com>
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
8
  Classifier: License :: OSI Approved :: MIT License
9
9
  Classifier: Operating System :: OS Independent
10
10
  Classifier: Programming Language :: Python :: 3
@@ -16,6 +16,7 @@ Requires-Dist: pika
16
16
  Requires-Dist: pyaml
17
17
  Requires-Dist: pyopenssl>=23.0.0
18
18
  Requires-Dist: python-dateutil
19
+ Requires-Dist: web3
19
20
  Description-Content-Type: text/markdown
20
21
 
21
22
  # naeural_client SDK
@@ -1,10 +1,10 @@
1
1
  naeural_client/__init__.py,sha256=UKEDGS0wFYyxwmhEAKJGecO2vYbIfRYUP4SQgnK10IE,578
2
- naeural_client/_ver.py,sha256=lZiu4kJosgmfFcg5Zp1ACcy6GyugTYJj01wjM9GVt7s,330
3
- naeural_client/base_decentra_object.py,sha256=qDBpitcyhr1eEXPD8cGFtcNPNf71fqNRsmOEcCpx4sM,4180
2
+ naeural_client/_ver.py,sha256=zb8Hzmn-gdMpHg77kLusI381BxnNJ0u_wIk_OIVA5jQ,330
3
+ naeural_client/base_decentra_object.py,sha256=wXjl65gWxxkhV6Tq48wFfNGITvdYdkKPT-wLurGB5vc,4287
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=QbaRWvC5XeWSdYWYg7kZXSYX4jUHH7aVcDcFLRQ9I5o,75834
7
+ naeural_client/base/generic_session.py,sha256=I1M2dxFMWe6FVxOs_n54DjYpWDIRGn9UJOqWo0YdBjA,77367
8
8
  naeural_client/base/instance.py,sha256=kcZJmjLBtx8Bjj_ysIOx1JmLA-qSpG7E28j5rq6IYus,20444
9
9
  naeural_client/base/pipeline.py,sha256=KwcPWD2XMvHotWFMpcnIycFhqiNnZuyUTUWiLU0PM5Y,57519
10
10
  naeural_client/base/plugin_template.py,sha256=qGaXByd_JZFpjvH9GXNbT7KaitRxIJB6-1IhbKrZjq4,138123
@@ -13,11 +13,15 @@ naeural_client/base/transaction.py,sha256=bfs6td5M0fINgPQNxhrl_AUjb1YiilLDQ-Cd_o
13
13
  naeural_client/base/payload/__init__.py,sha256=y8fBI8tG2ObNfaXFWjyWZXwu878FRYj_I8GIbHT4GKE,29
14
14
  naeural_client/base/payload/payload.py,sha256=v50D7mBBD2WwWzvpbRGMSr-X6vv5ie21IY1aDxTqe1c,2275
15
15
  naeural_client/bc/__init__.py,sha256=FQj23D1PrY06NUOARiKQi4cdj0-VxnoYgYDEht8lpr8,158
16
- naeural_client/bc/base.py,sha256=nAFL4z9kyFcmrilHWCia69xMWi65VzjoQ_mo-yCQhGA,28331
16
+ naeural_client/bc/base.py,sha256=GI9CbI2RVgIBXpOoRjrhXt45tONlBDMrybX_BcL3_Os,29513
17
17
  naeural_client/bc/chain.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- naeural_client/bc/ec.py,sha256=8JQ4AIDz0AZAhm3xOFRrYW-k1WGRIchn4O1WO_qRntQ,10370
18
+ naeural_client/bc/ec.py,sha256=eUkdHUDZ-OgmbwjbYx1wmTOJVWLmjvP4Svduocj69Uk,13841
19
19
  naeural_client/certs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  naeural_client/certs/r9092118.ala.eu-central-1.emqxsl.com.crt,sha256=y-6io0tseyx9-a4Pmde1z1gPULtJNSYUpG_YFkYaMKU,1337
21
+ naeural_client/cli/README.md,sha256=WPdI_EjzAbUW1aPyj1sSR8rLydcJKZtoiaEtklQrjHo,74
22
+ naeural_client/cli/cli.py,sha256=KlPb6ExUMrrFxBl2j7XRnOQhtH-1s-jFtsca5zcz5qI,1342
23
+ naeural_client/cli/cli_commands.py,sha256=NluWqnsyuabu0EKNM6MDUBtnfloH7oqCOrkfJs8Ajjk,353
24
+ naeural_client/cli/nodes.py,sha256=tOgWGzsCqGhznne1-FRbiYdmIALSXljYz2C8Eub25Ks,619
21
25
  naeural_client/code_cheker/__init__.py,sha256=pwkdeZGVL16ZA4Qf2mRahEhoOvKhL7FyuQbMFLr1E5M,33
22
26
  naeural_client/code_cheker/base.py,sha256=lT5DRIFO5rqzsMNCmdMRfkAeevmezozehyfgmhnKpuI,19074
23
27
  naeural_client/code_cheker/checker.py,sha256=QWupeM7ToancVIq1tRUxRNUrI8B5l5eoY0kDU4-O5aE,7365
@@ -52,8 +56,8 @@ naeural_client/io_formatter/default/a_dummy.py,sha256=qr9eUizQ-NN5jdXVzkaZKMaf9K
52
56
  naeural_client/io_formatter/default/aixp1.py,sha256=MX0TeUR4APA-qN3vUC6uzcz8Pssz5lgrQWo7td5Ri1A,3052
53
57
  naeural_client/io_formatter/default/default.py,sha256=gEy78cP2D5s0y8vQh4aHuxqz7D10gGfuiKF311QhrpE,494
54
58
  naeural_client/logging/__init__.py,sha256=b79X45VC6c37u32flKB2GAK9f-RR0ocwP0JDCy0t7QQ,33
55
- naeural_client/logging/base_logger.py,sha256=tTxzCKOhcqGOtckHK8lQ1nUTNeaBNkeh0_SmDyzX6_Y,65146
56
- naeural_client/logging/small_logger.py,sha256=6wljiHP1moCkgohRnr2EX095eM2RtdJ5B3cytbO_Ow4,2887
59
+ naeural_client/logging/base_logger.py,sha256=IEhDnR6vn_nMBE4OwzUHO_71wuvAtCXjHHx4yeRxm6s,65880
60
+ naeural_client/logging/small_logger.py,sha256=m12hCb_H4XifJYYfgCAOUDkcXm-h4pSODnFf277OFVI,2937
57
61
  naeural_client/logging/logger_mixins/__init__.py,sha256=yQO7umlRvz63FeWpi-F9GRmC_MOHcNW6R6pwvZZBy3A,600
58
62
  naeural_client/logging/logger_mixins/class_instance_mixin.py,sha256=xUXE2VZgmrlrSrvw0f6GF1jlTnVLeVkIiG0bhlBfq3o,2741
59
63
  naeural_client/logging/logger_mixins/computer_vision_mixin.py,sha256=TrtG7ayM2ab-4jjIkIWAQaFi9cVfiINAWrJCt8mCCFI,13213
@@ -74,8 +78,10 @@ naeural_client/logging/tzlocal/win32.py,sha256=zBoj0vFVrGhnCm_f7xmYzGym4-fV-4Ij2
74
78
  naeural_client/logging/tzlocal/windows_tz.py,sha256=Sv9okktjZJfRGGUOOppsvQuX_eXyXUxkSKCAFmWT9Hw,34203
75
79
  naeural_client/utils/__init__.py,sha256=mAnke3-MeRzz3nhQvhuHqLnpaaCSmDxicd7Ck9uwpmI,77
76
80
  naeural_client/utils/comm_utils.py,sha256=4cS9llRr_pK_3rNgDcRMCQwYPO0kcNU7AdWy_LtMyCY,1072
81
+ naeural_client/utils/config.py,sha256=eqVhrSte94nC1eJOkRzuUAdIgJUOLoqcV3CZEFS2O6g,2767
77
82
  naeural_client/utils/dotenv.py,sha256=_AgSo35n7EnQv5yDyu7C7i0kHragLJoCGydHjvOkrYY,2008
78
- naeural_client-2.2.6.dist-info/METADATA,sha256=W-910TVqH3CTfTisr-54C-CgiLW6-xhhUvULCi4BYbU,14457
79
- naeural_client-2.2.6.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
80
- naeural_client-2.2.6.dist-info/licenses/LICENSE,sha256=cvOsJVslde4oIaTCadabXnPqZmzcBO2f2zwXZRmJEbE,11311
81
- naeural_client-2.2.6.dist-info/RECORD,,
83
+ naeural_client-2.3.0.dist-info/METADATA,sha256=7X1taqH_efSawHEHyKlLCRjwh5CLamWnnmW5hDc6Ays,14449
84
+ naeural_client-2.3.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
85
+ naeural_client-2.3.0.dist-info/entry_points.txt,sha256=PNdyotDaQBAslZREx5luVyj0kqpQnwNACwkFNTPIHU4,55
86
+ naeural_client-2.3.0.dist-info/licenses/LICENSE,sha256=cvOsJVslde4oIaTCadabXnPqZmzcBO2f2zwXZRmJEbE,11311
87
+ naeural_client-2.3.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nepctl = naeural_client.cli.cli:main