atomicshop 2.16.34__py3-none-any.whl → 2.16.36__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.

Potentially problematic release.


This version of atomicshop might be problematic. Click here for more details.

atomicshop/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """Atomic Basic functions and classes to make developer life easier"""
2
2
 
3
3
  __author__ = "Den Kras"
4
- __version__ = '2.16.34'
4
+ __version__ = '2.16.36'
@@ -0,0 +1,12 @@
1
+ #! /usr/bin/env python3
2
+ import sys
3
+
4
+ from atomicshop.wrappers.mongodbw import install_mongodb_ubuntu
5
+
6
+
7
+ def main():
8
+ install_mongodb_ubuntu.install_main(compass=True)
9
+
10
+
11
+ if __name__ == "__main__":
12
+ sys.exit(main())
@@ -1,3 +1,4 @@
1
+ #! /usr/bin/env python3
1
2
  import sys
2
3
 
3
4
  from atomicshop.wrappers.pycharmw import ubuntu
@@ -1,8 +1,8 @@
1
- from atomicshop.wrappers.mongodbw import install_mongodb
1
+ from atomicshop.wrappers.mongodbw import install_mongodb_win
2
2
 
3
3
 
4
4
  def main():
5
- install_mongodb.download_install_latest_main()
5
+ install_mongodb_win.download_install_latest_main()
6
6
 
7
7
 
8
8
  if __name__ == "__main__":
@@ -1,4 +1,3 @@
1
- # v1.0.3 - 28.03.2023 17:20
2
1
  import sys
3
2
 
4
3
  from .threads import current_thread_id
@@ -15,3 +14,8 @@ def print_exception() -> None:
15
14
  exc_type, exc_value, exc_traceback = sys.exc_info()
16
15
 
17
16
  print(f"{error_log_prefix} Thread {thread_id}: * Details: {exc_type}, {exc_value}")
