atomicshop 2.11.47__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.
- atomicshop/__init__.py +1 -1
- atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
- atomicshop/a_mains/addons/process_list/compile.cmd +7 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
- atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +8 -1
- atomicshop/a_mains/dns_gateway_setting.py +11 -0
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/github_wrapper.py +11 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/{addons/mains → a_mains}/msi_unpacker.py +3 -1
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/a_mains/update_config_toml.py +38 -0
- atomicshop/appointment_management.py +5 -3
- atomicshop/basics/ansi_escape_codes.py +3 -1
- atomicshop/basics/argparse_template.py +2 -0
- atomicshop/basics/booleans.py +27 -30
- atomicshop/basics/bytes_arrays.py +43 -0
- atomicshop/basics/classes.py +149 -1
- atomicshop/basics/dicts.py +12 -0
- atomicshop/basics/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/list_of_dicts.py +69 -5
- atomicshop/basics/lists.py +14 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/package_module.py +10 -0
- atomicshop/basics/strings.py +160 -7
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -4
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +12 -7
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +98 -0
- atomicshop/diff_check.py +340 -40
- atomicshop/dns.py +128 -12
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/const.py +38 -0
- atomicshop/etws/providers.py +21 -0
- atomicshop/etws/sessions.py +43 -0
- atomicshop/etws/trace.py +168 -0
- atomicshop/etws/traces/trace_dns.py +162 -0
- atomicshop/etws/traces/trace_sysmon_process_creation.py +126 -0
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +222 -24
- atomicshop/file_io/docxs.py +35 -18
- atomicshop/file_io/file_io.py +35 -19
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +864 -293
- atomicshop/get_process_list.py +133 -0
- atomicshop/{process_name_cmd.py → get_process_name_cmd_dll.py} +52 -19
- atomicshop/http_parse.py +149 -93
- atomicshop/ip_addresses.py +6 -1
- atomicshop/mitm/centered_settings.py +132 -0
- atomicshop/mitm/config_static.py +207 -0
- atomicshop/mitm/config_toml_editor.py +55 -0
- atomicshop/mitm/connection_thread_worker.py +875 -357
- atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
- atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
- atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
- atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
- atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
- atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
- atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
- atomicshop/mitm/engines/create_module_template.py +58 -14
- atomicshop/mitm/import_config.py +359 -139
- atomicshop/mitm/initialize_engines.py +160 -74
- atomicshop/mitm/message.py +64 -23
- atomicshop/mitm/mitm_main.py +892 -0
- atomicshop/mitm/recs_files.py +183 -0
- atomicshop/mitm/shared_functions.py +4 -10
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +257 -166
- atomicshop/mitm/statistic_analyzer_helper/analyzer_helper.py +136 -0
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +525 -0
- atomicshop/monitor/change_monitor.py +96 -120
- atomicshop/monitor/checks/dns.py +139 -70
- atomicshop/monitor/checks/file.py +77 -0
- atomicshop/monitor/checks/network.py +81 -77
- atomicshop/monitor/checks/process_running.py +33 -34
- atomicshop/monitor/checks/url.py +94 -0
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +205 -0
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/permissions.py +22 -0
- atomicshop/permissions/ubuntu_permissions.py +239 -0
- atomicshop/permissions/win_permissions.py +33 -0
- atomicshop/print_api.py +24 -41
- atomicshop/process.py +63 -17
- atomicshop/process_poller/__init__.py +0 -0
- atomicshop/process_poller/pollers/__init__.py +0 -0
- atomicshop/process_poller/pollers/psutil_pywin32wmi_dll.py +95 -0
- atomicshop/process_poller/process_pool.py +207 -0
- atomicshop/process_poller/simple_process_pool.py +311 -0
- atomicshop/process_poller/tracer_base.py +45 -0
- atomicshop/process_poller/tracers/__init__.py +0 -0
- atomicshop/process_poller/tracers/event_log.py +46 -0
- atomicshop/process_poller/tracers/sysmon_etw.py +68 -0
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/question_answer_engine.py +2 -2
- atomicshop/scheduling.py +24 -5
- atomicshop/sound.py +4 -2
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/startup/__init__.py +0 -0
- atomicshop/startup/win/__init__.py +0 -0
- atomicshop/startup/win/startup_folder.py +53 -0
- atomicshop/startup/win/task_scheduler.py +119 -0
- atomicshop/system_resource_monitor.py +61 -46
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/timer.py +30 -11
- atomicshop/urls.py +41 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +110 -25
- atomicshop/web_apis/__init__.py +0 -0
- atomicshop/web_apis/google_custom_search.py +44 -0
- atomicshop/web_apis/google_llm.py +188 -0
- atomicshop/websocket_parse.py +450 -0
- atomicshop/wrappers/certauthw/certauth.py +1 -0
- atomicshop/wrappers/cryptographyw.py +29 -8
- atomicshop/wrappers/ctyping/etw_winapi/__init__.py +0 -0
- atomicshop/wrappers/ctyping/etw_winapi/const.py +335 -0
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +393 -0
- atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +13 -9
- atomicshop/wrappers/ctyping/msi_windows_installer/tables.py +35 -0
- atomicshop/wrappers/ctyping/setup_device.py +466 -0
- atomicshop/wrappers/ctyping/win_console.py +39 -0
- atomicshop/wrappers/dockerw/dockerw.py +113 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
- atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
- atomicshop/wrappers/factw/get_file_data.py +12 -5
- atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
- atomicshop/wrappers/factw/postgresql/firmware.py +4 -6
- atomicshop/wrappers/githubw.py +583 -51
- atomicshop/wrappers/loggingw/consts.py +49 -0
- atomicshop/wrappers/loggingw/filters.py +102 -0
- atomicshop/wrappers/loggingw/formatters.py +58 -71
- atomicshop/wrappers/loggingw/handlers.py +459 -40
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +1010 -178
- atomicshop/wrappers/loggingw/reading.py +344 -19
- atomicshop/wrappers/mongodbw/__init__.py +0 -0
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1432 -0
- atomicshop/wrappers/netshw.py +271 -0
- atomicshop/wrappers/playwrightw/engine.py +34 -19
- atomicshop/wrappers/playwrightw/infra.py +5 -0
- atomicshop/wrappers/playwrightw/javascript.py +7 -3
- atomicshop/wrappers/playwrightw/keyboard.py +14 -0
- atomicshop/wrappers/playwrightw/scenarios.py +172 -5
- atomicshop/wrappers/playwrightw/waits.py +9 -7
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/processes.py +81 -0
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/psutilw/psutilw.py +9 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/console.py +34 -0
- atomicshop/wrappers/pywin32w/win_event_log/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribe.py +212 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +57 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +49 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py +97 -0
- atomicshop/wrappers/pywin32w/winshell.py +19 -0
- atomicshop/wrappers/pywin32w/wmis/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
- atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
- atomicshop/wrappers/socketw/accepter.py +21 -7
- atomicshop/wrappers/socketw/certificator.py +216 -150
- atomicshop/wrappers/socketw/creator.py +190 -50
- atomicshop/wrappers/socketw/dns_server.py +500 -173
- atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +144 -102
- atomicshop/wrappers/socketw/sender.py +65 -35
- atomicshop/wrappers/socketw/sni.py +334 -165
- atomicshop/wrappers/socketw/socket_base.py +134 -0
- atomicshop/wrappers/socketw/socket_client.py +137 -95
- atomicshop/wrappers/socketw/socket_server_tester.py +14 -9
- atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
- atomicshop/wrappers/socketw/ssl_base.py +15 -14
- atomicshop/wrappers/socketw/statistics_csv.py +148 -17
- atomicshop/wrappers/sysmonw.py +157 -0
- atomicshop/wrappers/ubuntu_terminal.py +65 -26
- atomicshop/wrappers/win_auditw.py +189 -0
- atomicshop/wrappers/winregw/__init__.py +0 -0
- atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
- atomicshop/wrappers/winregw/winreg_network.py +232 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -49
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
- atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
- atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
- atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
- atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/addons/process_list/compile.cmd +0 -2
- atomicshop/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
- atomicshop/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
- atomicshop/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/archiver.py +0 -34
- atomicshop/archiver/search_in_archive.py +0 -250
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -44
- atomicshop/archiver/zips.py +0 -293
- atomicshop/etw/dns_trace.py +0 -118
- atomicshop/etw/etw.py +0 -61
- atomicshop/file_types.py +0 -24
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -240
- atomicshop/monitor/checks/hash.py +0 -44
- atomicshop/monitor/checks/hash_checks/file.py +0 -55
- atomicshop/monitor/checks/hash_checks/url.py +0 -62
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -110
- atomicshop/process_poller.py +0 -237
- atomicshop/script_as_string_processor.py +0 -38
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -209
- atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/loggingw/checks.py +0 -20
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.11.47.dist-info/RECORD +0 -251
- /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons/mains → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons/mains → a_mains}/search_for_hyperlinks_in_docx.py +0 -0
- /atomicshop/{archiver → etws}/__init__.py +0 -0
- /atomicshop/{etw → etws/traces}/__init__.py +0 -0
- /atomicshop/{monitor/checks/hash_checks → mitm/statistic_analyzer_helper}/__init__.py +0 -0
- /atomicshop/{wrappers/nodejsw → permissions}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import time
|
|
4
|
+
import multiprocessing.managers
|
|
5
|
+
import queue
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from ..wrappers.pywin32w.win_event_log.subscribes import process_create, process_terminate
|
|
9
|
+
from .. import get_process_list
|
|
10
|
+
from ..print_api import print_api
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
WAIT_BEFORE_PROCESS_TERMINATION_CHECK_SECONDS: float = 3
|
|
14
|
+
WAIT_BEFORE_PROCESS_TERMINATION_CHECK_COUNTS: float = WAIT_BEFORE_PROCESS_TERMINATION_CHECK_SECONDS * 10
|
|
15
|
+
|
|
16
|
+
WAIT_FOR_PROCESS_POLLER_PID_SECONDS: int = 3
|
|
17
|
+
WAIT_FOR_PROCESS_POLLER_PID_COUNTS: int = WAIT_FOR_PROCESS_POLLER_PID_SECONDS * 10
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PidProcessConverterPIDNotFoundError(Exception):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SimpleProcessPool:
|
|
25
|
+
"""
|
|
26
|
+
THE POOL OF PROCESSES IS NOT REAL TIME!!!
|
|
27
|
+
There can be several moments delay (less than a second) + you can add delay before pid removal from the pool.
|
|
28
|
+
This is needed only to correlate PIDs to process names and command lines of other events you get on Windows.
|
|
29
|
+
The idea is similar to the process_poller.process_pool.ProcessPool class, but this class is simpler and uses
|
|
30
|
+
only the pywin32 tracing of the Windows Event Log Process Creation and Process Termination events.
|
|
31
|
+
The simple process pool is used to get things simpler than the process_pool.ProcessPool class.
|
|
32
|
+
|
|
33
|
+
Example of starting the process pool in multiprocess:
|
|
34
|
+
import sys
|
|
35
|
+
|
|
36
|
+
from atomicshop.process_poller import simple_process_pool
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def start_process_pool(process_pool_shared_dict_proxy):
|
|
40
|
+
process_poller = simple_process_pool.SimpleProcessPool(
|
|
41
|
+
process_pool_shared_dict_proxy=process_pool_shared_dict_proxy)
|
|
42
|
+
process_poller.start()
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
# Keep the process alive.
|
|
46
|
+
while True:
|
|
47
|
+
time.sleep(1)
|
|
48
|
+
except KeyboardInterrupt:
|
|
49
|
+
process_poller.stop()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main():
|
|
53
|
+
# Create the shared multiprocessing dictionary of the process pool.
|
|
54
|
+
manager = multiprocessing.Manager()
|
|
55
|
+
multiprocess_dict_proxy = manager.dict()
|
|
56
|
+
|
|
57
|
+
# Start the process pool in a separate process.
|
|
58
|
+
pool_process = multiprocessing.Process(target=start_process_pool, args=(multiprocess_dict_proxy,))
|
|
59
|
+
pool_process.start()
|
|
60
|
+
|
|
61
|
+
# Pass the shared dict proxy to other functions.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == '__main__':
|
|
65
|
+
sys.exit(main())
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
wait_before_pid_remove_seconds: float = 5,
|
|
71
|
+
process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy = None
|
|
72
|
+
):
|
|
73
|
+
"""
|
|
74
|
+
:param wait_before_pid_remove_seconds: float, how many seconds to wait before the process is removed from
|
|
75
|
+
the pool after process termination event is received for that pid.
|
|
76
|
+
:param process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy, shared dict proxy to update
|
|
77
|
+
the process pool.
|
|
78
|
+
If you run a function from other multiprocessing.Process, you can pass the shared_dict_proxy to the function
|
|
79
|
+
and update the process pool from that function.
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
import multiprocessing.managers
|
|
83
|
+
|
|
84
|
+
manager: multiprocessing.managers.SyncManager = multiprocessing.Manager()
|
|
85
|
+
multiprocess_dict_proxy: multiprocessing.managers.DictProxy = manager.dict()
|
|
86
|
+
|
|
87
|
+
process_poller = SimpleProcessPool()
|
|
88
|
+
process_poller.start()
|
|
89
|
+
|
|
90
|
+
while True:
|
|
91
|
+
#============================
|
|
92
|
+
# your function where you get info with pid
|
|
93
|
+
# result = {
|
|
94
|
+
# 'pid': 1234,
|
|
95
|
+
# 'info': 'some info'
|
|
96
|
+
# }
|
|
97
|
+
#============================
|
|
98
|
+
info_with_process_details = {
|
|
99
|
+
'pid': result['pid'],
|
|
100
|
+
'info': result['info']
|
|
101
|
+
'process_details': shared_dict_proxy[result['pid']]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
process_poller.stop()
|
|
107
|
+
manager.shutdown()
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
self.wait_before_pid_remove_seconds: float = wait_before_pid_remove_seconds
|
|
111
|
+
self.process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy = process_pool_shared_dict_proxy
|
|
112
|
+
|
|
113
|
+
self._processes: dict = dict()
|
|
114
|
+
self._running: bool = False
|
|
115
|
+
self.shared_dict_update_queue: queue.Queue = queue.Queue()
|
|
116
|
+
self.empty_cmdline_queue: queue.Queue = queue.Queue()
|
|
117
|
+
|
|
118
|
+
def start(self):
|
|
119
|
+
self._running = True
|
|
120
|
+
|
|
121
|
+
self._processes = get_process_list.GetProcessList(
|
|
122
|
+
get_method='psutil', connect_on_init=True).get_processes(as_dict=True)
|
|
123
|
+
|
|
124
|
+
thread_get_queue = threading.Thread(target=self._start_main_thread, args=(self.shared_dict_update_queue,))
|
|
125
|
+
thread_get_queue.daemon = True
|
|
126
|
+
thread_get_queue.start()
|
|
127
|
+
|
|
128
|
+
thread_process_termination = threading.Thread(target=self._thread_process_termination)
|
|
129
|
+
thread_process_termination.daemon = True
|
|
130
|
+
thread_process_termination.start()
|
|
131
|
+
|
|
132
|
+
thread_get_psutil_commandline = threading.Thread(target=self._thread_get_psutil_commandline)
|
|
133
|
+
thread_get_psutil_commandline.daemon = True
|
|
134
|
+
thread_get_psutil_commandline.start()
|
|
135
|
+
|
|
136
|
+
thread_update_shared_dict = threading.Thread(target=self._update_shared_dict, args=(self.shared_dict_update_queue,))
|
|
137
|
+
thread_update_shared_dict.daemon = True
|
|
138
|
+
thread_update_shared_dict.start()
|
|
139
|
+
|
|
140
|
+
def stop(self):
|
|
141
|
+
self._running = False
|
|
142
|
+
|
|
143
|
+
def get_processes(self):
|
|
144
|
+
return self._processes
|
|
145
|
+
|
|
146
|
+
def _start_main_thread(self, shared_dict_update_queue):
|
|
147
|
+
get_instance = process_create.ProcessCreateSubscriber()
|
|
148
|
+
get_instance.start()
|
|
149
|
+
|
|
150
|
+
while self._running:
|
|
151
|
+
event = get_instance.emit()
|
|
152
|
+
process_id = event['NewProcessIdInt']
|
|
153
|
+
process_name = Path(event['NewProcessName']).name
|
|
154
|
+
command_line = event['CommandLine']
|
|
155
|
+
|
|
156
|
+
# The event log tracing method doesn't always give the command line, unlike the psutil method.
|
|
157
|
+
# So, we'll get the command line from the current psutil snapshot separately.
|
|
158
|
+
if command_line == '':
|
|
159
|
+
self.empty_cmdline_queue.put(process_id)
|
|
160
|
+
|
|
161
|
+
self._processes[process_id] = {
|
|
162
|
+
'name': process_name,
|
|
163
|
+
'cmdline': command_line
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# Update the multiprocessing shared dict proxy.
|
|
167
|
+
shared_dict_update_queue.put(dict(self._processes))
|
|
168
|
+
|
|
169
|
+
def _thread_get_psutil_commandline(self):
|
|
170
|
+
"""
|
|
171
|
+
This function will get an entry from the queue where command line is missing and get the command line
|
|
172
|
+
from the psutil snapshot.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
while self._running:
|
|
176
|
+
empty_cmd_pid = self.empty_cmdline_queue.get()
|
|
177
|
+
current_psutil_process_snapshot: dict = get_process_list.GetProcessList(
|
|
178
|
+
get_method='psutil', connect_on_init=True).get_processes(as_dict=True)
|
|
179
|
+
command_line = current_psutil_process_snapshot[empty_cmd_pid]['cmdline']
|
|
180
|
+
|
|
181
|
+
self._processes[empty_cmd_pid]['cmdline'] = command_line
|
|
182
|
+
|
|
183
|
+
# Update the multiprocessing shared dict proxy.
|
|
184
|
+
self.shared_dict_update_queue.put(dict(self._processes))
|
|
185
|
+
|
|
186
|
+
def _thread_process_termination(self):
|
|
187
|
+
process_terminate_instance = process_terminate.ProcessTerminateSubscriber()
|
|
188
|
+
process_terminate_instance.start()
|
|
189
|
+
|
|
190
|
+
while self._running:
|
|
191
|
+
termination_event = process_terminate_instance.emit()
|
|
192
|
+
process_id = termination_event['ProcessIdInt']
|
|
193
|
+
|
|
194
|
+
removal_thread = threading.Thread(target=self._remove_pid, args=(process_id,))
|
|
195
|
+
removal_thread.daemon = True
|
|
196
|
+
removal_thread.start()
|
|
197
|
+
|
|
198
|
+
def _remove_pid(self, process_id):
|
|
199
|
+
# We need to wait a bit before we remove the process.
|
|
200
|
+
# This is because termination event can come sooner than the creation and the process
|
|
201
|
+
# is not yet in the pool.
|
|
202
|
+
# This happens mostly when the process is terminated immediately after the creation.
|
|
203
|
+
# Example: ping example.c
|
|
204
|
+
# 'example.c' is not a valid address, so the process is terminated immediately after the creation.
|
|
205
|
+
counter = 0
|
|
206
|
+
while counter < WAIT_BEFORE_PROCESS_TERMINATION_CHECK_COUNTS:
|
|
207
|
+
if process_id in self._processes:
|
|
208
|
+
break
|
|
209
|
+
counter += 1
|
|
210
|
+
time.sleep(0.1)
|
|
211
|
+
|
|
212
|
+
if counter == WAIT_BEFORE_PROCESS_TERMINATION_CHECK_COUNTS:
|
|
213
|
+
# print_api(f'Process [{process_id}] not found in the pool.', color='yellow')
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
# time.sleep(1)
|
|
217
|
+
# if process_id not in self._processes:
|
|
218
|
+
# print_api(f'Process [{process_id}] not found in the pool.', color='red')
|
|
219
|
+
# return
|
|
220
|
+
|
|
221
|
+
time.sleep(self.wait_before_pid_remove_seconds)
|
|
222
|
+
_ = self._processes.pop(process_id, None)
|
|
223
|
+
# print_api(f'Process [{process_id}] removed from the pool.', color='yellow')
|
|
224
|
+
|
|
225
|
+
self.shared_dict_update_queue.put(dict(self._processes))
|
|
226
|
+
|
|
227
|
+
def _update_shared_dict(self, shared_dict_update_queue):
|
|
228
|
+
while self._running:
|
|
229
|
+
current_process_pool = shared_dict_update_queue.get()
|
|
230
|
+
if self.process_pool_shared_dict_proxy is not None:
|
|
231
|
+
self.process_pool_shared_dict_proxy.clear()
|
|
232
|
+
self.process_pool_shared_dict_proxy.update(current_process_pool)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class PidProcessConverter:
|
|
236
|
+
"""
|
|
237
|
+
This class is used to get the process details by PID from the process pool shared dict proxy.
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
def __init__(
|
|
241
|
+
self,
|
|
242
|
+
process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy
|
|
243
|
+
):
|
|
244
|
+
"""
|
|
245
|
+
:param process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy, multiprocessing shared dict proxy.
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
self.process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy = process_pool_shared_dict_proxy
|
|
249
|
+
|
|
250
|
+
self.get_process_with_psutil = get_process_list.GetProcessList(get_method='psutil', connect_on_init=True)
|
|
251
|
+
|
|
252
|
+
def get_process_by_pid(self, pid: int):
|
|
253
|
+
"""
|
|
254
|
+
THIS FUNCTION WILL RUN OUTSIDE THE PROCESS POOL PROCESS. Inside the function that needs
|
|
255
|
+
the pid to process conversion.
|
|
256
|
+
Get the process details by PID from the process pool shared dict proxy.
|
|
257
|
+
|
|
258
|
+
:param pid: int, the process ID.
|
|
259
|
+
:return: dict, the process details.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
counter = 0
|
|
263
|
+
process_dict: dict = dict()
|
|
264
|
+
while counter < WAIT_FOR_PROCESS_POLLER_PID_COUNTS:
|
|
265
|
+
# We need it so that the pool will not change in the middle of the process.
|
|
266
|
+
current_pid_pool = convert_proxy_dict_to_dict(self.process_pool_shared_dict_proxy)
|
|
267
|
+
if pid not in current_pid_pool:
|
|
268
|
+
# print(dict(self.process_pool_shared_dict_proxy))
|
|
269
|
+
time.sleep(0.1)
|
|
270
|
+
counter += 1
|
|
271
|
+
else:
|
|
272
|
+
process_dict = current_pid_pool[pid]
|
|
273
|
+
break
|
|
274
|
+
|
|
275
|
+
if not process_dict:
|
|
276
|
+
print_api(f"Error: The PID [{pid}] is not in the pool, trying psutil snapshot.", color='yellow')
|
|
277
|
+
# Last resort, try to get the process name by current process snapshot.
|
|
278
|
+
processes = self.get_process_with_psutil.get_processes(as_dict=True)
|
|
279
|
+
if pid not in processes:
|
|
280
|
+
print_api(f"Error: Couldn't get the process name for PID: {pid}.", color='red')
|
|
281
|
+
process_dict = {
|
|
282
|
+
'name': pid,
|
|
283
|
+
'cmdline': ''
|
|
284
|
+
}
|
|
285
|
+
else:
|
|
286
|
+
process_dict = processes[pid]
|
|
287
|
+
|
|
288
|
+
return process_dict
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def convert_proxy_dict_to_dict(proxy_dict: multiprocessing.managers.DictProxy) -> dict:
|
|
292
|
+
"""
|
|
293
|
+
Convert the multiprocessing shared dict proxy to a normal dict.
|
|
294
|
+
|
|
295
|
+
:param proxy_dict: multiprocessing.managers.DictProxy, the shared dict proxy.
|
|
296
|
+
:return: dict, the normal dict.
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
# Create a snapshot of the keys
|
|
300
|
+
keys = list(proxy_dict.keys())
|
|
301
|
+
current_pid_pool = {}
|
|
302
|
+
|
|
303
|
+
for key in keys:
|
|
304
|
+
try:
|
|
305
|
+
# Attempt to retrieve the value for each key
|
|
306
|
+
current_pid_pool[key] = proxy_dict[key]
|
|
307
|
+
except KeyError:
|
|
308
|
+
# The key was removed concurrently; skip it
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
return current_pid_pool
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
|
|
3
|
+
from .. import get_process_list
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Tracer:
|
|
7
|
+
"""
|
|
8
|
+
Main tracer class for getting the list of processes.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.tracer_instance = None
|
|
13
|
+
self.process_queue = None
|
|
14
|
+
|
|
15
|
+
self._cycle_callable = None
|
|
16
|
+
self._processes = {}
|
|
17
|
+
|
|
18
|
+
def start(self):
|
|
19
|
+
"""
|
|
20
|
+
Start the tracer.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
self._processes = get_process_list.GetProcessList(
|
|
24
|
+
get_method='pywin32', connect_on_init=True).get_processes(as_dict=True)
|
|
25
|
+
|
|
26
|
+
self.process_queue.put(self._processes)
|
|
27
|
+
|
|
28
|
+
self.tracer_instance.start()
|
|
29
|
+
|
|
30
|
+
thread = threading.Thread(target=self.update_queue)
|
|
31
|
+
thread.daemon = True
|
|
32
|
+
thread.start()
|
|
33
|
+
|
|
34
|
+
def update_queue(self):
|
|
35
|
+
"""
|
|
36
|
+
Update the list of processes.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
while True:
|
|
40
|
+
# Get subclass specific get process cycle.
|
|
41
|
+
current_processes = self._cycle_callable()
|
|
42
|
+
|
|
43
|
+
self._processes.update(current_processes)
|
|
44
|
+
|
|
45
|
+
self.process_queue.put(self._processes)
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from ...wrappers.pywin32w.win_event_log.subscribes import process_create, process_terminate
|
|
4
|
+
from .. import tracer_base
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TracerEventlog(tracer_base.Tracer):
|
|
8
|
+
"""
|
|
9
|
+
Tracer subclass for getting the list of processes with Windows Event Log Subscription.
|
|
10
|
+
"""
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
process_queue
|
|
14
|
+
):
|
|
15
|
+
"""
|
|
16
|
+
:param process_queue: Queue. The queue to put the processes in. If None, the processes will not be put in the
|
|
17
|
+
queue.
|
|
18
|
+
"""
|
|
19
|
+
super().__init__()
|
|
20
|
+
|
|
21
|
+
self.tracer_instance = process_create.ProcessCreateSubscriber()
|
|
22
|
+
|
|
23
|
+
self.process_queue = process_queue
|
|
24
|
+
|
|
25
|
+
# This function is used in the thread start in the main Tracer class, 'start' function.
|
|
26
|
+
self._cycle_callable = self.emit_cycle
|
|
27
|
+
|
|
28
|
+
def start(self):
|
|
29
|
+
"""
|
|
30
|
+
Start the tracer.
|
|
31
|
+
"""
|
|
32
|
+
super().start()
|
|
33
|
+
|
|
34
|
+
def emit_cycle(self):
|
|
35
|
+
"""
|
|
36
|
+
Get the list of processes.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
# Get the current processes and reinitialize the instance of the dict.
|
|
40
|
+
current_cycle: dict = self.tracer_instance.emit()
|
|
41
|
+
current_processes: dict = {int(current_cycle['NewProcessIdInt']): {
|
|
42
|
+
'name': Path(current_cycle['NewProcessName']).name,
|
|
43
|
+
'cmdline': current_cycle['CommandLine']}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return current_processes
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from .. import tracer_base
|
|
2
|
+
from ...etws.traces import trace_sysmon_process_creation
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TracerSysmonEtw(tracer_base.Tracer):
|
|
6
|
+
"""
|
|
7
|
+
Tracer subclass for getting the list of processes with SysMon ETW.
|
|
8
|
+
"""
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
attrs: list = None,
|
|
12
|
+
settings: dict = None,
|
|
13
|
+
process_queue=None
|
|
14
|
+
):
|
|
15
|
+
"""
|
|
16
|
+
:param attrs: list. Default is ['pid', 'original_file_name', 'command_line'].
|
|
17
|
+
The list of attributes to get from the sysmon trace output. Check SysmonProcessCreationTrace class for
|
|
18
|
+
available attributes.
|
|
19
|
+
:settings: dict. The settings dictionary:
|
|
20
|
+
'sysmon_etw_session_name': str. The Sysmon ETW session name. If None, the default from
|
|
21
|
+
'trace_sysmon_process_creation' will be used.
|
|
22
|
+
'sysmon_directory': str. The directory where Sysmon.exe resides. If None, the default from
|
|
23
|
+
'trace_sysmon_process_creation' will be used.
|
|
24
|
+
:param process_queue: Queue. The queue to put the processes in. If None, the processes will not be put in the
|
|
25
|
+
queue.
|
|
26
|
+
"""
|
|
27
|
+
super().__init__()
|
|
28
|
+
|
|
29
|
+
if not attrs:
|
|
30
|
+
attrs = ['ProcessId', 'OriginalFileName', 'CommandLine']
|
|
31
|
+
|
|
32
|
+
if not settings:
|
|
33
|
+
settings = {
|
|
34
|
+
'sysmon_etw_session_name': None,
|
|
35
|
+
'sysmon_directory': None
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
self.tracer_instance = trace_sysmon_process_creation.SysmonProcessCreationTrace(
|
|
39
|
+
attrs=attrs,
|
|
40
|
+
session_name=settings.get('sysmon_etw_session_name'),
|
|
41
|
+
close_existing_session_name=True,
|
|
42
|
+
sysmon_directory=settings.get('sysmon_directory')
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
self.process_queue = process_queue
|
|
46
|
+
|
|
47
|
+
# This function is used in the thread start in the main Tracer class, 'start' function.
|
|
48
|
+
self._cycle_callable = self.emit_cycle
|
|
49
|
+
|
|
50
|
+
def start(self):
|
|
51
|
+
"""
|
|
52
|
+
Start the tracer.
|
|
53
|
+
"""
|
|
54
|
+
super().start()
|
|
55
|
+
|
|
56
|
+
def emit_cycle(self):
|
|
57
|
+
"""
|
|
58
|
+
Get the list of processes.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# Get the current processes and reinitialize the instance of the dict.
|
|
62
|
+
current_cycle: dict = self.tracer_instance.emit()
|
|
63
|
+
current_processes: dict = {int(current_cycle['ProcessId']): {
|
|
64
|
+
'name': current_cycle['OriginalFileName'],
|
|
65
|
+
'cmdline': current_cycle['CommandLine']}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return current_processes
|
|
@@ -113,4 +113,4 @@ def find_and_replace_in_file(
|
|
|
113
113
|
for index in found_string_indexes:
|
|
114
114
|
file_data[index] = file_data[index].replace(single_find.find_what, single_find.replace_to)
|
|
115
115
|
|
|
116
|
-
file_io.write_file(content=file_data, file_path=file_path, encoding=encoding
|
|
116
|
+
file_io.write_file(content=file_data, file_path=file_path, encoding=encoding)
|
atomicshop/python_functions.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
from typing import Union
|
|
2
3
|
|
|
3
4
|
from .print_api import print_api
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
def
|
|
7
|
+
def get_python_version_string() -> str:
|
|
7
8
|
"""
|
|
8
9
|
Function gets version MAJOR.MINOR.MICRO from 'sys.version_info' object and returns it as a string.
|
|
9
10
|
:return: python MAJOR.MINOR.MICRO version string.
|
|
@@ -12,84 +13,35 @@ def get_current_python_version_string() -> str:
|
|
|
12
13
|
return f'{sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]}'
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Function checks if 'version_object' that was passed is tuple or string.
|
|
20
|
-
If it's tuple then returns it back. If it's string, converts to tuple then returns it.
|
|
21
|
-
If the object is none of the above, returns None.
|
|
22
|
-
|
|
23
|
-
:param version_object: Can be string ('3.10') or tuple of integers ((3, 10)).
|
|
24
|
-
:return:
|
|
25
|
-
"""
|
|
26
|
-
# Check if tuple was passed.
|
|
27
|
-
if isinstance(version_object, tuple):
|
|
28
|
-
return version_object
|
|
29
|
-
else:
|
|
30
|
-
# Then check if a string was passed.
|
|
31
|
-
if isinstance(version_object, str):
|
|
32
|
-
# The check will be against tuple of integers, so we'll convert a string to tuple of integers.
|
|
33
|
-
return tuple(map(int, version_object.split('.')))
|
|
34
|
-
else:
|
|
35
|
-
message = f'[*] Function: [check_if_version_object_is_tuple_or_string]\n' \
|
|
36
|
-
f'[*] [version_object] object passed is not tuple or string.\n' \
|
|
37
|
-
f'[*] Object type: {type(version_object)}\n' \
|
|
38
|
-
f'[*] Object content {version_object}\n' \
|
|
39
|
-
f'Exiting...'
|
|
40
|
-
print_api(message, error_type=True, logger_method='critical', **kwargs)
|
|
41
|
-
|
|
42
|
-
return None
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
# noinspection PyUnusedLocal
|
|
46
|
-
def check_python_version_compliance(minimum_version: any,
|
|
47
|
-
maximum_version: any = None,
|
|
48
|
-
**kwargs) -> bool:
|
|
16
|
+
def check_python_version_compliance(
|
|
17
|
+
min_ver: tuple = None,
|
|
18
|
+
max_ver: tuple = None,
|
|
19
|
+
) -> str | None:
|
|
49
20
|
"""
|
|
50
21
|
Python version check. Should be executed before importing external libraries, since they depend on Python version.
|
|
51
22
|
|
|
52
|
-
:param
|
|
53
|
-
:param
|
|
23
|
+
:param min_ver: tuple of integers (3, 10).
|
|
24
|
+
:param max_ver: tuple of integers (3, 10).
|
|
54
25
|
If maximum version is not specified, it will be considered as all versions above the minimum are compliant.
|
|
55
|
-
:return:
|
|
26
|
+
:return: If version is not compliant, returns string with error message. Otherwise, returns None.
|
|
56
27
|
"""
|
|
57
28
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
f"{'.'.join(str(i) for i in minimum_version_scheme)}, " \
|
|
76
|
-
f"AND EARLIER THAN {'.'.join(str(i) for i in maximum_version_scheme)}, " \
|
|
77
|
-
f"to work properly. Unhandled exceptions are inevitable!"
|
|
78
|
-
print_api(message, error_type=True, logger_method='critical', **kwargs)
|
|
79
|
-
|
|
80
|
-
return False
|
|
81
|
-
# If 'maximum_version' wasn't passed.
|
|
29
|
+
if not min_ver and not max_ver:
|
|
30
|
+
raise ValueError("At least one of the version parameters should be passed.")
|
|
31
|
+
|
|
32
|
+
current_version_info: tuple = sys.version_info[:3]
|
|
33
|
+
if min_ver and not max_ver:
|
|
34
|
+
if current_version_info < min_ver:
|
|
35
|
+
return f'Python version {".".join(map(str, min_ver))} or higher is required. '\
|
|
36
|
+
f'Current version is {".".join(map(str, current_version_info))}.'
|
|
37
|
+
elif max_ver and not min_ver:
|
|
38
|
+
if current_version_info > max_ver:
|
|
39
|
+
return f'Python version up to {".".join(map(str, max_ver))} is required. '\
|
|
40
|
+
f'Current version is {".".join(map(str, current_version_info))}.'
|
|
41
|
+
elif min_ver and max_ver:
|
|
42
|
+
if not (min_ver <= current_version_info <= max_ver):
|
|
43
|
+
return f'Python version between {".".join(map(str, min_ver))} and '\
|
|
44
|
+
f'{".".join(map(str, max_ver))} is required. '\
|
|
45
|
+
f'Current version is {".".join(map(str, current_version_info))}.'
|
|
82
46
|
else:
|
|
83
|
-
|
|
84
|
-
if not sys.version_info >= minimum_version_scheme:
|
|
85
|
-
message = f"[!!!] YOU NEED TO INSTALL AT LEAST PYTHON " \
|
|
86
|
-
f"{'.'.join(str(i) for i in minimum_version_scheme)}, " \
|
|
87
|
-
f"to work properly. Unhandled exceptions are inevitable!"
|
|
88
|
-
print_api(message, error_type=True, logger_method='critical', **kwargs)
|
|
89
|
-
|
|
90
|
-
return False
|
|
91
|
-
|
|
92
|
-
message = "[*] Version Check PASSED."
|
|
93
|
-
print_api(message, logger_method='info', **kwargs)
|
|
94
|
-
|
|
95
|
-
return True
|
|
47
|
+
return None
|
|
@@ -4,7 +4,7 @@ import sys
|
|
|
4
4
|
import json
|
|
5
5
|
|
|
6
6
|
# Custom class imports.
|
|
7
|
-
from atomicshop
|
|
7
|
+
from atomicshop import filesystem
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class QAEngine:
|
|
@@ -20,7 +20,7 @@ class QAEngine:
|
|
|
20
20
|
# Get 'qa.json' full path.
|
|
21
21
|
qa_fullpath: str = script_directory + os.sep + self.qa_filename
|
|
22
22
|
# Check if it exists.
|
|
23
|
-
if not
|
|
23
|
+
if not filesystem.is_file_exists(qa_fullpath):
|
|
24
24
|
print(f'File non-existent: {qa_fullpath}')
|
|
25
25
|
sys.exit()
|
|
26
26
|
|