atomicshop 2.15.11__py3-none-any.whl → 3.10.5__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.
Files changed (221) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
  3. atomicshop/a_mains/dns_gateway_setting.py +11 -0
  4. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  5. atomicshop/a_mains/github_wrapper.py +11 -0
  6. atomicshop/a_mains/install_ca_certificate.py +172 -0
  7. atomicshop/a_mains/process_from_port.py +119 -0
  8. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  9. atomicshop/a_mains/update_config_toml.py +38 -0
  10. atomicshop/basics/ansi_escape_codes.py +3 -1
  11. atomicshop/basics/argparse_template.py +2 -0
  12. atomicshop/basics/booleans.py +27 -30
  13. atomicshop/basics/bytes_arrays.py +43 -0
  14. atomicshop/basics/classes.py +149 -1
  15. atomicshop/basics/enums.py +2 -2
  16. atomicshop/basics/exceptions.py +5 -1
  17. atomicshop/basics/list_of_classes.py +29 -0
  18. atomicshop/basics/multiprocesses.py +374 -50
  19. atomicshop/basics/strings.py +72 -3
  20. atomicshop/basics/threads.py +14 -0
  21. atomicshop/basics/tracebacks.py +13 -3
  22. atomicshop/certificates.py +153 -52
  23. atomicshop/config_init.py +11 -6
  24. atomicshop/console_user_response.py +7 -14
  25. atomicshop/consoles.py +9 -0
  26. atomicshop/datetimes.py +1 -1
  27. atomicshop/diff_check.py +3 -3
  28. atomicshop/dns.py +128 -3
  29. atomicshop/etws/_pywintrace_fix.py +17 -0
  30. atomicshop/etws/trace.py +40 -42
  31. atomicshop/etws/traces/trace_dns.py +56 -44
  32. atomicshop/etws/traces/trace_tcp.py +130 -0
  33. atomicshop/file_io/csvs.py +27 -5
  34. atomicshop/file_io/docxs.py +34 -17
  35. atomicshop/file_io/file_io.py +31 -17
  36. atomicshop/file_io/jsons.py +49 -0
  37. atomicshop/file_io/tomls.py +139 -0
  38. atomicshop/filesystem.py +616 -291
  39. atomicshop/get_process_list.py +3 -3
  40. atomicshop/http_parse.py +149 -93
  41. atomicshop/ip_addresses.py +6 -1
  42. atomicshop/mitm/centered_settings.py +132 -0
  43. atomicshop/mitm/config_static.py +207 -0
  44. atomicshop/mitm/config_toml_editor.py +55 -0
  45. atomicshop/mitm/connection_thread_worker.py +875 -357
  46. atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
  47. atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
  48. atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
  49. atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
  50. atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
  51. atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
  52. atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
  53. atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
  54. atomicshop/mitm/engines/create_module_template.py +58 -14
  55. atomicshop/mitm/import_config.py +359 -139
  56. atomicshop/mitm/initialize_engines.py +160 -80
  57. atomicshop/mitm/message.py +64 -23
  58. atomicshop/mitm/mitm_main.py +892 -0
  59. atomicshop/mitm/recs_files.py +183 -0
  60. atomicshop/mitm/shared_functions.py +4 -10
  61. atomicshop/mitm/ssh_tester.py +82 -0
  62. atomicshop/mitm/statistic_analyzer.py +136 -40
  63. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
  64. atomicshop/monitor/checks/dns.py +1 -1
  65. atomicshop/networks.py +671 -0
  66. atomicshop/on_exit.py +39 -9
  67. atomicshop/package_mains_processor.py +84 -0
  68. atomicshop/permissions/permissions.py +22 -0
  69. atomicshop/permissions/ubuntu_permissions.py +239 -0
  70. atomicshop/permissions/win_permissions.py +33 -0
  71. atomicshop/print_api.py +24 -42
  72. atomicshop/process.py +24 -6
  73. atomicshop/process_poller/process_pool.py +0 -1
  74. atomicshop/process_poller/simple_process_pool.py +204 -5
  75. atomicshop/python_file_patcher.py +1 -1
  76. atomicshop/python_functions.py +27 -75
  77. atomicshop/speech_recognize.py +8 -0
  78. atomicshop/ssh_remote.py +158 -172
  79. atomicshop/system_resource_monitor.py +61 -47
  80. atomicshop/system_resources.py +8 -8
  81. atomicshop/tempfiles.py +1 -2
  82. atomicshop/urls.py +6 -0
  83. atomicshop/venvs.py +28 -0
  84. atomicshop/versioning.py +27 -0
  85. atomicshop/web.py +98 -27
  86. atomicshop/web_apis/google_custom_search.py +44 -0
  87. atomicshop/web_apis/google_llm.py +188 -0
  88. atomicshop/websocket_parse.py +450 -0
  89. atomicshop/wrappers/certauthw/certauth.py +1 -0
  90. atomicshop/wrappers/cryptographyw.py +29 -8
  91. atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
  92. atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
  93. atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
  94. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  95. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
  96. atomicshop/wrappers/ctyping/setup_device.py +466 -0
  97. atomicshop/wrappers/ctyping/win_console.py +39 -0
  98. atomicshop/wrappers/dockerw/dockerw.py +113 -2
  99. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  100. atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
  101. atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
  102. atomicshop/wrappers/factw/get_file_data.py +12 -5
  103. atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
  104. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
  105. atomicshop/wrappers/githubw.py +537 -54
  106. atomicshop/wrappers/loggingw/consts.py +1 -1
  107. atomicshop/wrappers/loggingw/filters.py +23 -0
  108. atomicshop/wrappers/loggingw/formatters.py +12 -0
  109. atomicshop/wrappers/loggingw/handlers.py +214 -107
  110. atomicshop/wrappers/loggingw/loggers.py +19 -0
  111. atomicshop/wrappers/loggingw/loggingw.py +860 -22
  112. atomicshop/wrappers/loggingw/reading.py +134 -112
  113. atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
  114. atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
  115. atomicshop/wrappers/netshw.py +271 -0
  116. atomicshop/wrappers/playwrightw/engine.py +34 -19
  117. atomicshop/wrappers/playwrightw/infra.py +5 -0
  118. atomicshop/wrappers/playwrightw/javascript.py +7 -3
  119. atomicshop/wrappers/playwrightw/keyboard.py +14 -0
  120. atomicshop/wrappers/playwrightw/scenarios.py +172 -5
  121. atomicshop/wrappers/playwrightw/waits.py +9 -7
  122. atomicshop/wrappers/powershell_networking.py +80 -0
  123. atomicshop/wrappers/psutilw/processes.py +37 -1
  124. atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
  125. atomicshop/wrappers/pyopensslw.py +9 -2
  126. atomicshop/wrappers/pywin32w/cert_store.py +116 -0
  127. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  128. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  129. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  130. atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
  131. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
  132. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
  133. atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
  134. atomicshop/wrappers/socketw/accepter.py +21 -7
  135. atomicshop/wrappers/socketw/certificator.py +216 -150
  136. atomicshop/wrappers/socketw/creator.py +190 -50
  137. atomicshop/wrappers/socketw/dns_server.py +491 -182
  138. atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
  139. atomicshop/wrappers/socketw/process_getter.py +86 -0
  140. atomicshop/wrappers/socketw/receiver.py +144 -102
  141. atomicshop/wrappers/socketw/sender.py +65 -35
  142. atomicshop/wrappers/socketw/sni.py +334 -165
  143. atomicshop/wrappers/socketw/socket_base.py +134 -0
  144. atomicshop/wrappers/socketw/socket_client.py +137 -95
  145. atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
  146. atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
  147. atomicshop/wrappers/socketw/ssl_base.py +15 -14
  148. atomicshop/wrappers/socketw/statistics_csv.py +148 -17
  149. atomicshop/wrappers/sysmonw.py +1 -1
  150. atomicshop/wrappers/ubuntu_terminal.py +65 -26
  151. atomicshop/wrappers/win_auditw.py +189 -0
  152. atomicshop/wrappers/winregw/__init__.py +0 -0
  153. atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
  154. atomicshop/wrappers/winregw/winreg_network.py +232 -0
  155. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
  156. atomicshop-3.10.5.dist-info/RECORD +306 -0
  157. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
  158. atomicshop/_basics_temp.py +0 -101
  159. atomicshop/a_installs/win/fibratus.py +0 -9
  160. atomicshop/a_installs/win/mongodb.py +0 -9
  161. atomicshop/a_installs/win/pycharm.py +0 -9
  162. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  163. atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
  164. atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
  165. atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
  166. atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
  167. atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
  168. atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
  169. atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
  170. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  171. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  172. atomicshop/addons/package_setup/Setup.cmd +0 -7
  173. atomicshop/archiver/_search_in_zip.py +0 -189
  174. atomicshop/archiver/archiver.py +0 -34
  175. atomicshop/archiver/search_in_archive.py +0 -250
  176. atomicshop/archiver/sevenz_app_w.py +0 -86
  177. atomicshop/archiver/sevenzs.py +0 -44
  178. atomicshop/archiver/zips.py +0 -293
  179. atomicshop/file_types.py +0 -24
  180. atomicshop/mitm/config_editor.py +0 -37
  181. atomicshop/mitm/engines/create_module_template_example.py +0 -13
  182. atomicshop/mitm/initialize_mitm_server.py +0 -268
  183. atomicshop/pbtkmultifile_argparse.py +0 -88
  184. atomicshop/permissions.py +0 -151
  185. atomicshop/script_as_string_processor.py +0 -38
  186. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  187. atomicshop/ssh_scripts/process_from_port.py +0 -27
  188. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  189. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  190. atomicshop/wrappers/dockerw/install_docker.py +0 -209
  191. atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
  192. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
  193. atomicshop/wrappers/ffmpegw.py +0 -125
  194. atomicshop/wrappers/fibratusw/install.py +0 -81
  195. atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
  196. atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
  197. atomicshop/wrappers/msiw.py +0 -149
  198. atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
  199. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  200. atomicshop/wrappers/psutilw/networks.py +0 -45
  201. atomicshop/wrappers/pycharmw.py +0 -81
  202. atomicshop/wrappers/socketw/base.py +0 -59
  203. atomicshop/wrappers/socketw/get_process.py +0 -107
  204. atomicshop/wrappers/wslw.py +0 -191
  205. atomicshop-2.15.11.dist-info/RECORD +0 -302
  206. /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
  207. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  208. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  209. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  210. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  211. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  212. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  213. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  214. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  215. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  216. /atomicshop/{archiver → permissions}/__init__.py +0 -0
  217. /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
  218. /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
  219. /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
  220. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
  221. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,9 @@
