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

@@ -1,4 +1,3 @@
1
- import logging
2
1
  import threading
3
2
  import multiprocessing
4
3
  import time
@@ -6,12 +5,11 @@ import datetime
6
5
 
7
6
  import atomicshop # Importing atomicshop package to get the version of the package.
8
7
 
9
- from .. import filesystem, queues, dns, on_exit
8
+ from .. import filesystem, queues, dns, on_exit, print_api
10
9
  from ..permissions import permissions
11
10
  from ..python_functions import get_current_python_version_string, check_python_version_compliance
12
11
  from ..wrappers.socketw import socket_wrapper, dns_server, base
13
12
  from ..wrappers.loggingw import loggingw
14
- from ..print_api import print_api
15
13
 
16
14
  from .initialize_engines import ModuleCategory
17
15
  from .connection_thread_worker import thread_worker_main
@@ -33,14 +31,14 @@ MITM_ERROR_LOGGER: loggingw.ExceptionCsvLogger = None
33
31
  def exit_cleanup():
34
32
  if permissions.is_admin():
35
33
  is_dns_dynamic, current_dns_gateway = dns.get_default_dns_gateway()
36
- print_api(f'Current DNS Gateway: {current_dns_gateway}')
34
+ print_api.print_api(f'Current DNS Gateway: {current_dns_gateway}')
37
35
 
38
36
  if is_dns_dynamic != NETWORK_INTERFACE_IS_DYNAMIC or \
39
37
  (not is_dns_dynamic and current_dns_gateway != NETWORK_INTERFACE_IPV4_ADDRESS_LIST):
40
38
  dns.set_connection_dns_gateway_dynamic(use_default_connection=True)
41
- print_api("Returned default DNS gateway...", color='blue')
39
+ print_api.print_api("Returned default DNS gateway...", color='blue')
42
40
 
43
- print_api(RECS_PROCESS_INSTANCE.is_alive())
41
+ print_api.print_api(RECS_PROCESS_INSTANCE.is_alive())
44
42
  RECS_PROCESS_INSTANCE.terminate()
45
43
  RECS_PROCESS_INSTANCE.join()
46
44
 
@@ -59,7 +57,8 @@ def mitm_server(config_file_path: str):
59
57
  return result
60
58
 
61
59
  global MITM_ERROR_LOGGER
62
- MITM_ERROR_LOGGER = loggingw.ExceptionCsvLogger(EXCEPTIONS_CSV_LOGGER_NAME, config_static.LogRec.logs_path)
60
+ MITM_ERROR_LOGGER = loggingw.ExceptionCsvLogger(
61
+ logger_name=EXCEPTIONS_CSV_LOGGER_NAME, directory_path=config_static.LogRec.logs_path)
63
62
 
64
63
  # Create folders.
65
64
  filesystem.create_directory(config_static.LogRec.logs_path)
@@ -159,70 +158,72 @@ def mitm_server(config_file_path: str):
159
158
  # === EOF Initialize Reference Module ==========================================================================
160
159
  # === Engine logging ===========================================================================================
161
160
  # Printing the parsers using "start=1" for index to start counting from "1" and not "0"
162
- print_api(f"[*] Found Engines:", logger=system_logger)
161
+ print_api.print_api(f"[*] Found Engines:", logger=system_logger)
163
162
  for index, engine in enumerate(engines_list, start=1):
164
163
  message = f"[*] {index}: {engine.engine_name} | {engine.domain_list}"
165
- print_api(message, logger=system_logger)
164
+ print_api.print_api(message, logger=system_logger)
166
165
 
167
166
  message = (f"[*] Modules: {engine.parser_class_object.__name__}, "
168
167
  f"{engine.responder_class_object.__name__}, "
169
168
  f"{engine.recorder_class_object.__name__}")
170
- print_api(message, logger=system_logger)
169
+ print_api.print_api(message, logger=system_logger)
171
170
 
172
171
  if config_static.DNSServer.enable:
173
- print_api("DNS Server is enabled.", logger=system_logger)
172
+ print_api.print_api("DNS Server is enabled.", logger=system_logger)
174
173
 
175
174
  # If engines were found and dns is set to route by the engine domains.
176
175
  if engines_list and config_static.DNSServer.resolve_to_tcp_server_only_engine_domains:
177
- print_api("Engine domains will be routed by the DNS server to Built-in TCP Server.", logger=system_logger)
176
+ print_api.print_api(
177
+ "Engine domains will be routed by the DNS server to Built-in TCP Server.", logger=system_logger)
178
178
  # If engines were found, but the dns isn't set to route to engines.
179
179
  elif engines_list and not config_static.DNSServer.resolve_to_tcp_server_only_engine_domains:
180
180
  message = f"[*] Engine domains found, but the DNS routing is set not to use them for routing."
181
- print_api(message, color="yellow", logger=system_logger)
181
+ print_api.print_api(message, color="yellow", logger=system_logger)
182
182
  elif not engines_list and config_static.DNSServer.resolve_to_tcp_server_only_engine_domains:
183
183
  error_message = (
184
184
  f"No engines were found in: [{config_static.MainConfig.ENGINES_DIRECTORY_PATH}]\n"
185
185
  f"But the DNS routing is set to use them for routing.\n"
186
186
  f"Please check your DNS configuration in the 'config.ini' file.")
187
- print_api(error_message, color="red")
187
+ print_api.print_api(error_message, color="red")
188
188
  return 1
189
189
 
190
190
  if config_static.DNSServer.resolve_to_tcp_server_all_domains:
191
- print_api("All domains will be routed by the DNS server to Built-in TCP Server.", logger=system_logger)
191
+ print_api.print_api(
192
+ "All domains will be routed by the DNS server to Built-in TCP Server.", logger=system_logger)
192
193
 
193
194
  if config_static.DNSServer.resolve_regular:
194
- print_api(
195
+ print_api.print_api(
195
196
  "Regular DNS resolving is enabled. Built-in TCP server will not be routed to",
196
197
  logger=system_logger, color="yellow")
197
198
  else:
198
- print_api("DNS Server is disabled.", logger=system_logger, color="yellow")
199
+ print_api.print_api("DNS Server is disabled.", logger=system_logger, color="yellow")
199
200
 
200
201
  if config_static.TCPServer.enable:
201
- print_api("TCP Server is enabled.", logger=system_logger)
202
+ print_api.print_api("TCP Server is enabled.", logger=system_logger)
202
203
 
203
204
  if engines_list and not config_static.TCPServer.engines_usage:
204
205
  message = \
205
206
  f"Engines found, but the TCP server is set not to use them for processing. General responses only."
206
- print_api(message, color="yellow", logger=system_logger)
207
+ print_api.print_api(message, color="yellow", logger=system_logger)
207
208
  elif engines_list and config_static.TCPServer.engines_usage:
208
209
  message = f"Engines found, and the TCP server is set to use them for processing."
209
- print_api(message, logger=system_logger)
210
+ print_api.print_api(message, logger=system_logger)
210
211
  elif not engines_list and config_static.TCPServer.engines_usage:
211
212
  error_message = (
212
213
  f"No engines were found in: [{config_static.MainConfig.ENGINES_DIRECTORY_PATH}]\n"
213
214
  f"But the TCP server is set to use them for processing.\n"
214
215
  f"Please check your TCP configuration in the 'config.ini' file.")
215
- print_api(error_message, color="red")
216
+ print_api.print_api(error_message, color="red")
216
217
  return 1
217
218
  else:
218
- print_api("TCP Server is disabled.", logger=system_logger, color="yellow")
219
+ print_api.print_api("TCP Server is disabled.", logger=system_logger, color="yellow")
219
220
 
220
221
  # === EOF Engine Logging =======================================================================================
221
222
 
222
223
  # Assigning all the engines domains to all time domains, that will be responsible for adding new domains.
223
224
  config_static.Certificates.domains_all_times = list(domains_engine_list_full)
224
225
 
225
- print_api("Press [Ctrl]+[C] to stop.", color='blue')
226
+ print_api.print_api("Press [Ctrl]+[C] to stop.", color='blue')
226
227
 
227
228
  # Create request domain queue.
228
229
  domain_queue = queues.NonBlockQueue()
@@ -250,7 +251,7 @@ def mitm_server(config_file_path: str):
250
251
  logger=network_logger
251
252
  )
252
253
  except (dns_server.DnsPortInUseError, dns_server.DnsConfigurationValuesError) as e:
253
- print_api(e, error_type=True, color="red", logger=system_logger)
254
+ print_api.print_api(e, error_type=True, color="red", logger=system_logger)
254
255
  # Wait for the message to be printed and saved to file.
255
256
  time.sleep(1)
256
257
  return 1
@@ -296,6 +297,7 @@ def mitm_server(config_file_path: str):
296
297
  ssh_pass=config_static.ProcessName.ssh_pass,
297
298
  ssh_script_to_execute=config_static.ProcessName.ssh_script_to_execute,
298
299
  logger=listener_logger,
300
+ exceptions_logger=MITM_ERROR_LOGGER,
299
301
  statistics_logs_directory=config_static.LogRec.logs_path,
300
302
  forwarding_dns_service_ipv4_list___only_for_localhost=(
301
303
  config_static.TCPServer.forwarding_dns_service_ipv4_list___only_for_localhost),
@@ -303,12 +305,12 @@ def mitm_server(config_file_path: str):
303
305
  request_domain_from_dns_server_queue=domain_queue
304
306
  )
305
307
  except socket_wrapper.SocketWrapperPortInUseError as e:
306
- print_api(e, error_type=True, color="red", logger=system_logger)
308
+ print_api.print_api(e, error_type=True, color="red", logger=system_logger)
307
309
  # Wait for the message to be printed and saved to file.
308
310
  time.sleep(1)
309
311
  return 1
310
312
  except socket_wrapper.SocketWrapperConfigurationValuesError as e:
311
- print_api(e, error_type=True, color="red", logger=system_logger, logger_method='critical')
313
+ print_api.print_api(e, error_type=True, color="red", logger=system_logger, logger_method='critical')
312
314
  # Wait for the message to be printed and saved to file.
313
315
  time.sleep(1)
314
316
  return 1
@@ -386,7 +388,7 @@ def mitm_server_main(config_file_path: str):
386
388
  # Main function should return integer with error code, 0 is successful.
387
389
  return mitm_server(config_file_path)
388
390
  except KeyboardInterrupt:
389
- print_api("Server Stopped by [KeyboardInterrupt].", color='blue')
391
+ print_api.print_api("Server Stopped by [KeyboardInterrupt].", color='blue')
390
392
  exit_cleanup()
391
393
  return 0
392
394
  except Exception as e:
atomicshop/print_api.py CHANGED
@@ -1,19 +1,18 @@
1
1
  import sys
2
2
  import logging
3
3
 
4
- from .basics.ansi_escape_codes import ColorsBasic, get_colors_basic_dict
5
- from .wrappers.loggingw import handlers
4
+ from .basics import ansi_escape_codes
5
+ from .wrappers.loggingw import loggingw
6
6
  from .basics import tracebacks
7
7
 
8
8
 
9
- # noinspection PyUnusedLocal,PyIncorrectDocstring
10
9
  def print_api(
11
10
  message: any,
12
11
  color: any = None,
13
12
  print_end: str = '\n',
14
13
  rtl: bool = False,
15
14
  error_type: bool = False,
16
- logger: object = None,
15
+ logger: logging.Logger = None,
17
16
  logger_method: str = 'info',
18
17
  stdout: bool = True,
19
18
  stderr: bool = True,
@@ -21,8 +20,6 @@ def print_api(
21
20
  traceback_string: bool = False,
22
21
  oneline: bool = False,
23
22
  oneline_end: str = '',
24
- # raise_exception: bool = True,
25
- # exit_on_error: bool = False,
26
23
  **kwargs: object) -> None:
27
24
  """
28
25
  Function of custom api that is responsible for printing messages to console.
@@ -84,10 +81,6 @@ def print_api(
84
81
 
85
82
  # This section takes care of different types of string manipulations for message.
86
83
 
87
- # If 'exit_on_error' is set to 'True', we'll add 'exit_message' on new line after 'message'.
88
- # if error_type and exit_on_error and raise_exception:
89
- # message = message + '\n' + exit_message
90
-
91
84
  # If 'rtl' is set to 'True', we'll add Right-To-Left text conversion to 'message'.
92
85
  if rtl:
93
86
  # Lazy importing of 'bidi' library. It's not a problem since python caches the library after first import.
@@ -119,24 +112,8 @@ def print_api(
119
112
  elif logger_method == 'critical' and not color:
120
113
  color = 'red'
121
114
 
122
- if color and not logger:
123
- message = get_colors_basic_dict(color) + message + ColorsBasic.END
124
- elif color and logger:
125
- # Save the original formatter
126
- original_formatter = None
127
-
128
- # Find the stream handler and change its formatter
129
- # noinspection PyUnresolvedReferences
130
- for handler in logger.handlers:
131
- if isinstance(handler, logging.StreamHandler):
132
- # Save the original formatter
133
- original_formatter = handler.formatter
134
- original_formatter_string = handlers.get_formatter_string(handler)
135
-
136
- # Create a colored formatter for errors
137
- color_formatter = logging.Formatter(
138
- get_colors_basic_dict(color) + original_formatter_string + ColorsBasic.END)
139
- handler.setFormatter(color_formatter)
115
+ if color is not None and logger is None:
116
+ message = ansi_escape_codes.get_colors_basic_dict(color) + message + ansi_escape_codes.ColorsBasic.END
140
117
 
141
118
  # If 'online' is set to 'True', we'll output message as oneline.
142
119
  if oneline:
@@ -149,23 +126,20 @@ def print_api(
149
126
  if logger:
150
127
  # Emit to logger only if 'print_end' is default, since we can't take responsibility for anything else.
151
128
  if print_end == '\n':
152
- # Use logger to output message.
153
- getattr(logger, logger_method)(message)
154
-
155
- if stdcolor:
156
- # Restore the original formatter after logging
157
- # noinspection PyUnresolvedReferences
158
- for handler in logger.handlers:
159
- if isinstance(handler, logging.StreamHandler):
160
- handler.setFormatter(original_formatter)
129
+ if stdcolor and color is not None:
130
+ # Use logger to output message.
131
+ # with loggingw.temporary_change_logger_stream_handler_color(logger, color=color):
132
+ with loggingw.temporary_change_logger_stream_handler_emit_color(logger, color):
133
+ getattr(logger, logger_method)(message)
134
+ else:
135
+ # Use logger to output message.
136
+ getattr(logger, logger_method)(message)
161
137
  # If logger wasn't passed.
162
138
  else:
163
139
  # Use print to output the message.
164
140
  print(message, end=print_end)
165
141
 
166
142
  # = Main Section with printing cases ===============================================================================
167
- # exit_message: str = 'Exiting...'
168
-
169
143
  # Convert message to string.
170
144
  message = str(message)
171
145
 
@@ -179,20 +153,6 @@ def print_api(
179
153
  if error_type:
180
154
  print_or_logger()
181
155
 
182
- # ==================================
183
- # This section is responsible for ending the script.
184
-
185
- # Check if we're inside exception. In this case each of 3 entries in 'sys.exc_info()' tuple will not equal
186
- # to 'None', so picked only the first one.
187
- # if sys.exc_info()[0] and not exit_on_error:
188
- # # If 'raise_exception' is set to 'True', we'll end the script with exception.
189
- # if pass_exception:
190
- # pass
191
-
192
- # If 'exit_on_error' is set to 'True', we'll end the script.
193
- # if exit_on_error and error_type:
194
- # sys.exit()
195
-
196
156
 
197
157
  def print_status(
198
158
  prefix_string: str,
@@ -2,8 +2,7 @@ from typing import Union
2
2
  import threading
3
3
  import multiprocessing.managers
4
4
 
5
- from .print_api import print_api
6
- from . import system_resources
5
+ from . import system_resources, print_api
7
6
 
8
7
 
9
8
  class SystemResourceMonitor:
@@ -109,7 +108,7 @@ class SystemResourceMonitor:
109
108
  self.thread: Union[threading.Thread, None] = None
110
109
  # Sets the running state of the monitoring process. Needed to stop the monitoring and queue threads.
111
110
  self.running: bool = False
112
- # The shared results dictionary.
111
+ # The shared results' dictionary.
113
112
  self.results: dict = {}
114
113
 
115
114
  def start(self, print_kwargs: dict = None):
@@ -128,7 +127,8 @@ class SystemResourceMonitor:
128
127
  """
129
128
 
130
129
  while self.running:
131
- # Get the results of the system resources check function and store them in temporary results dictionary.
130
+ # Get the results of the system resources check function and store them in
131
+ # temporary results' dictionary.
132
132
  results = system_resources.check_system_resources(
133
133
  interval=interval, get_cpu=get_cpu, get_memory=get_memory,
134
134
  get_disk_io_bytes=get_disk_io_bytes, get_disk_files_count=get_disk_files_count,
@@ -149,9 +149,9 @@ class SystemResourceMonitor:
149
149
  for queue in queue_list:
150
150
  queue.put(results)
151
151
 
152
- # Update the shared results dictionary with the temporary results dictionary.
152
+ # Update the shared results dictionary with the temporary results' dictionary.
153
153
  # This is done in separate steps to avoid overwriting the special 'multiprocessing.Manager.dict' object.
154
- # So we update the shared results dictionary with the temporary results dictionary.
154
+ # So we update the shared results dictionary with the temporary results' dictionary.
155
155
  if manager_dict is not None:
156
156
  manager_dict.update(results)
157
157
 
@@ -169,7 +169,7 @@ class SystemResourceMonitor:
169
169
  self.thread.daemon = True
170
170
  self.thread.start()
171
171
  else:
172
- print_api("Monitoring is already running.", color='yellow', **print_kwargs)
172
+ print_api.print_api("Monitoring is already running.", color='yellow', **print_kwargs)
173
173
 
174
174
  def get_results(self) -> dict:
175
175
  """
@@ -258,7 +258,7 @@ def start_monitoring(
258
258
  )
259
259
  SYSTEM_RESOURCES_MONITOR.start()
260
260
  else:
261
- print_api("System resources monitoring is already running.", color='yellow', **(print_kwargs or {}))
261
+ print_api.print_api("System resources monitoring is already running.", color='yellow', **(print_kwargs or {}))
262
262
 
263
263
 
264
264
  def stop_monitoring():
@@ -5,9 +5,8 @@ import shutil
5
5
  import threading
6
6
  import multiprocessing.managers
7
7
 
8
- from .print_api import print_api
9
8
  from .wrappers.psutilw import cpus, memories, disks
10
- from . import system_resource_monitor
9
+ from . import system_resource_monitor, print_api
11
10
 
12
11
 
13
12
  def check_system_resources(
@@ -129,13 +128,13 @@ def wait_for_resource_availability(
129
128
 
130
129
  if result['cpu_usage'] < cpu_percent_max and result['memory_usage'] < memory_percent_max:
131
130
  break
132
- print_api(
131
+ print_api.print_api(
133
132
  f"Waiting for resources to be available... "
134
133
  f"CPU: {result['cpu_usage']}%, Memory: {result['memory_usage']}%", color='yellow')
135
134
  time.sleep(wait_time) # Wait for 'wait_time' seconds before checking again
136
135
 
137
136
 
138
- def test_disk_speed_with_monitoring(
137
+ def _test_disk_speed_with_monitoring(
139
138
  file_settings: list[dict],
140
139
  remove_file_after_each_copy: bool = False,
141
140
  target_directory=None,
@@ -144,6 +143,7 @@ def test_disk_speed_with_monitoring(
144
143
  print_kwargs: dict = None
145
144
  ):
146
145
  """
146
+ THIS IS NOT TESTED.
147
147
  Generates files and performs write and read operations in the specified target directory,
148
148
  while monitoring disk I/O speeds in a separate thread. Returns the maximum read and write rates,
149
149
  and the total operation time.
@@ -171,7 +171,7 @@ def test_disk_speed_with_monitoring(
171
171
  if monitoring:
172
172
  system_resource_monitor.start_monitoring(
173
173
  interval=1, get_cpu=False, get_memory=False, get_disk_io_bytes=True, get_disk_used_percent=False,
174
- get_disk_files_count=True, calculate_maximum_changed_disk_io=True, use_queue=True)
174
+ get_disk_files_count=True, calculate_maximum_changed_disk_io=True)
175
175
 
176
176
  if target_directory is None:
177
177
  target_directory = tempfile.mkdtemp()
@@ -196,7 +196,7 @@ def test_disk_speed_with_monitoring(
196
196
  shutil.copy(src_file_path, dest_directory)
197
197
 
198
198
  target_file_path = os.path.join(dest_directory, os.path.basename(src_file_path))
199
- print_api(f"Copied: {target_file_path}", **(print_kwargs or {}))
199
+ print_api.print_api(f"Copied: {target_file_path}", **(print_kwargs or {}))
200
200
 
201
201
  # Measure read speed.
202
202
  with open(target_file_path, "rb") as file:
@@ -212,7 +212,7 @@ def test_disk_speed_with_monitoring(
212
212
 
213
213
  overall_end_time = time.time()
214
214
  total_execution_time = overall_end_time - overall_start_time
215
- print_api(f"Total execution time: {total_execution_time}", **(print_kwargs or {}))
215
+ print_api.print_api(f"Total execution time: {total_execution_time}", **(print_kwargs or {}))
216
216
 
217
217
  # Cleanup. Remove all created files and directories.
218
218
  shutil.rmtree(source_directory)
@@ -220,7 +220,7 @@ def test_disk_speed_with_monitoring(
220
220
 
221
221
  if monitoring:
222
222
  # Stop the I/O monitoring.
223
- max_io_changes = system_resource_monitor.get_result()['maximum_disk_io']
223
+ max_io_changes = system_resource_monitor.get_results()['maximum_disk_io']
224
224
  system_resource_monitor.stop_monitoring()
225
225
 
226
226
  return total_execution_time, max_io_changes
atomicshop/web.py CHANGED
@@ -1,14 +1,14 @@
1
1
  import os
2
2
  import urllib.request
3
3
  import ssl
4
+ # noinspection PyPackageRequirements
4
5
  import certifi
5
6
 
6
- from .print_api import print_api
7
7
  from .archiver import zips
8
8
  from .urls import url_parser
9
9
  from .file_io import file_io
10
10
  from .wrappers.playwrightw import scenarios
11
- from . import filesystem
11
+ from . import filesystem, print_api
12
12
 
13
13
 
14
14
  # https://www.useragents.me/
@@ -28,10 +28,10 @@ def is_status_ok(status_code: int, **kwargs) -> bool:
28
28
  """
29
29
 
30
30
  if status_code != 200:
31
- print_api(f'URL Error, status code: {str(status_code)}', error_type=True, **kwargs)
31
+ print_api.print_api(f'URL Error, status code: {str(status_code)}', error_type=True, **kwargs)
32
32
  return False
33
33
  else:
34
- print_api('URL Status: 200 OK', color="green", **kwargs)
34
+ print_api.print_api('URL Status: 200 OK', color="green", **kwargs)
35
35
  return True
36
36
 
37
37
 
@@ -165,10 +165,10 @@ def download(
165
165
 
166
166
  def print_to_console(print_end=None):
167
167
  if file_size_bytes_int:
168
- print_api(
168
+ print_api.print_api(
169
169
  f'Downloaded bytes: {aggregated_bytes_int} / {file_size_bytes_int}', print_end=print_end, **kwargs)
170
170
  else:
171
- print_api(f'Downloaded bytes: {aggregated_bytes_int}', print_end=print_end, **kwargs)
171
+ print_api.print_api(f'Downloaded bytes: {aggregated_bytes_int}', print_end=print_end, **kwargs)
172
172
 
173
173
  # Size of the buffer to read each time from url.
174
174
  buffer_size: int = 4096
@@ -185,8 +185,8 @@ def download(
185
185
  # Build full path to file.
186
186
  file_path: str = f'{target_directory}{os.sep}{file_name}'
187
187
 
188
- print_api(f'Downloading: {file_url}', **kwargs)
189
- print_api(f'To: {file_path}', **kwargs)
188
+ print_api.print_api(f'Downloading: {file_url}', **kwargs)
189
+ print_api.print_api(f'To: {file_path}', **kwargs)
190
190
 
191
191
  # In order to use 'urllib.request', it is not enough to 'import urllib', you need to 'import urllib.request'.
192
192
  # Open the URL for data gathering with SSL context from certifi
@@ -223,10 +223,10 @@ def download(
223
223
  print_to_console()
224
224
  break
225
225
  if aggregated_bytes_int == file_size_bytes_int:
226
- print_api(f'Successfully Downloaded to: {file_path}', color="green", **kwargs)
226
+ print_api.print_api(f'Successfully Downloaded to: {file_path}', color="green", **kwargs)
227
227
  else:
228
228
  message = f'Download failed: {aggregated_bytes_int} / {file_size_bytes_int}. File: {file_path}'
229
- print_api(
229
+ print_api.print_api(
230
230
  message, error_type=True, color="red", **kwargs)
231
231
 
232
232
  return file_path
@@ -1,12 +1,18 @@
1
1
  import logging
2
+ import threading
2
3
  import os
3
4
 
4
5
 
6
+ from ...basics import ansi_escape_codes
7
+
8
+
5
9
  class HeaderFilter(logging.Filter):
6
10
  """
7
11
  A logging.Filter that writes a header to a log file if the file is empty (
8
12
  i.e., no log records have been written, i.e.2, on file rotation).
9
13
  """
14
+
15
+ # noinspection PyPep8Naming
10
16
  def __init__(self, header, baseFilename):
11
17
  super().__init__()
12
18
  self.header = header
@@ -27,6 +33,23 @@ class HeaderFilter(logging.Filter):
27
33
  return True
28
34
 
29
35
 
36
+ class ThreadColorLogFilter(logging.Filter):
37
+ """
38
+ A logging.Filter that adds color to log records based on the thread that emitted the log record.
39
+ """
40
+ def __init__(self, color: str, thread_id):
41
+ super().__init__()
42
+ self.color = color
43
+ self.thread_id = thread_id
44
+
45
+ def filter(self, record):
46
+ if threading.get_ident() == self.thread_id:
47
+ record.msg = (
48
+ ansi_escape_codes.get_colors_basic_dict(self.color) + record.msg +
49
+ ansi_escape_codes.ColorsBasic.END)
50
+ return True
51
+
52
+
30
53
  """
31
54
  A logging.Filter in Python's logging module is an object that provides a way to perform fine-grained
32
55
  filtering of log records.
@@ -8,6 +8,7 @@ import queue
8
8
  from typing import Literal, Union
9
9
  import threading
10
10
  from datetime import datetime
11
+ import contextlib
11
12
 
12
13
  from . import loggers, formatters, filters, consts
13
14
  from ... import datetimes, filesystem
@@ -482,3 +483,22 @@ def get_formatter_string(handler: logging.Handler) -> str:
482
483
  """
483
484
 
484
485
  return formatters.get_formatter_string(handler.formatter)
486
+
487
+
488
+ @contextlib.contextmanager
489
+ def temporary_change_formatter(handler: logging.Handler, formatter_string: str):
490
+ """
491
+ Context manager to temporarily change the formatter of the handler.
492
+
493
+ Example:
494
+ with temporary_change_formatter(handler, formatter_string):
495
+ # Do something with the temporary formatter.
496
+ pass
497
+ """
498
+ original_formatter = handler.formatter
499
+
500
+ try:
501
+ handler.setFormatter(logging.Formatter(formatter_string))
502
+ yield
503
+ finally:
504
+ handler.setFormatter(original_formatter)