17
+
18
+
19
+ def get_exception_type_string(exception: Exception) -> str:
20
+ """ Get exception type string """
21
+ return type(exception).__name__
@@ -273,6 +273,8 @@ def thread_worker_main(
273
273
 
274
274
  # So if the socket was closed and there was an error we can break the loop
275
275
  if not service_ssl_socket:
276
+ record_and_statistics_write()
277
+ recorded = True
276
278
  break
277
279
 
278
280
  # If there is a response, then send it.
@@ -0,0 +1,100 @@
1
+ import subprocess
2
+ import sys
3
+ import os
4
+
5
+
6
+ from ... import print_api, web
7
+ from .. import ubuntu_terminal
8
+ from ...permissions import ubuntu_permissions
9
+
10
+
11
+ COMPASS_INSTALLATION_SCRIPT_URL: str = \
12
+ 'https://raw.githubusercontent.com/mongodb/mongo/master/src/mongo/installer/compass/install_compass'
13
+
14
+
15
+ def run_command(command):
16
+ """Run a system command and exit if the command fails."""
17
+ try:
18
+ subprocess.run(command, check=True, shell=True)
19
+ except subprocess.CalledProcessError as e:
20
+ print_api.print_api(f"Error: {e}", color='red')
21
+ sys.exit(1)
22
+
23
+
24
+ def install_mongodb(version):
25
+ """Install the specified major version of MongoDB on Ubuntu."""
26
+
27
+ if not version.endswith(".0"):
28
+ version = f"{version}.0"
29
+
30
+ print_api.print_api(f"Installing MongoDB {version} on Ubuntu...")
31
+ print_api.print_api(f"Installing Prerequisites...")
32
+ ubuntu_terminal.update_system_packages()
33
+ ubuntu_terminal.install_packages(["wget", "curl", "gnupg"])
34
+
35
+ # Step 1: Import the MongoDB public GPG key
36
+ print_api.print_api("Step 1: Importing the MongoDB public GPG key...")
37
+ run_command(f"curl -fsSL https://pgp.mongodb.com/server-{version}.asc | "
38
+ f"sudo gpg --dearmor -o /usr/share/keyrings/mongodb-server-{version}.gpg")
39
+
40
+ # Step 2: Create the MongoDB list file for APT
41
+ print_api.print_api("Step 2: Creating MongoDB APT list file...")
42
+ distro_version = subprocess.check_output("lsb_release -sc", shell=True).decode('utf-8').strip()
43
+ run_command(
44
+ f"echo 'deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-{version}.gpg ] "
45
+ f"https://repo.mongodb.org/apt/ubuntu {distro_version}/mongodb-org/{version} multiverse' | "
46
+ f"sudo tee /etc/apt/sources.list.d/mongodb-org-{version}.list")
47
+
48
+ # Step 3: Update the APT package index
49
+ print_api.print_api("Step 3: Updating the APT package index...")
50
+ ubuntu_terminal.update_system_packages()
51
+
52
+ # Step 4: Install the latest version of MongoDB for the specified major version
53
+ print_api.print_api(f"Step 4: Installing MongoDB version {version}...")
54
+ ubuntu_terminal.install_packages(["mongodb-org"])
55
+
56
+ # Step 5: Start MongoDB service and enable it on startup
57
+ print_api.print_api("Step 5: Starting MongoDB service and enabling it on startup...")
58
+ ubuntu_terminal.start_enable_service_check_availability("mongod")
59
+
60
+ print_api.print_api(f"MongoDB {version} installation complete!", color='green')
61
+
62
+
63
+ def install_main(
64
+ compass: bool = False,
65
+ ):
66
+ """
67
+ Install the latest minor version of MongoDB Community Server on Ubuntu by providing the major version.
68
+ :param compass: bool, if True, MongoDB Compass will be installed.
69
+ :return:
70
+ """
71
+ # Ensure the user provides a MongoDB major version as an argument.
72
+ if len(sys.argv) != 2:
73
+ message: str = ("Usage: python install_mongodb.py <mongo_major_version>\n"
74
+ "Example: python install_mongodb.py 7")
75
+ print_api.print_api(message, color='red')
76
+ return 1
77
+
78
+ mongo_version = sys.argv[1]
79
+
80
+ # Call the 'install' function with the major version.
81
+ install_mongodb(mongo_version)
82
+
83
+ if not compass:
84
+ return 0
85
+
86
+ # It doesn't matter what you do with the MSI it will not install Compass, only if you run it manually.
87
+ # So we will use installation script from their GitHub.
88
+ print_api.print_api("Downloading MongoDB Compass installation script...")
89
+ compass_script_path: str = web.download(COMPASS_INSTALLATION_SCRIPT_URL)
90
+
91
+ print_api.print_api("Installing MongoDB Compass from script...")
92
+ ubuntu_permissions.set_executable(compass_script_path)
93
+ run_command(f'sudo -E python3 {compass_script_path}')
94
+
95
+ # Clean up the installer file
96
+ if os.path.exists(compass_script_path):
97
+ os.remove(compass_script_path)
98
+ print_api.print_api("Cleaned up the Compass installer file.")
99
+
100
+ return 0
@@ -18,6 +18,18 @@ These are good examples to get you started, but can't really cover all the use c
18
18
  """
19
19
 
20
20
 
21
+ class MongoDBReplaceOneError(Exception):
22
+ pass
23
+
24
+
25
+ class MongoDBUpdateOneError(Exception):
26
+ pass
27
+
28
+
29
+ class MongoDBUpdateManyError(Exception):
30
+ pass
31
+
32
+
21
33
  class MongoDBWrapper:
22
34
  def __init__(
23
35
  self,
@@ -80,13 +92,15 @@ class MongoDBWrapper:
80
92
 
81
93
  def delete(
82
94
  self,
83
- object_instance: Union[list[dict], dict],
95
+ query_instance: Union[list[dict], dict],
84
96
  collection_name: str
85
97
  ):
86
98
  """
87
- Remove a list of dictionaries or a dictionary from a MongoDB collection.
99
+ Remove a dict or list of dictionaries or a dictionary from a MongoDB collection.
100
+ For pure mongo, this is the list of queries to remove.
101
+ Each query for a single item.
88
102
 
89
- :param object_instance: list of dictionaries, the list of dictionaries to remove from the collection.
103
+ :param query_instance: dict or list of dictionaries, the list of queries to remove from the collection.
90
104
  :param collection_name: str, the name of the collection.
91
105
 
92
106
  :return: None
@@ -95,7 +109,30 @@ class MongoDBWrapper:
95
109
  self.connect()
96
110
 
97
111
  delete(
98
- object_instance=object_instance,
112
+ query_instance=query_instance,
113
+ database=self.db, collection_name=collection_name,
114
+ mongo_client=self.client, close_client=False)
115
+
116
+ def delete_many(
117
+ self,
118
+ query: dict,
119
+ collection_name: str
120
+ ):
121
+ """
122
+ Remove all entries that match the query from a MongoDB collection.
123
+
124
+ :param query: dict, the query to search for.
125
+ Example, search for all entries with column name 'name' equal to 'John':
126
+ query = {'name': 'John'}
127
+ :param collection_name: str, the name of the collection.
128
+
129
+ :return: result of the operation.
130
+ """
131
+
132
+ self.connect()
133
+
134
+ return delete_many(
135
+ query=query,
99
136
  database=self.db, collection_name=collection_name,
100
137
  mongo_client=self.client, close_client=False)
101
138
 
@@ -203,6 +240,69 @@ class MongoDBWrapper:
203
240
 
204
241
  return distinct_values
205
242
 
243
+ def update(
244
+ self,
245
+ collection_name: str,
246
+ query: dict,
247
+ update_instance: Union[dict, list[dict]],
248
+ add_timestamp: bool = False,
249
+ convert_mixed_lists_to_strings: bool = False
250
+ ):
251
+ """
252
+ Update one entry in a MongoDB collection by query.
253
+ :param collection_name: str, the name of the collection.
254
+ :param query: dict, the query to search for.
255
+ Example, search for all entries with column name 'name' equal to 'John':
256
+ query = {'name': 'John'}
257
+ Find by Object id:
258
+ query = {'_id': ObjectId('5f3e3b3b4b9f3b3b4b9f3b3b')}
259
+ :param update_instance: dict or list of dicts, the update to apply.
260
+ Get examples for operators for each dict in the docstring of the function 'update' below.
261
+ :param add_timestamp: bool, if True, a current time timestamp will be added to the object.
262
+ :param convert_mixed_lists_to_strings: bool, if True, mixed lists or tuples when entries are
263
+ strings and integers, the integers will be converted to strings.
264
+ :return: result of the operation.
265
+ """
266
+
267
+ self.connect()
268
+
269
+ return update(
270
+ database=self.db, collection_name=collection_name,
271
+ query=query, update_instance=update_instance, add_timestamp=add_timestamp,
272
+ convert_mixed_lists_to_strings=convert_mixed_lists_to_strings,
273
+ mongo_client=self.client, close_client=False)
274
+
275
+ def replace(
276
+ self,
277
+ collection_name: str,
278
+ query: dict,
279
+ replacement: dict,
280
+ add_timestamp: bool = False,
281
+ convert_mixed_lists_to_strings: bool = False
282
+ ):
283
+ """
284
+ Replace one entry in a MongoDB collection by query.
285
+ :param collection_name: str, the name of the collection.
286
+ :param query: dict, the query to search for.
287
+ Example, search for all entries with column name 'name' equal to 'John':
288
+ query = {'name': 'John'}
289
+ Find by Object id:
290
+ query = {'_id': ObjectId('5f3e3b3b4b9f3b3b4b9f3b3b')}
291
+ :param replacement: dict, the replacement to apply.
292
+ :param add_timestamp: bool, if True, a current time timestamp will be added to the object.
293
+ :param convert_mixed_lists_to_strings: bool, if True, mixed lists or tuples when entries are
294
+
295
+ :return: result of the operation.
296
+ """
297
+
298
+ self.connect()
299
+
300
+ return replace(
301
+ database=self.db, collection_name=collection_name,
302
+ query=query, replacement=replacement,
303
+ add_timestamp=add_timestamp, convert_mixed_lists_to_strings=convert_mixed_lists_to_strings,
304
+ mongo_client=self.client, close_client=False)
305
+
206
306
  def count_entries_in_collection(
207
307
  self,
208
308
  collection_name: str,
@@ -328,7 +428,11 @@ def index(
328
428
  collection = db[collection_name]
329
429
 
330
430
  if convert_mixed_lists_to_strings:
331
- object_instance = dicts.convert_int_to_str_in_mixed_lists(object_instance)
431
+ if isinstance(object_instance, dict):
432
+ object_instance = dicts.convert_int_to_str_in_mixed_lists(object_instance)
433
+ elif isinstance(object_instance, list):
434
+ for doc_index, doc in enumerate(object_instance):
435
+ object_instance[doc_index] = dicts.convert_int_to_str_in_mixed_lists(doc)
332
436
 
333
437
  if add_timestamp:
334
438
  timestamp = datetime.datetime.now()
@@ -348,16 +452,18 @@ def index(
348
452
 
349
453
 
350
454
  def delete(
351
- object_instance: Union[list[dict], dict],
455
+ query_instance: Union[list[dict], dict],
352
456
  database: Union[str, pymongo.database.Database],
353
457
  collection_name: str,
354
458
  mongo_client: pymongo.MongoClient = None,
355
459
  close_client: bool = False
356
460
  ):
357
461
  """
358
- Remove a list of dictionaries or a dictionary from a MongoDB collection.
462
+ Remove a dict or list of dictionaries or a dictionary from a MongoDB collection.
359
463
 
360
- :param object_instance: list of dictionaries, the list of dictionaries to remove from the collection.
464
+ :param query_instance: list of dictionaries, the list of dictionaries to remove from the collection.
465
+ For pure mongo, this is the list of queries to remove.
466
+ Each query for a single item.
361
467
  :param database: String or the database object.
362
468
  str - the name of the database. In this case the database object will be created.
363
469
  pymongo.database.Database - the database object that will be used instead of creating a new one.
@@ -369,7 +475,7 @@ def delete(
369
475
  :return: None
370
476
  """
371
477
 
372
- _is_object_list_of_dicts_or_dict(object_instance)
478
+ _is_object_list_of_dicts_or_dict(query_instance)
373
479
 
374
480
  if not mongo_client:
375
481
  mongo_client = connect()
@@ -378,16 +484,55 @@ def delete(
378
484
  db = _get_pymongo_db_from_string_or_pymongo_db(database, mongo_client)
379
485
  collection = db[collection_name]
380
486
 
381
- if isinstance(object_instance, dict):
382
- collection.delete_one(object_instance)
383
- elif isinstance(object_instance, list):
384
- for doc in object_instance:
487
+ if isinstance(query_instance, dict):
488
+ collection.delete_one(query_instance)
489
+ elif isinstance(query_instance, list):
490
+ for doc in query_instance:
385
491
  collection.delete_one(doc)
386
492
 
387
493
  if close_client:
388
494
  mongo_client.close()
389
495
 
390
496
 
497
+ def delete_many(
498
+ query: dict,
499
+ database: Union[str, pymongo.database.Database],
500
+ collection_name: str,
501
+ mongo_client: pymongo.MongoClient = None,
502
+ close_client: bool = False
503
+ ):
504
+ """
505
+ Remove all entries that match the query from a MongoDB collection.
506
+
507
+ :param query: dict, the query to search for.
508
+ Example, search for all entries with column name 'name' equal to 'John':
509
+ query = {'name': 'John'}
510
+ :param database: String or the database object.
511
+ str - the name of the database. In this case the database object will be created.
512
+ pymongo.database.Database - the database object that will be used instead of creating a new one.
513
+ :param collection_name: str, the name of the collection.
514
+ :param mongo_client: pymongo.MongoClient, the connection object.
515
+ If None, a new connection will be created to default URI.
516
+ :param close_client: bool, if True, the connection will be closed after the operation.
517
+
518
+ :return: result of the operation.
519
+ """
520
+
521
+ if not mongo_client:
522
+ mongo_client = connect()
523
+ close_client = True
524
+
525
+ db = _get_pymongo_db_from_string_or_pymongo_db(database, mongo_client)
526
+ collection = db[collection_name]
527
+
528
+ result = collection.delete_many(query)
529
+
530
+ if close_client:
531
+ mongo_client.close()
532
+
533
+ return result
534
+
535
+
391
536
  def find(
392
537
  database: Union[str, pymongo.database.Database],
393
538
  collection_name: str,
@@ -614,6 +759,145 @@ def distinct(
614
759
  return distinct_values
615
760
 
616
761
 
762
+ def update(
763
+ database: Union[str, pymongo.database.Database],
764
+ collection_name: str,
765
+ query: dict,
766
+ update_instance: Union[dict, list[dict]],
767
+ add_timestamp: bool = False,
768
+ convert_mixed_lists_to_strings: bool = False,
769
+ mongo_client: pymongo.MongoClient = None,
770
+ close_client: bool = False
771
+ ):
772
+ """
773
+ Update one entry in a MongoDB collection by query.
774
+ :param database: String or the database object.
775
+ str - the name of the database. In this case the database object will be created.
776
+ pymongo.database.Database - the database object that will be used instead of creating a new one.
777
+ :param collection_name: str, the name of the collection.
778
+ :param query: dict, the query to search for.
779
+ Example, search for all entries with column name 'name' equal to 'John':
780
+ query = {'name': 'John'}
781
+ Find by Object id:
782
+ query = {'_id': ObjectId('5f3e3b3b4b9f3b3b4b9f3b3b')}
783
+ :param update_instance: dict or list of dicts, the update to apply.
784
+ If dict, the update will be applied to one entry using 'update_one'.
785
+ If list of dicts, the update will be applied to multiple entries using 'update_many'.
786
+
787
+ Examples for operators for each dict:
788
+ $set: update the column 'name' to 'Alice':
789
+ update_instance = {'$set': {'name': 'Alice'}}
790
+ $inc: increment the column 'age' by 1:
791
+ update_instance = {'$inc': {'age': 1}}
792
+ $unset: remove the column 'name':
793
+ update_instance = {'$unset': {'name': ''}}
794
+ $push: add a value to the list 'hobbies':
795
+ update_instance = {'$push': {'hobbies': 'swimming'}}
796
+ $pull: remove a value from the list 'hobbies':
797
+ update_instance = {'$pull': {'hobbies': 'swimming'}}
798
+ :param add_timestamp: bool, if True, a current time timestamp will be added to the object.
799
+ :param convert_mixed_lists_to_strings: bool, if True, mixed lists or tuples when entries are
800
+ strings and integers, the integers will be converted to strings.
801
+ :param mongo_client: pymongo.MongoClient, the connection object.
802
+ If None, a new connection will be created to default URI.
803
+ :param close_client: bool, if True, the connection will be closed after the operation.
804
+
805
+ :return: None
806
+ """
807
+
808
+ if not mongo_client:
809
+ mongo_client = connect()
810
+ close_client = True
811
+
812
+ db = _get_pymongo_db_from_string_or_pymongo_db(database, mongo_client)
813
+ collection = db[collection_name]
814
+
815
+ if convert_mixed_lists_to_strings:
816
+ if isinstance(update_instance, dict):
817
+ object_instance = dicts.convert_int_to_str_in_mixed_lists(update_instance)
818
+ elif isinstance(update_instance, list):
819
+ for doc_index, doc in enumerate(update_instance):
820
+ update_instance[doc_index] = dicts.convert_int_to_str_in_mixed_lists(doc)
821
+
822
+ if add_timestamp:
823
+ timestamp = datetime.datetime.now()
824
+ if isinstance(update_instance, dict):
825
+ update_instance['timestamp'] = timestamp
826
+ elif isinstance(update_instance, list):
827
+ for doc in update_instance:
828
+ doc['timestamp'] = timestamp
829
+
830
+ result = None
831
+ if isinstance(update_instance, dict):
832
+ result = collection.update_one(query, update_instance)
833
+ elif isinstance(update_instance, list):
834
+ result = collection.update_many(query, update_instance)
835
+
836
+ if result.matched_count == 0:
837
+ raise MongoDBUpdateOneError("No document found to update.")
838
+
839
+ if close_client:
840
+ mongo_client.close()
841
+
842
+ return result
843
+
844
+
845
+ def replace(
846
+ database: Union[str, pymongo.database.Database],
847
+ collection_name: str,
848
+ query: dict,
849
+ replacement: dict,
850
+ add_timestamp: bool = False,
851
+ convert_mixed_lists_to_strings: bool = False,
852
+ mongo_client: pymongo.MongoClient = None,
853
+ close_client: bool = False
854
+ ):
855
+ """
856
+ Replace one entry in a MongoDB collection by query.
857
+ :param database: String or the database object.
858
+ str - the name of the database. In this case the database object will be created.
859
+ pymongo.database.Database - the database object that will be used instead of creating a new one.
860
+ :param collection_name: str, the name of the collection.
861
+ :param query: dict, the query to search for.
862
+ Example, search for all entries with column name 'name' equal to 'John':
863
+ query = {'name': 'John'}
864
+ Find by Object id:
865
+ query = {'_id': ObjectId('5f3e3b3b4b9f3b3b4b9f3b3b')}
866
+ :param replacement: dict, the replacement to apply.
867
+ :param add_timestamp: bool, if True, a current time timestamp will be added to the object.
868
+ :param convert_mixed_lists_to_strings: bool, if True, mixed lists or tuples when entries are strings and integers,
869
+ the integers will be converted to strings.
870
+ :param mongo_client: pymongo.MongoClient, the connection object.
871
+ If None, a new connection will be created to default URI.
872
+ :param close_client: bool, if True, the connection will be closed after the operation.
873
+
874
+ :return: None
875
+ """
876
+
877
+ if not mongo_client:
878
+ mongo_client = connect()
879
+ close_client = True
880
+
881
+ db = _get_pymongo_db_from_string_or_pymongo_db(database, mongo_client)
882
+ collection = db[collection_name]
883
+
884
+ if convert_mixed_lists_to_strings:
885
+ replacement = dicts.convert_int_to_str_in_mixed_lists(replacement)
886
+
887
+ if add_timestamp:
888
+ timestamp = datetime.datetime.now()
889
+ replacement['timestamp'] = timestamp
890
+
891
+ result = collection.replace_one(query, replacement)
892
+ if result.matched_count == 0:
893
+ raise MongoDBReplaceOneError("No document found to replace.")
894
+
895
+ if close_client:
896
+ mongo_client.close()
897
+
898
+ return result
899
+
900
+
617
901
  def count_entries_in_collection(
618
902
  database: Union[str, pymongo.database.Database],
619
903
  collection_name: str,
@@ -76,7 +76,7 @@ class Sender:
76
76
  destination_address = self.ssl_socket.server_hostname
77
77
  destination: str = f'[{source_address}:{source_port}<->{destination_address}:{destination_port}]'
78
78
 
79
- error_class_type = str(type(e)).replace("<class '", '').replace("'>", '')
79
+ error_class_type = type(e).__name__
80
80
  exception_error = tracebacks.get_as_string(one_line=True)
81
81
 
82
82
  if 'ssl' in error_class_type.lower():
@@ -18,6 +18,7 @@ from .. import cryptographyw
18
18
  from ..loggingw import loggingw
19
19
  from ...print_api import print_api
20
20
  from ...file_io import file_io
21
+ from ...basics import tracebacks
21
22
 
22
23
 
23
24
  class SocketClient:
@@ -55,6 +56,12 @@ class SocketClient:
55
56
  self.connection_ip = connection_ip
56
57
  self.dns_servers_list = dns_servers_list
57
58
 
59
+ if logger:
60
+ # Create child logger for the provided logger with the module's name.
61
+ self.logger: logging.Logger = loggingw.get_logger_with_level(f'{logger.name}.{Path(__file__).stem}')
62
+ else:
63
+ self.logger: logging.Logger = logger
64
+
58
65
  self.socket_instance = None
59
66
 
60
67
  # If 'connection_ip' was specified, but no 'dns_servers_list', then this IP will be used for 'socket.connect()'.
@@ -68,12 +75,6 @@ class SocketClient:
68
75
  elif self.connection_ip and self.dns_servers_list:
69
76
  raise ValueError("Both 'connection_ip' and 'dns_servers_list' were specified.")
70
77
 
71
- if logger:
72
- # Create child logger for the provided logger with the module's name.
73
- self.logger: logging.Logger = loggingw.get_logger_with_level(f'{logger.name}.{Path(__file__).stem}')
74
- else:
75
- self.logger: logging.Logger = logger
76
-
77
78
  # Function to create SSL socket to destination service
78
79
  def create_service_socket(self):
79
80
  # If TLS is enabled.
@@ -86,8 +87,18 @@ class SocketClient:
86
87
  return creator.wrap_socket_with_ssl_context_client___default_certs___ignore_verification(
87
88
  socket_object, self.service_name)
88
89
 
89
- def service_connection(self):
90
- """ Function to establish connection to server """
90
+ def service_connection(
91
+ self
92
+ ) -> tuple[
93
+ Union[socket.socket, ssl.SSLSocket, None],
94
+ Union[str, None]]:
95
+ """
96
+ Function to establish connection to server
97
+
98
+ :return: Tuple with socket object and error string.
99
+ If connection was successful, the error string will be None.
100
+ If connection wasn't successful, the socket object will be None.
101
+ """
91
102
  # Check if socket to service domain exists.
92
103
  # If not
93
104
  if not self.socket_instance:
@@ -106,8 +117,8 @@ class SocketClient:
106
117
  f"Socket already defined to [{self.service_name}:{self.service_port}]. "
107
118
  f"Should be connected - Reusing.")
108
119
  # Since, restart the function each send_receive iteration, and there's still a connection we need to
109
- # set it True, or the socket object will be nullified in the next step.
110
- return True
120
+ # return the socket, or the socket object will be nullified in the next step.
121
+ return self.socket_instance
111
122
 
112
123
  # If 'dns_servers_list' was provided, we will resolve the domain to ip through these servers.
113
124
  if self.dns_servers_list:
@@ -125,11 +136,13 @@ class SocketClient:
125
136
  # Get only the first entry of the list of IPs [0]
126
137
  self.connection_ip = function_server_address[0].to_text()
127
138
  self.logger.info(f"Resolved to [{self.connection_ip}]")
128
- except dns.resolver.NXDOMAIN:
129
- self.logger.error(f"Domain {self.service_name} doesn't exist - Couldn't resolve with "
130
- f"{self.dns_servers_list}.")
131
- pass
132
- return None
139
+ except dns.resolver.NXDOMAIN as e:
140
+ exception_type: str = type(e).__name__
141
+ error_string = (
142
+ f"Socket Client Connect: {exception_type}: "
143
+ f"Domain {self.service_name} doesn't exist - Couldn't resolve with {self.dns_servers_list}.")
144
+ print_api(error_string, logger=self.logger, logger_method='error')
145
+ return None, error_string
133
146
 
134
147
  # If DNS was resolved correctly or DNS servers weren't specified - we can try connecting.
135
148
  # If 'connection_ip' was manually specified or resolved with 'dnspython' - the connection
@@ -144,43 +157,25 @@ class SocketClient:
144
157
  try:
145
158
  # "connect()" to the server using address and port
146
159
  self.socket_instance.connect((destination, self.service_port))
147
- except ConnectionRefusedError:
148
- message = f"Couldn't connect to: {self.service_name}. The server is unreachable - Connection refused."
149
- print_api(message, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
150
- # Socket close will be handled in the thread_worker_main
151
- pass
152
- return None
153
- except ConnectionAbortedError:
154
- message = f"Connection was aborted (by the software on host) to {self.service_name}."
155
- print_api(message, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
156
- # Socket close will be handled in the thread_worker_main
157
- pass
158
- return None
159
- except socket.gaierror:
160
- message = f"Couldn't resolve [{self.service_name}] to IP using default methods. " \
161
- f"Domain doesn't exist or there's no IP assigned to it."
162
- print_api(message, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
163
- # Socket close will be handled in the thread_worker_main
164
- pass
165
- return None
166
- except ssl.SSLError:
167
- message = f"SSLError raised on connection to {self.service_name}."
168
- print_api(message, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
169
- # Socket close will be handled in the thread_worker_main
170
- pass
171
- return None
172
- except TimeoutError:
173
- message = f"TimeoutError raised on connection to {self.service_name}."
174
- print_api(message, logger=self.logger, logger_method='error', traceback_string=True, oneline=True)
175
- # Socket close will be handled in the thread_worker_main
176
- pass
177
- return None
178
- except ValueError as e:
179
- message = f'{str(e)} | on connect to [{self.service_name}].'
180
- print_api(message, logger=self.logger, logger_method='error')
181
- # Socket close will be handled in the thread_worker_main
182
- pass
183
- return None
160
+ except Exception as e:
161
+ exception_type: str = type(e).__name__
162
+ exception_error: str = tracebacks.get_as_string(one_line=True)
163
+ error_string: str = f"Socket Client Connect: {destination}: {exception_type}"
164
+
165
+ if exception_type in ['ConnectionRefusedError', 'ConnectionAbortedError', 'ConnectionResetError',
166
+ 'ssl.SSLError', 'TimeoutError']:
167
+ error_message: str = f"{error_string}: {exception_error}"
168
+ print_api(error_message, logger=self.logger, logger_method='error')
169
+ return None, error_message
170
+ elif exception_type == 'socket.gaierror':
171
+ custom_error_message: str = (
172
+ f"Couldn't resolve [{self.service_name}] to IP using default methods. "
173
+ f"Domain doesn't exist or there's no IP assigned to it.")
174
+ error_message: str = f"{error_string}: {custom_error_message}"
175
+ print_api(error_message, logger=self.logger, logger_method='error')
176
+ return None, error_message
177
+ else:
178
+ raise e
184
179
 
185
180
  # If everything was fine, we'll log the connection.
186
181
  self.logger.info("Connected...")
@@ -200,14 +195,12 @@ class SocketClient:
200
195
  def send_receive_to_service(self, request_bytes: bytearray):
201
196
  # Define variables
202
197
  function_service_data = None
203
- error_string = None
198
+ error_message = None
204
199
 
200
+ service_socket, error_message = self.service_connection()
205
201
  # If connection to service server wasn't successful
206
- if not self.service_connection():
207
- error_string = "Wasn't able to connect to service, closing the destination service socket"
208
- print_api(error_string, logger=self.logger, logger_method='error')
209
-
210
- # We'll close the socket and nullify the object
202
+ if error_message:
203
+ # Wasn't able to connect to service, closing the destination service socket and nullify the object.
211
204
  self.close_socket()
212
205
  # If the connection to the service was successful
213
206
  else:
@@ -227,7 +220,7 @@ class SocketClient:
227
220
 
228
221
  # If the socket disconnected on data send
229
222
  if error_on_send:
230
- error_string = f"Service socket closed on data send: {error_on_send}"
223
+ error_message = f"Service socket closed on data send: {error_on_send}"
231
224
 
232
225
  # We'll close the socket and nullify the object
233
226
  self.close_socket()
@@ -238,12 +231,12 @@ class SocketClient:
238
231
 
239
232
  # If data received is empty meaning the socket was closed on the other side
240
233
  if not function_service_data:
241
- error_string = "Service server closed the connection on receive"
234
+ error_message = "Service server closed the connection on receive"
242
235
 
243
236
  # We'll close the socket and nullify the object
244
237
  self.close_socket()
245
238
 
246
- return function_service_data, error_string, self.connection_ip, self.socket_instance
239
+ return function_service_data, error_message, self.connection_ip, self.socket_instance
247
240
 
248
241
  def send_receive_message_list_with_interval(
249
242
  self, requests_bytes_list: list, intervals_list: list, intervals_defaults: int, cycles: int = 1):
@@ -316,15 +309,15 @@ class SocketClient:
316
309
  # be passed.
317
310
  # If there was connection error or socket close, then "ssl_socket" of the "service_client"
318
311
  # will be empty.
319
- response_raw_bytes, error_string, self.connection_ip, service_ssl_socket = \
312
+ response_raw_bytes, error_message, self.connection_ip, service_ssl_socket = \
320
313
  self.send_receive_to_service(request_raw_bytes)
321
314
 
322
315
  # Adding the response to responses list. Same for error.
323
316
  responses_list.append(response_raw_bytes)
324
- errors_list.append(error_string)
317
+ errors_list.append(error_message)
325
318
 
326
319
  self.logger.info(f"Response: {response_raw_bytes}")
327
- self.logger.info(f"Error: {error_string}")
320
+ self.logger.info(f"Error: {error_message}")
328
321
 
329
322
  # So if the socket was closed and there was an error we can break the loop.
330
323
  # This is needed for more complex operations
@@ -359,7 +352,7 @@ class SocketClient:
359
352
  raise ValueError("If 'save_as_file' is True, then 'cert_file_path' must be provided.")
360
353
 
361
354
  # Connect and get the connected socket.
362
- server_socket_for_certificate = self.service_connection()
355
+ server_socket_for_certificate, error_message = self.service_connection()
363
356
  # Get the DER byte certificate from the socket.
364
357
  certificate_from_socket_der_bytes = ssl_base.get_certificate_from_socket(server_socket_for_certificate)
365
358
  print_api('Fetched certificate from socket.', logger=self.logger, **kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atomicshop
3
- Version: 2.16.34
3
+ Version: 2.16.36
4
4
  Summary: Atomic functions and classes to make developer life easier
5
5
  Author: Denis Kras
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
- atomicshop/__init__.py,sha256=CBAptMUJXJVgtdUNjx4iw5nayaXCSZahEQHtGmfl1oM,124
1
+ atomicshop/__init__.py,sha256=OeOIovtO11WvqryrnXL0p8647yTwRAMmL0vtoDpvg5A,124
2
2
  atomicshop/_basics_temp.py,sha256=6cu2dd6r2dLrd1BRNcVDKTHlsHs_26Gpw8QS6v32lQ0,3699
3
3
  atomicshop/_create_pdf_demo.py,sha256=Yi-PGZuMg0RKvQmLqVeLIZYadqEZwUm-4A9JxBl_vYA,3713
4
4
  atomicshop/_patch_import.py,sha256=ENp55sKVJ0e6-4lBvZnpz9PQCt3Otbur7F6aXDlyje4,6334
@@ -48,9 +48,10 @@ atomicshop/web.py,sha256=DkpdUYiwRu_Du5J8LlUyxLj-NAEnDLfEwfVlSI22trM,11642
48
48
  atomicshop/a_installs/ubuntu/docker_rootless.py,sha256=9IPNtGZYjfy1_n6ZRt7gWz9KZgR6XCgevjqq02xk-o0,281
49
49
  atomicshop/a_installs/ubuntu/docker_sudo.py,sha256=JzayxeyKDtiuT4Icp2L2LyFRbx4wvpyN_bHLfZ-yX5E,281
50
50
  atomicshop/a_installs/ubuntu/elastic_search_and_kibana.py,sha256=yRB-l1zBxdiN6av-FwNkhcBlaeu4zrDPjQ0uPGgpK2I,244
51
- atomicshop/a_installs/ubuntu/pycharm.py,sha256=eFk_JDIzJJhxCuYFkjxOYHYMnZBC62Q9n5ZaFt4z9cs,132
51
+ atomicshop/a_installs/ubuntu/mongodb.py,sha256=xuRJS1qqOZ0EZp7of5R3tTjSu6CwBIxYg8-NaM7othE,230
52
+ atomicshop/a_installs/ubuntu/pycharm.py,sha256=Ld7YQBwPxrjuZcTG1K4kZqjbBdt8aooCVRa15u5FOmE,157
52
53
  atomicshop/a_installs/win/fibratus.py,sha256=TU4e9gdZ_zI73C40uueJ59pD3qmN-UFGdX5GFoVf6cM,179
53
- atomicshop/a_installs/win/mongodb.py,sha256=5k9sFWM_9Zh5ShutH2IhGvAo7nrLkOIeUPzhoKvEsx8,171
54
+ atomicshop/a_installs/win/mongodb.py,sha256=AqyItXu19aaoe49pppDxtEkXey6PMy0PoT2Y_RmPpPE,179
54
55
  atomicshop/a_installs/win/pycharm.py,sha256=j_RSd7aDOyC3yDd-_GUTMLlQTmDrqtVFG--oUfGLiZk,140
55
56
  atomicshop/a_installs/win/wsl_ubuntu_lts.py,sha256=dZbPRLNKFeMd6MotjkE6UDY9cOiIaaclIdR1kGYWI50,139
56
57
  atomicshop/a_mains/dns_gateway_setting.py,sha256=ncc2rFQCChxlNP59UshwmTonLqC6MWblrVAzbbz-13M,149
@@ -89,7 +90,7 @@ atomicshop/basics/dicts.py,sha256=DeYHIh940pMMBrFhpXt4dsigFVYzTrlqWymNo4Pq_Js,14
89
90
  atomicshop/basics/dicts_nested.py,sha256=StYxYnYPa0SEJr1lmEwAv5zfERWWqoULeyG8e0zRAwE,4107
90
91
  atomicshop/basics/enumerations.py,sha256=41VVQYh_vnVapggxKg2IRU5e_EiMpZzX1n1mtxvoSzM,1364
91
92
  atomicshop/basics/enums.py,sha256=aAk1jFeQLvrC4NOpk9kgyX1-DCBr2ArPhZ8Ad7cMAVA,3537
92
- atomicshop/basics/exceptions.py,sha256=-1Gu8bHA3hrf11mmeaPuVE73jWV3EOyRgh2vSpWfveg,504
93
+ atomicshop/basics/exceptions.py,sha256=8mhhdQloYVz8D3u16I5O_cMeuIf5pPTXoi1iY94O9zw,616
93
94
  atomicshop/basics/guids.py,sha256=iRx5n18ATZWhpo748BwEjuLWLsu9y3OwF5-Adp-Dtik,403
94
95
  atomicshop/basics/hexs.py,sha256=i8CTG-J0TGGa25yFSbWEvpVyHFnof_qSWUrmXY-ylKM,1054
95
96
  atomicshop/basics/if_else.py,sha256=MakivJChofZCpr0mOVjwCthzpiaBxXVB-zv7GwMOqVo,202
@@ -124,7 +125,7 @@ atomicshop/file_io/xmls.py,sha256=zh3SuK-dNaFq2NDNhx6ivcf4GYCfGM8M10PcEwDSpxk,21
124
125
  atomicshop/mitm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
125
126
  atomicshop/mitm/config_static.py,sha256=ROAtbibSWSsF3BraUbhu-QO3MPIFqYY5KUKgsQbiSkk,7813
126
127
  atomicshop/mitm/config_toml_editor.py,sha256=2p1CMcktWRR_NW-SmyDwylu63ad5e0-w1QPMa8ZLDBw,1635
127
- atomicshop/mitm/connection_thread_worker.py,sha256=fybUBcZckgF7TC_P1z2yIYGH6ATX7jQEfsQSBuddt2s,16531
128
+ atomicshop/mitm/connection_thread_worker.py,sha256=YdPL2E2ZYV5nnuaXXtyBZssT4qDalVb05BVO62MwW0k,16627
128
129
  atomicshop/mitm/import_config.py,sha256=ZKQXxbtjVqzN9fpRrMwPNQREecH06RG8F_nXZAKTUJM,8182
129
130
  atomicshop/mitm/initialize_engines.py,sha256=VyJE8QnzlgD3QbX5inz5o6rC3zQ3is9CeTL7-B10g1w,8292
130
131
  atomicshop/mitm/message.py,sha256=URR5JKSuAT8XmGIkyprEjlPW2GW4ef_gfUz_GgcFseE,2184
@@ -253,9 +254,10 @@ atomicshop/wrappers/loggingw/loggers.py,sha256=mmM__XR3W4QC82wbsDRG_M4_0JYGGEP0Q
253
254
  atomicshop/wrappers/loggingw/loggingw.py,sha256=uLY7DJS-3xIYQBRvI--9eFvdcnvsWSXmtJKS-gTRfjM,20863
254
255
  atomicshop/wrappers/loggingw/reading.py,sha256=sCNlgqLNH5XdKqOOjjEox7CvViMHzs6h7-hwCnx4NKk,17566
255
256
  atomicshop/wrappers/mongodbw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
256
- atomicshop/wrappers/mongodbw/install_mongodb.py,sha256=3ZPqrXxj3lC-PnAKGXclylLuOqsbyXYeUpb5iGjdeUU,6626
257
+ atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py,sha256=pmI9AwWJ2cv5h8GionSpSJkllg6kfp0M381pk6y4Y5U,4015
258
+ atomicshop/wrappers/mongodbw/install_mongodb_win.py,sha256=3ZPqrXxj3lC-PnAKGXclylLuOqsbyXYeUpb5iGjdeUU,6626
257
259
  atomicshop/wrappers/mongodbw/mongo_infra.py,sha256=IjEF0jPzQz866MpTm7rnksnyyWQeUT_B2h2DA9ryAio,2034
258
- atomicshop/wrappers/mongodbw/mongodbw.py,sha256=oM2pS-M0EI7HhewWY0ri_Ri9U5GOBo0CehPmo4Yas3o,32736
260
+ atomicshop/wrappers/mongodbw/mongodbw.py,sha256=DlKiZaB__OIkWNQgQxKD569qrzm1l7m1TWKH2NTe8qs,44278
259
261
  atomicshop/wrappers/nodejsw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
260
262
  atomicshop/wrappers/nodejsw/install_nodejs.py,sha256=TKGa3jSlSqZTL2NA0nMkWDFtlkz7rxGGn44ywCg7MN8,5228
261
263
  atomicshop/wrappers/playwrightw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -301,17 +303,17 @@ atomicshop/wrappers/socketw/dns_server.py,sha256=VHV6s7vd0zqqW3dhE6li-260YRzmEB5
301
303
  atomicshop/wrappers/socketw/exception_wrapper.py,sha256=B-X5SHLSUIWToihH2MKnOB1F4A81_X0DpLLfnYKYbEc,7067
302
304
  atomicshop/wrappers/socketw/get_process.py,sha256=aJC-_qFUv3NgWCSUzDI72E4z8_-VTZE9NVZ0CwUoNlM,5698
303
305
  atomicshop/wrappers/socketw/receiver.py,sha256=XVvWOoeCo3vA0O5p19ryi-hcDIyx382WNG7WzMNVeYk,9322
304
- atomicshop/wrappers/socketw/sender.py,sha256=5HPrgTS2pA1g-jbG1TUtR7drHT1Z_8UevlRCTwW7dgY,5007
306
+ atomicshop/wrappers/socketw/sender.py,sha256=vjgU1TaADJjaYiZOkLzfxcdCbmkvjhEhVjSV5mmIbw8,4969
305
307
  atomicshop/wrappers/socketw/sni.py,sha256=J1kPnQ77XwKN1pO5aOI1c_VfhuivCm95OOaQxMpPuZ0,17627
306
- atomicshop/wrappers/socketw/socket_client.py,sha256=XC-YaqA1wu0rvWQ9Q99DWLxcycKPkPc72pSnflzalfo,20320
308
+ atomicshop/wrappers/socketw/socket_client.py,sha256=TduqAFBHvPWojMnj4q6--1bQyLZpv8os0vYJ1sBupRM,19855
307
309
  atomicshop/wrappers/socketw/socket_server_tester.py,sha256=Qobmh4XV8ZxLUaw-eW4ESKAbeSLecCKn2OWFzMhadk0,6420
308
310
  atomicshop/wrappers/socketw/socket_wrapper.py,sha256=WtylpezgIIBuz-A6PfM0hO1sm9Exd4j3qhDXcFc74-E,35567
309
311
  atomicshop/wrappers/socketw/ssl_base.py,sha256=kmiif84kMhBr5yjQW17p935sfjR5JKG0LxIwBA4iVvU,2275
310
312
  atomicshop/wrappers/socketw/statistics_csv.py,sha256=w1AH-zf4mBuT4euf28UKij9ihM-b1BRU9Qfby0QDdqI,2957
311
313
  atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
312
314
  atomicshop/wrappers/winregw/winreg_network.py,sha256=bQ8Jql8bVGBJ0dt3VQ56lga_1LBOMLI3Km_otvvbU6c,7138
313
- atomicshop-2.16.34.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
314
- atomicshop-2.16.34.dist-info/METADATA,sha256=BKW8IYDADJjg7d-7-exeW7BZT5mboP95IsNtRy7lom4,10473
315
- atomicshop-2.16.34.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
316
- atomicshop-2.16.34.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
317
- atomicshop-2.16.34.dist-info/RECORD,,
315
+ atomicshop-2.16.36.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
316
+ atomicshop-2.16.36.dist-info/METADATA,sha256=7WSrbRJAwsJH5uWhqieDDjUdx-uUgxLcqKzfV3nFCDE,10473
317
+ atomicshop-2.16.36.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
318
+ atomicshop-2.16.36.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
319
+ atomicshop-2.16.36.dist-info/RECORD,,