1
1
  from typing import Union
2
2
  import functools
3
+ import os
3
4
 
4
- from ..print_api import print_api
5
- from ..inspect_wrapper import get_target_function_default_args_and_combine_with_current
5
+ from .. import print_api
6
+ from .. import inspect_wrapper
6
7
 
7
8
 
8
9
  def get_write_file_mode_string_from_overwrite_bool(overwrite: bool) -> str:
@@ -17,9 +18,20 @@ def write_file_decorator(function_name):
17
18
  def wrapper_write_file_decorator(*args, **kwargs):
18
19
  # Put 'args' into 'kwargs' with appropriate key.
19
20
  # args, kwargs = put_args_to_kwargs(function_name, *args, **kwargs)
20
- args, kwargs = get_target_function_default_args_and_combine_with_current(function_name, *args, **kwargs)
21
+ args, kwargs = inspect_wrapper.get_target_function_default_args_and_combine_with_current(
22
+ function_name, *args, **kwargs)
21
23
 
22
- print_api(message=f"Writing file: {kwargs['file_path']}", **kwargs)
24
+ print_api.print_api(message=f"Writing file: {kwargs['file_path']}", **kwargs)
25
+
26
+ enable_long_file_path = kwargs.get('enable_long_file_path', False)
27
+ if enable_long_file_path and os.name == 'nt':
28
+ # A simpler string method would be to add '\\?\' to the beginning of the file path.
29
+ # kwargs['file_path'] = rf"\\?\{kwargs['file_path']}"
30
+
31
+ # Enable long file path.
32
+ from ctypes import windll
33
+ # Enable long file path.
34
+ windll.kernel32.SetFileAttributesW(kwargs['file_path'], 0x80)
23
35
 
24
36
  try:
25
37
  with open(kwargs['file_path'], kwargs['file_mode'], encoding=kwargs['encoding']) as output_file:
@@ -31,7 +43,7 @@ def write_file_decorator(function_name):
31
43
  except FileExistsError:
32
44
  message = f"Can't write file: {kwargs['file_path']}\n" \
33
45
  f"File exists, you should enable force/overwrite mode."
34
- print_api(message, error_type=True, logger_method='critical', **kwargs)
46
+ print_api.print_api(message, error_type=True, logger_method='critical', **kwargs)
35
47
 
36
48
  return wrapper_write_file_decorator
37
49
 
@@ -41,12 +53,13 @@ def read_file_decorator(function_name):
41
53
  def wrapper_read_file_decorator(*args, **kwargs):
42
54
  # Put 'args' into 'kwargs' with appropriate key.
43
55
  # args, kwargs = put_args_to_kwargs(function_name, *args, **kwargs)
44
- args, kwargs = get_target_function_default_args_and_combine_with_current(function_name, *args, **kwargs)
56
+ args, kwargs = inspect_wrapper.get_target_function_default_args_and_combine_with_current(
57
+ function_name, *args, **kwargs)
45
58
 
46
59
  continue_loop: bool = True
47
60
  while continue_loop:
48
61
  try:
49
- print_api(message=f"Reading file: {kwargs['file_path']}", **kwargs)
62
+ print_api.print_api(message=f"Reading file: {kwargs['file_path']}", **kwargs)
50
63
  with open(kwargs['file_path'], kwargs['file_mode'], encoding=kwargs['encoding']) as input_file:
51
64
  # Pass the 'output_file' object to kwargs that will pass the object to the executing function.
52
65
  kwargs['file_object'] = input_file
@@ -54,19 +67,19 @@ def read_file_decorator(function_name):
54
67
  return function_name(**kwargs)
55
68
  except FileNotFoundError:
56
69
  message = f"File doesn't exist: {kwargs['file_path']}"
57
- print_api(message, error_type=True, logger_method='critical', **kwargs)
70
+ print_api.print_api(message, error_type=True, logger_method='critical', **kwargs)
58
71
  raise
59
72
  except UnicodeDecodeError as exception_object:
60
73
  if kwargs["encoding"] != 'utf-8':
61
74
  message = f'File decode error, current encoding: {kwargs["encoding"]}. Will try "utf-8".'
62
- print_api(message, logger_method='error', **kwargs)
75
+ print_api.print_api(message, logger_method='error', **kwargs)
63
76
  kwargs["encoding"] = 'utf-8'
64
77
  pass
65
78
  continue
66
79
  else:
67
80
  message = f'File decode error.\n' \
68
81
  f'{exception_object}'
69
- print_api(message, merror_type=True, logger_method='critical', **kwargs)
82
+ print_api.print_api(message, merror_type=True, logger_method='critical', **kwargs)
70
83
  continue_loop = False
71
84
 
72
85
  return wrapper_read_file_decorator
@@ -78,7 +91,7 @@ def write_file(
78
91
  file_path: str,
79
92
  file_mode: str = 'w',
80
93
  encoding: str = None,
81
- convert_list_to_string: bool = False,
94
+ enable_long_file_path: bool = False,
82
95
  file_object=None,
83
96
  **kwargs) -> None:
84
97
  """
@@ -90,18 +103,19 @@ def write_file(
90
103
  Default is 'w'.
91
104
  :param encoding: string, write the file with encoding. Example: 'utf-8'. 'None' is default, since it is default
92
105
  in 'open()' function.
93
- :param convert_list_to_string: Boolean, if True, the list of strings will be converted to one string with '\n'
94
- separator between the lines.
106
+ :param enable_long_file_path: Boolean, by default Windows has a limit of 260 characters for file path. If True,
107
+ the long file path will be enabled, and the limit will be 32,767 characters.
95
108
  :param file_object: file object of the 'open()' function in the decorator. Decorator executes the 'with open()'
96
109
  statement and passes to this function. That's why the default is 'None', since we get it from the decorator.
97
110
  :return:
98
111
  """
99
112
 
100
113
  if isinstance(content, list):
101
- if convert_list_to_string:
102
- content = '\n'.join(content)
103
- else:
104
- file_object.writelines(content)
114
+ for line in content:
115
+ if not line.endswith("\n"):
116
+ file_object.write(line + "\n")
117
+ else:
118
+ file_object.write(line)
105
119
  elif isinstance(content, str):
106
120
  file_object.write(content)
107
121
  # THis will happen if the content is bytes and the file mode is 'wb'.
@@ -133,3 +133,52 @@ def is_dict_json_serializable(
133
133
  raise e
134
134
  else:
135
135
  return False, str(e)
136
+
137
+
138
+ def append_to_json(
139
+ dict_or_list: Union[dict, list],
140
+ json_file_path: str,
141
+ indent=None,
142
+ use_default_indent=False,
143
+ enable_long_file_path=False,
144
+ print_kwargs: dict = None
145
+ ) -> None:
146
+ """
147
+ Append dictionary or list of dictionaries to json file.
148
+
149
+ :param dict_or_list: dictionary or list of dictionaries to append.
150
+ :param json_file_path: full file path to the json file.
151
+ :param indent: integer number of spaces for indentation.
152
+ If 'ident=0' new lines still will be created. The most compact is 'indent=None' (from documentation)
153
+ So, using default as 'None' and not something else.
154
+ :param use_default_indent: boolean. Default indent for 'json' format in many places is '2'. So, if you don't want
155
+ to set 'indent=2', just set this to 'True'.
156
+ :param enable_long_file_path: Boolean, by default Windows has a limit of 260 characters for file path. If True,
157
+ the long file path will be enabled, and the limit will be 32,767 characters.
158
+ :param print_kwargs: dict, the print_api arguments.
159
+ :return:
160
+ """
161
+
162
+ # Read existing data from the file
163
+ try:
164
+ with open(json_file_path, 'r') as f:
165
+ current_json_file = json.load(f)
166
+ except FileNotFoundError:
167
+ current_json_file: list = []
168
+
169
+ # Append the new message to the existing data
170
+ final_json_list_of_dicts: list[dict] = []
171
+ if isinstance(current_json_file, list):
172
+ current_json_file.append(dict_or_list)
173
+ final_json_list_of_dicts = current_json_file
174
+ elif isinstance(current_json_file, dict):
175
+ final_json_list_of_dicts.append(current_json_file)
176
+ final_json_list_of_dicts.append(dict_or_list)
177
+ else:
178
+ error_message = "The current file is neither a list nor a dictionary."
179
+ raise TypeError(error_message)
180
+
181
+ # Write the data back to the file
182
+ write_json_file(
183
+ final_json_list_of_dicts, json_file_path, indent=indent, use_default_indent=use_default_indent,
184
+ enable_long_file_path=enable_long_file_path, **print_kwargs)
@@ -5,11 +5,17 @@ try:
5
5
  import tomllib
6
6
  except ModuleNotFoundError:
7
7
  # This is library from pypi.
8
+ # noinspection PyPackageRequirements
8
9
  import tomli as tomllib
9
10
 
10
11
  from . import file_io
11
12
 
12
13
 
14
+ class TomlValueNotImplementedError(Exception):
15
+ pass
16
+
17
+
18
+ # noinspection PyUnusedLocal
13
19
  @file_io.read_file_decorator
14
20
  def read_toml_file(file_path: str,
15
21
  file_mode: str = 'rb',
@@ -31,6 +37,7 @@ def read_toml_file(file_path: str,
31
37
  return tomllib.load(file_object)
32
38
 
33
39
 
40
+ # noinspection PyUnusedLocal
34
41
  @file_io.write_file_decorator
35
42
  def write_toml_file(
36
43
  toml_content: dict,
@@ -84,3 +91,135 @@ def dumps(toml_dict: dict):
84
91
  toml_string += process_item(key, value)
85
92
 
86
93
  return toml_string
94
+
95
+
96
+ def update_toml_file_with_new_config(
97
+ main_config_file_path: str,
98
+ changes_config_file_path: str = None,
99
+ changes_dict: dict = None,
100
+ new_config_file_path: str = None
101
+ ) -> None:
102
+ """
103
+ Update the old toml config file with the new values from the new toml config file.
104
+ This will update only the changed values.
105
+ If the values from the changes file aren't present in the main config file, they will not be added.
106
+
107
+ :param main_config_file_path: string, path to the main config file that you want to use as the main reference.
108
+ If you provide the 'new_config_file_path', then changes to the 'main_config_file_path' will be written there.
109
+ :param changes_config_file_path: string, the config file path that have the changes.
110
+ Only changed values will be updated to the 'main_config_file_path'.
111
+ :param changes_dict: dict, the dictionary with the changes.
112
+ Instead of providing the 'changes_config_file_path', you can provide only the dictionary with the changes.
113
+ :param new_config_file_path: string, path to the new config file.
114
+ If provided, the changes will be written to this file.
115
+ If not, the changes will be written to the 'main_config_file_path'.
116
+ """
117
+
118
+ if not changes_config_file_path and not changes_dict:
119
+ raise ValueError("You must provide either 'changes_config_file_path' or 'changes_dict'.")
120
+ if changes_config_file_path and changes_dict:
121
+ raise ValueError("You can't provide both 'changes_config_file_path' and 'changes_dict'.")
122
+
123
+ with open(main_config_file_path, 'r') as file:
124
+ main_config_file_text_lines: list = file.readlines()
125
+
126
+ main_config_file_text_lines_backup: list = list(main_config_file_text_lines)
127
+
128
+ # Read the new config file.
129
+ main_config_file_dict: dict = read_toml_file(main_config_file_path)
130
+
131
+ if not changes_dict:
132
+ changes_dict: dict = read_toml_file(changes_config_file_path)
133
+
134
+ # Update the config text lines.
135
+ for category, settings in main_config_file_dict.items():
136
+ if category not in changes_dict:
137
+ continue
138
+
139
+ for key, value in settings.items():
140
+ # If the key is in the old config file, use the old value.
141
+ if key not in changes_dict[category]:
142
+ continue
143
+
144
+ if main_config_file_dict[category][key] != changes_dict[category][key]:
145
+ # Get the line of the current category line.
146
+ current_category_line_index_in_text = None
147
+ for current_category_line_index_in_text, line in enumerate(main_config_file_text_lines):
148
+ if f"[{category}]" in line:
149
+ break
150
+
151
+ # Get the index inside the main config file dictionary of the current category.
152
+ main_config_list_of_keys: list = list(main_config_file_dict.keys())
153
+ current_category_index_in_main_config_dict = main_config_list_of_keys.index(category)
154
+
155
+ try:
156
+ next_category_name = list(
157
+ main_config_file_dict.keys())[current_category_index_in_main_config_dict + 1]
158
+ except IndexError:
159
+ next_category_name = list(main_config_file_dict.keys())[-1]
160
+
161
+ next_category_line_index_in_text = None
162
+ for next_category_line_index_in_text, line in enumerate(main_config_file_text_lines):
163
+ if f"[{next_category_name}]" in line:
164
+ break
165
+
166
+ # In case the current and the next categories are the same and the last in the file.
167
+ if next_category_line_index_in_text == current_category_line_index_in_text:
168
+ next_category_line_index_in_text = len(main_config_file_text_lines)
169
+
170
+ string_to_check_list: list = list()
171
+ if isinstance(value, bool):
172
+ string_to_check_list.append(f"{key} = {str(value).lower()}")
173
+ elif isinstance(value, int):
174
+ string_to_check_list.append(f"{key} = {value}")
175
+ elif isinstance(value, str):
176
+ string_to_check_list.append(f"{key} = '{value}'")
177
+ string_to_check_list.append(f'{key} = "{value}"')
178
+ else:
179
+ raise TomlValueNotImplementedError(f"Value type '{type(value)}' not implemented.")
180
+
181
+ # next_category_line_index_in_text = main_config_file_text_lines.index(f"[{next_category_name}]\n")
182
+ # Find the index of this line in the text file between current category line and
183
+ # the next category line.
184
+ line_index = None
185
+ found_line = False
186
+ for line_index in range(current_category_line_index_in_text, next_category_line_index_in_text):
187
+ if found_line:
188
+ line_index = line_index - 1
189
+ break
190
+ for string_to_check in string_to_check_list:
191
+ if string_to_check in main_config_file_text_lines[line_index]:
192
+ found_line = True
193
+ break
194
+
195
+ if found_line:
196
+ # If there are comments, get only them from the line. Comment will also get the '\n' character.
197
+ # noinspection PyUnboundLocalVariable
198
+ comment = main_config_file_text_lines[line_index].replace(string_to_check, '')
199
+
200
+ object_type = type(changes_dict[category][key])
201
+ if object_type == bool:
202
+ value_string_to_set = str(changes_dict[category][key]).lower()
203
+ elif object_type == str:
204
+ value_string_to_set = f"'{changes_dict[category][key]}'"
205
+ elif object_type == int:
206
+ value_string_to_set = str(changes_dict[category][key])
207
+
208
+ # noinspection PyUnboundLocalVariable
209
+ line_to_set = f"{key} = {value_string_to_set}{comment}"
210
+ # Replace the line with the old value.
211
+ main_config_file_text_lines[line_index] = line_to_set
212
+
213
+ main_config_file_dict[category][key] = changes_dict[category][key]
214
+
215
+ if new_config_file_path:
216
+ file_path_to_write = new_config_file_path
217
+ else:
218
+ file_path_to_write = main_config_file_path
219
+
220
+ if not main_config_file_text_lines == main_config_file_text_lines_backup:
221
+ # Write the final config file.
222
+ with open(file_path_to_write, 'w') as file:
223
+ file.writelines(main_config_file_text_lines)
224
+ else:
225
+ print("No changes to the config file.")