atomicshop 3.3.8__py3-none-any.whl → 3.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (120) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  3. atomicshop/a_mains/install_ca_certificate.py +172 -0
  4. atomicshop/a_mains/process_from_port.py +119 -0
  5. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  6. atomicshop/basics/strings.py +1 -1
  7. atomicshop/certificates.py +2 -2
  8. atomicshop/dns.py +26 -28
  9. atomicshop/etws/traces/trace_tcp.py +1 -2
  10. atomicshop/mitm/centered_settings.py +133 -0
  11. atomicshop/mitm/config_static.py +22 -44
  12. atomicshop/mitm/connection_thread_worker.py +383 -165
  13. atomicshop/mitm/engines/__parent/recorder___parent.py +1 -1
  14. atomicshop/mitm/engines/__parent/requester___parent.py +1 -1
  15. atomicshop/mitm/engines/__parent/responder___parent.py +15 -2
  16. atomicshop/mitm/engines/create_module_template.py +1 -2
  17. atomicshop/mitm/import_config.py +91 -89
  18. atomicshop/mitm/initialize_engines.py +1 -2
  19. atomicshop/mitm/message.py +5 -4
  20. atomicshop/mitm/mitm_main.py +238 -122
  21. atomicshop/mitm/recs_files.py +61 -5
  22. atomicshop/mitm/ssh_tester.py +82 -0
  23. atomicshop/mitm/statistic_analyzer.py +33 -12
  24. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +104 -31
  25. atomicshop/networks.py +160 -92
  26. atomicshop/package_mains_processor.py +84 -0
  27. atomicshop/permissions/ubuntu_permissions.py +47 -0
  28. atomicshop/print_api.py +3 -5
  29. atomicshop/process.py +11 -4
  30. atomicshop/python_functions.py +23 -108
  31. atomicshop/speech_recognize.py +8 -0
  32. atomicshop/ssh_remote.py +140 -164
  33. atomicshop/web.py +63 -22
  34. atomicshop/web_apis/google_llm.py +22 -14
  35. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  36. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -1
  37. atomicshop/wrappers/dockerw/dockerw.py +2 -2
  38. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  39. atomicshop/wrappers/elasticsearchw/elastic_infra.py +0 -190
  40. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +5 -5
  41. atomicshop/wrappers/githubw.py +180 -68
  42. atomicshop/wrappers/loggingw/consts.py +1 -1
  43. atomicshop/wrappers/loggingw/handlers.py +1 -1
  44. atomicshop/wrappers/loggingw/loggingw.py +20 -4
  45. atomicshop/wrappers/loggingw/reading.py +18 -0
  46. atomicshop/wrappers/mongodbw/mongo_infra.py +0 -38
  47. atomicshop/wrappers/netshw.py +124 -3
  48. atomicshop/wrappers/playwrightw/scenarios.py +1 -1
  49. atomicshop/wrappers/powershell_networking.py +80 -0
  50. atomicshop/wrappers/psutilw/psutil_networks.py +9 -0
  51. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  52. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  53. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  54. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +12 -27
  55. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +15 -9
  56. atomicshop/wrappers/socketw/certificator.py +19 -9
  57. atomicshop/wrappers/socketw/creator.py +101 -14
  58. atomicshop/wrappers/socketw/dns_server.py +17 -5
  59. atomicshop/wrappers/socketw/exception_wrapper.py +21 -16
  60. atomicshop/wrappers/socketw/process_getter.py +86 -0
  61. atomicshop/wrappers/socketw/receiver.py +29 -9
  62. atomicshop/wrappers/socketw/sender.py +10 -9
  63. atomicshop/wrappers/socketw/sni.py +31 -10
  64. atomicshop/wrappers/socketw/{base.py → socket_base.py} +33 -1
  65. atomicshop/wrappers/socketw/socket_client.py +11 -10
  66. atomicshop/wrappers/socketw/socket_wrapper.py +125 -32
  67. atomicshop/wrappers/socketw/ssl_base.py +6 -2
  68. atomicshop/wrappers/ubuntu_terminal.py +21 -18
  69. atomicshop/wrappers/win_auditw.py +189 -0
  70. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/METADATA +25 -30
  71. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/RECORD +83 -109
  72. atomicshop/_basics_temp.py +0 -101
  73. atomicshop/a_installs/ubuntu/docker_rootless.py +0 -11
  74. atomicshop/a_installs/ubuntu/docker_sudo.py +0 -11
  75. atomicshop/a_installs/ubuntu/elastic_search_and_kibana.py +0 -10
  76. atomicshop/a_installs/ubuntu/mongodb.py +0 -12
  77. atomicshop/a_installs/win/fibratus.py +0 -9
  78. atomicshop/a_installs/win/mongodb.py +0 -9
  79. atomicshop/a_installs/win/wsl_ubuntu_lts.py +0 -10
  80. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  81. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  82. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  83. atomicshop/addons/package_setup/Setup.cmd +0 -7
  84. atomicshop/archiver/__init__.py +0 -0
  85. atomicshop/archiver/_search_in_zip.py +0 -189
  86. atomicshop/archiver/search_in_archive.py +0 -284
  87. atomicshop/archiver/sevenz_app_w.py +0 -86
  88. atomicshop/archiver/sevenzs.py +0 -73
  89. atomicshop/archiver/shutils.py +0 -34
  90. atomicshop/archiver/zips.py +0 -353
  91. atomicshop/file_types.py +0 -24
  92. atomicshop/pbtkmultifile_argparse.py +0 -88
  93. atomicshop/script_as_string_processor.py +0 -42
  94. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  95. atomicshop/ssh_scripts/process_from_port.py +0 -27
  96. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  97. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  98. atomicshop/wrappers/dockerw/install_docker.py +0 -449
  99. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -233
  100. atomicshop/wrappers/ffmpegw.py +0 -125
  101. atomicshop/wrappers/fibratusw/__init__.py +0 -0
  102. atomicshop/wrappers/fibratusw/install.py +0 -80
  103. atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py +0 -100
  104. atomicshop/wrappers/mongodbw/install_mongodb_win.py +0 -244
  105. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  106. atomicshop/wrappers/socketw/get_process.py +0 -123
  107. atomicshop/wrappers/wslw.py +0 -192
  108. atomicshop-3.3.8.dist-info/entry_points.txt +0 -2
  109. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  110. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  111. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  112. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  113. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  114. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  115. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  116. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  117. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  118. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/WHEEL +0 -0
  119. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/licenses/LICENSE.txt +0 -0
  120. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,82 @@
1
+ import socket
2
+
3
+ import paramiko
4
+
5
+ from .. import package_mains_processor, ssh_remote, config_init
6
+ from ..wrappers.socketw import process_getter
7
+ from ..print_api import print_api
8
+
9
+
10
+ PORT_TO_CMD_FILE: str = 'process_from_port'
11
+ TCP_PORTS_FILE: str = 'get_local_tcp_ports'
12
+
13
+
14
+ def test_ssh_main(config: dict) -> int:
15
+ hosts: list = config['main']['hosts_or_ips']
16
+
17
+ for host in hosts:
18
+ print("-----------------------------------")
19
+ print_api(f"Testing cmd for host: {host}", color='blue')
20
+
21
+ if host in config['main']:
22
+ print("Using host-specific credentials")
23
+ username = config[host]['user']
24
+ password = config[host]['pass']
25
+ else:
26
+ print("Didn't find host-specific credential, using defaults")
27
+ username = config['all_hosts']['user']
28
+ password = config['all_hosts']['pass']
29
+
30
+ ssh_client = ssh_remote.SSHRemote(ip_address=host, username=username, password=password)
31
+
32
+ try:
33
+ ssh_client.connect()
34
+ except socket.gaierror as e:
35
+ if e.errno == 11001:
36
+ print_api(f"Couldn't resolve IP to {host}: {str(e)}\n"
37
+ f"Try providing IP address instead of hostname", color='red')
38
+ continue
39
+ else:
40
+ raise e
41
+ except paramiko.ssh_exception.NoValidConnectionsError as e:
42
+ print_api(f"Couldn't connect to {host}: {str(e)}", color='red')
43
+ continue
44
+
45
+ # Read the TCP ports file to string.
46
+ tcp_ports_package_processor: package_mains_processor.PackageMainsProcessor = package_mains_processor.PackageMainsProcessor(
47
+ script_file_stem=TCP_PORTS_FILE)
48
+ tcp_ports_script_string: str = tcp_ports_package_processor.read_script_file_to_string()
49
+
50
+ # Execute the TCP ports script remotely via SSH to get the list of open TCP ports.
51
+ tcp_ports_output, tcp_ports_error = ssh_client.remote_execution_python(script_string=tcp_ports_script_string)
52
+ if tcp_ports_error:
53
+ print_api(f"Error getting TCP ports from host {host}: {tcp_ports_error}", color='red')
54
+ continue
55
+
56
+ tcp_ports_list: list = tcp_ports_output.strip().splitlines()
57
+ if not tcp_ports_list:
58
+ print_api(f"No TCP ports found on host {host}", color='red')
59
+ continue
60
+
61
+ last_port: int = int(tcp_ports_list[-1])
62
+
63
+ port_to_cmd_package_processor: package_mains_processor.PackageMainsProcessor = package_mains_processor.PackageMainsProcessor(
64
+ script_file_stem=PORT_TO_CMD_FILE)
65
+ get_command_instance = process_getter.GetCommandLine(
66
+ client_ip=host,
67
+ client_port=last_port,
68
+ package_processor=port_to_cmd_package_processor,
69
+ ssh_client=ssh_client)
70
+ process_name = get_command_instance.get_process_name()
71
+ print(f"Process for port {last_port} on host {host}: {process_name}")
72
+
73
+ print("Closing SSH connection")
74
+ ssh_client.close()
75
+
76
+ if not process_name:
77
+ print_api(f"Failed to get process name for port {last_port} on host {host}", color='red')
78
+ continue
79
+
80
+ print_api(f"SSH test success!", color='green')
81
+
82
+ return 0
@@ -346,11 +346,13 @@ def analyze(main_file_path: str):
346
346
 
347
347
 
348
348
  def deviation_calculator_by_moving_average(
349
- statistics_file_directory: str,
350
- by_type: Literal['host', 'url'],
351
- moving_average_window_days: int,
352
- top_bottom_deviation_percentage: float,
349
+ statistics_file_directory: str = None,
350
+ statistics_content: dict = None,
351
+ by_type: Literal['host', 'url'] = 'url',
352
+ moving_average_window_days: int = 5,
353
+ top_bottom_deviation_percentage: float = 0.25,
353
354
  get_deviation_for_last_day_only: bool = False,
355
+ get_deviation_for_date: str = None,
354
356
  summary: bool = False,
355
357
  output_file_path: str = None,
356
358
  output_file_type: Literal['json', 'csv'] = 'json',
@@ -360,9 +362,13 @@ def deviation_calculator_by_moving_average(
360
362
  """
361
363
  This function is the main function for the moving average calculator.
362
364
 
363
- :param statistics_file_directory: string, the directory where 'statistics.csv' file resides.
365
+ :param statistics_file_directory: string, can be either providing the directory where 'statistics.csv' file resides
366
+ or None. If None, the 'statistics_content' must be provided.
367
+ The directory where 'statistics.csv' file resides.
364
368
  Also, all the rotated files like: statistics_2021-01-01.csv, statistics_2021-01-02.csv, etc.
365
369
  These will be analyzed in the order of the date in the file name.
370
+ :param statistics_content: dict, if specified, this will be used instead of reading the files from the directory.
371
+ The dict should be a result of the 'atomicshop.mitm.statistic_analyzer_helper.moving_average_helper.get_all_files_content'.
366
372
  :param by_type: string, 'host' or 'url'. The type of the deviation calculation.
367
373
  'host' will calculate the deviation by the host name. Example: maps.google.com, yahoo.com, etc.
368
374
  'url' will calculate the deviation by the URL. Example: maps.google.com/maps, yahoo.com/news, etc.
@@ -381,6 +387,8 @@ def deviation_calculator_by_moving_average(
381
387
  Files 01 to 05 will be used for moving average and the file 06 for deviation.
382
388
  Meaning the average calculated for 2021-01-06 will be compared to the values moving average of 2021-01-01
383
389
  to 2021-01-05.
390
+ :param get_deviation_for_date: string, if specified, only the specified date will be analyzed.
391
+ The date must be in the format of 'YYYY-MM-DD'. Example: '2021-01-06'.
384
392
  :param summary: bool, if True, Only the summary will be generated without all the numbers that were used
385
393
  to calculate the averages and the moving average data.
386
394
  :param output_file_path: string, if None, no file will be written.
@@ -414,27 +422,40 @@ def deviation_calculator_by_moving_average(
414
422
  sys.exit(main())
415
423
  """
416
424
 
425
+ def convert_data_value_to_string(value_key: str, list_index: int) -> None:
426
+ deviation_list[list_index]['data'][value_key] = json.dumps(deviation_list[list_index]['data'][value_key])
427
+
428
+ def convert_value_to_string(value_key: str, list_index: int) -> None:
429
+ if value_key in deviation_list[list_index]:
430
+ deviation_list[list_index][value_key] = json.dumps(deviation_list[list_index][value_key])
431
+
432
+ if not statistics_file_directory and not statistics_content:
433
+ raise ValueError('Either [statistics_file_directory] or [statistics_content] must be provided.')
434
+ if statistics_file_directory and statistics_content:
435
+ raise ValueError('Either [statistics_file_directory] or [statistics_content] must be provided, not both.')
436
+
417
437
  if output_file_type not in ['json', 'csv']:
418
438
  raise ValueError(f'output_file_type must be "json" or "csv", not [{output_file_type}]')
419
439
 
420
440
  if by_type not in ['host', 'url']:
421
441
  raise ValueError(f'by_type must be "host" or "url", not [{by_type}]')
422
442
 
423
- statistics_file_path: str = f'{statistics_file_directory}{os.sep}{STATISTICS_FILE_NAME}'
443
+ if get_deviation_for_last_day_only and get_deviation_for_date:
444
+ raise ValueError('Either [get_deviation_for_last_day_only] or [get_deviation_for_date] can be provided, not both.')
424
445
 
425
- def convert_data_value_to_string(value_key: str, list_index: int) -> None:
426
- deviation_list[list_index]['data'][value_key] = json.dumps(deviation_list[list_index]['data'][value_key])
427
-
428
- def convert_value_to_string(value_key: str, list_index: int) -> None:
429
- if value_key in deviation_list[list_index]:
430
- deviation_list[list_index][value_key] = json.dumps(deviation_list[list_index][value_key])
446
+ if statistics_file_directory:
447
+ statistics_file_path: str | None = f'{statistics_file_directory}{os.sep}{STATISTICS_FILE_NAME}'
448
+ else:
449
+ statistics_file_path: str | None = None
431
450
 
432
451
  deviation_list = moving_average_helper.calculate_moving_average(
433
452
  statistics_file_path,
453
+ statistics_content,
434
454
  by_type,
435
455
  moving_average_window_days,
436
456
  top_bottom_deviation_percentage,
437
457
  get_deviation_for_last_day_only,
458
+ get_deviation_for_date,
438
459
  skip_total_count_less_than
439
460
  )
440
461
 
@@ -1,6 +1,7 @@
1
1
  import statistics
2
2
  from pathlib import Path
3
3
  from typing import Literal
4
+ import datetime
4
5
 
5
6
  from ...print_api import print_api
6
7
  from ...wrappers.loggingw import reading, consts
@@ -9,11 +10,13 @@ from ... import urls, filesystem
9
10
 
10
11
 
11
12
  def calculate_moving_average(
12
- file_path: str,
13
- by_type: Literal['host', 'url'],
14
- moving_average_window_days,
15
- top_bottom_deviation_percentage: float,
13
+ file_path: str = None,
14
+ statistics_content: dict = None,
15
+ by_type: Literal['host', 'url'] = 'url',
16
+ moving_average_window_days: int = 5,
17
+ top_bottom_deviation_percentage: float = 0.25,
16
18
  get_deviation_for_last_day_only: bool = False,
19
+ get_deviation_for_date: str = None,
17
20
  skip_total_count_less_than: int = None,
18
21
  print_kwargs: dict = None
19
22
  ) -> list:
@@ -21,14 +24,75 @@ def calculate_moving_average(
21
24
  This function calculates the moving average of the daily statistics.
22
25
 
23
26
  :param file_path: string, the path to the 'statistics.csv' file.
27
+ :param statistics_content: dict, the statistics content dictionary. If provided, 'file_path' will be ignored.
28
+ The dictionary should be in the format returned by 'get_all_files_content' function.
24
29
  :param by_type: string, the type to calculate the moving average by. Can be 'host' or 'url'.
25
30
  :param moving_average_window_days: integer, the window size for the moving average.
26
31
  :param top_bottom_deviation_percentage: float, the percentage of deviation from the moving average to the top or
27
32
  bottom.
33
+ :param get_deviation_for_last_day_only: bool, check the 'get_all_files_content' function.
34
+ :param get_deviation_for_date: str, check the 'get_all_files_content' function.
35
+ :param skip_total_count_less_than: integer, if the total count is less than this number, skip the deviation.
36
+ :param print_kwargs: dict, the print_api arguments.
37
+ """
38
+
39
+ if not file_path and not statistics_content:
40
+ raise ValueError('Either file_path or statistics_content must be provided.')
41
+ if file_path and statistics_content:
42
+ raise ValueError('Only one of file_path or statistics_content must be provided.')
43
+
44
+ if get_deviation_for_last_day_only and get_deviation_for_date:
45
+ raise ValueError('Only one of get_deviation_for_last_day_only or get_deviation_for_date can be set.')
46
+
47
+ if not statistics_content:
48
+ statistics_content: dict = get_all_files_content(
49
+ file_path=file_path, moving_average_window_days=moving_average_window_days,
50
+ get_deviation_for_last_day_only=get_deviation_for_last_day_only,
51
+ get_deviation_for_date=get_deviation_for_date,
52
+ print_kwargs=print_kwargs)
53
+
54
+ for date_string, day_dict in statistics_content.items():
55
+ day_dict['content_no_useless'] = get_content_without_useless(day_dict['content'])
56
+
57
+ # Get the data dictionary from the statistics content.
58
+ day_dict['statistics_daily'] = compute_statistics_from_content(
59
+ day_dict['content_no_useless'], by_type)
60
+
61
+ moving_average_dict: dict = compute_moving_averages_from_average_statistics(
62
+ statistics_content,
63
+ moving_average_window_days
64
+ )
65
+
66
+ # Add the moving average to the statistics content.
67
+ for day, day_dict in statistics_content.items():
68
+ try:
69
+ day_dict['moving_average'] = moving_average_dict[day]
70
+ except KeyError:
71
+ day_dict['moving_average'] = {}
72
+
73
+ # Find deviation from the moving average to the bottom or top by specified percentage.
74
+ deviation_list: list = find_deviation_from_moving_average(
75
+ statistics_content, top_bottom_deviation_percentage, skip_total_count_less_than)
76
+
77
+ return deviation_list
78
+
79
+
80
+ def get_all_files_content(
81
+ file_path: str,
82
+ moving_average_window_days: int,
83
+ get_deviation_for_last_day_only: bool = False,
84
+ get_deviation_for_date: str = None,
85
+ print_kwargs: dict = None
86
+ ) -> dict:
87
+ """
88
+ Get the dictionary that will contain all the details of the file, like date, header and content, to prepare for the MA analysis.
89
+
90
+ :param file_path: string, the path to the 'statistics.csv' file.
91
+ :param moving_average_window_days: integer, the window size for the moving average.
28
92
  :param get_deviation_for_last_day_only: bool, if True, only the last day will be analyzed.
29
93
  Example: With 'moving_average_window_days=5', the last 6 days will be analyzed.
30
94
  5 days for moving average and the last day for deviation.
31
- File names example:
95
+ File names example the last day is 2021-01-06:
32
96
  statistics_2021-01-01.csv
33
97
  statistics_2021-01-02.csv
34
98
  statistics_2021-01-03.csv
@@ -38,12 +102,24 @@ def calculate_moving_average(
38
102
  Files 01 to 05 will be used for moving average and the file 06 for deviation.
39
103
  Meaning the average calculated for 2021-01-06 will be compared to the values moving average of 2021-01-01
40
104
  to 2021-01-05.
41
- :param skip_total_count_less_than: integer, if the total count is less than this number, skip the deviation.
105
+ :param get_deviation_for_date: str, if set, the last day is considered the date that you set here.
106
+ The format should be the same as in the file names, e.g. 'YYYY-MM-DD'.
42
107
  :param print_kwargs: dict, the print_api arguments.
108
+ :return:
43
109
  """
44
110
 
111
+ if get_deviation_for_last_day_only and get_deviation_for_date:
112
+ raise ValueError('Only one of get_deviation_for_last_day_only or get_deviation_for_date can be set.')
113
+
45
114
  date_format: str = consts.DEFAULT_ROTATING_SUFFIXES_FROM_WHEN['midnight']
46
115
 
116
+ def is_valid_date(date_str: str) -> bool:
117
+ try:
118
+ datetime.datetime.strptime(date_str, date_format)
119
+ return True
120
+ except ValueError:
121
+ return False
122
+
47
123
  # Get all the file paths and their midnight rotations.
48
124
  logs_paths: list[filesystem.AtomicPath] = reading.get_logs_paths(
49
125
  log_file_path=file_path,
@@ -54,6 +130,24 @@ def calculate_moving_average(
54
130
  days_back_to_analyze: int = moving_average_window_days + 1
55
131
  logs_paths = logs_paths[-days_back_to_analyze:]
56
132
 
133
+ if get_deviation_for_date:
134
+ # Check if the date format is correct.
135
+ if not is_valid_date(get_deviation_for_date):
136
+ raise ValueError(f'Date [{get_deviation_for_date}] is not in the correct format: {date_format}')
137
+
138
+ # Find the index of the date in the logs_paths list.
139
+ date_index: int | None = None
140
+ for index, log_atomic_path in enumerate(logs_paths):
141
+ if log_atomic_path.datetime_string == get_deviation_for_date:
142
+ date_index = index
143
+ break
144
+
145
+ if date_index is None:
146
+ raise ValueError(f'Date {get_deviation_for_date} not found in the log files.')
147
+
148
+ start_index: int = max(0, date_index - moving_average_window_days)
149
+ logs_paths = logs_paths[start_index:date_index + 1]
150
+
57
151
  statistics_content: dict = {}
58
152
  # Read each file to its day.
59
153
  for log_atomic_path in logs_paths:
@@ -67,29 +161,7 @@ def calculate_moving_average(
67
161
  statistics_content[date_string]['content'] = log_file_content
68
162
  statistics_content[date_string]['header'] = log_file_header
69
163
 
70
- statistics_content[date_string]['content_no_useless'] = get_content_without_useless(log_file_content)
71
-
72
- # Get the data dictionary from the statistics content.
73
- statistics_content[date_string]['statistics_daily'] = compute_statistics_from_content(
74
- statistics_content[date_string]['content_no_useless'], by_type)
75
-
76
- moving_average_dict: dict = compute_moving_averages_from_average_statistics(
77
- statistics_content,
78
- moving_average_window_days
79
- )
80
-
81
- # Add the moving average to the statistics content.
82
- for day, day_dict in statistics_content.items():
83
- try:
84
- day_dict['moving_average'] = moving_average_dict[day]
85
- except KeyError:
86
- day_dict['moving_average'] = {}
87
-
88
- # Find deviation from the moving average to the bottom or top by specified percentage.
89
- deviation_list: list = find_deviation_from_moving_average(
90
- statistics_content, top_bottom_deviation_percentage, skip_total_count_less_than)
91
-
92
- return deviation_list
164
+ return statistics_content
93
165
 
94
166
 
95
167
  def get_content_without_useless(content: list) -> list:
@@ -227,7 +299,7 @@ def compute_moving_averages_from_average_statistics(
227
299
 
228
300
  # Create list of the last 'moving_average_window_days' days, including the current day.
229
301
  last_x_window_days_content_list = (
230
- list(average_statistics_dict.values()))[current_day-moving_average_window_days:current_day]
302
+ list(average_statistics_dict.values()))[current_day - moving_average_window_days:current_day]
231
303
 
232
304
  # Compute the moving averages.
233
305
  moving_average[day] = compute_average_for_current_day_from_past_x_days(
@@ -410,7 +482,8 @@ def find_deviation_from_moving_average(
410
482
  if day_index == 0:
411
483
  previous_day_moving_average_dict = {}
412
484
  else:
413
- previous_day_moving_average_dict = list(statistics_content.values())[day_index-1].get('moving_average', {})
485
+ previous_day_moving_average_dict = list(statistics_content.values())[day_index - 1].get('moving_average',
486
+ {})
414
487
 
415
488
  # If there is no moving average for previous day continue to the next day.
416
489
  if not previous_day_moving_average_dict: