atomicshop 2.2.9__py3-none-any.whl → 2.3.1__py3-none-any.whl

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

Potentially problematic release.


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

atomicshop/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """Atomic Basic functions and classes to make developer life easier"""
2
2
 
3
3
  __author__ = "Den Kras"
4
- __version__ = '2.2.9'
4
+ __version__ = '2.3.1'
@@ -1,4 +1,3 @@
1
- # v1.0.0
2
1
  def get_single_byte_from_byte_string(byte_string, index: int):
3
2
  """
4
3
  Function extracts single byte as byte from byte string object.
@@ -18,3 +17,37 @@ def get_single_byte_from_byte_string(byte_string, index: int):
18
17
  """
19
18
 
20
19
  return byte_string[index:index+1]
20
+
21
+
22
+ def convert_sequence_of_bytes_to_sequence_of_strings(byte_sequence: bytes) -> list[str]:
23
+ """
24
+ Convert sequence of bytes to sequence of strings.
25
+ :param byte_sequence: bytes, sequence of bytes.
26
+ :return: list of strings.
27
+ """
28
+
29
+ result: list[str] = list()
30
+ for byte_index, single_byte in enumerate(byte_sequence):
31
+ # Convert byte to string character.
32
+ string_from_byte = chr(single_byte)
33
+
34
+ # Check if string is alphanumeric.
35
+ if string_from_byte.isalnum():
36
+ # Append string to result.
37
+ result.append(string_from_byte)
38
+ # If string is not alphanumeric, it means that it is something like '\x04'.
39
+ # So, we need to output '04'. We need to remove the '\x' from the string.
40
+ # The problem is that the string doesn't contain 4 characters, but 1.
41
+ # len(string_from_byte) returns 1.
42
+ # So, we can't remove '\x' from the string, since it is the character '\x04' in the table.
43
+ # We will convert it to printable string with repr() function.
44
+ # repr() function returns string with quotes, so we need to remove them with [1:-1].
45
+ else:
46
+ printable_string = repr(string_from_byte)[1:-1]
47
+ # Remove '\x' from the string.
48
+ printable_string = printable_string.replace('\\x', '')
49
+ result.append(printable_string)
50
+
51
+ # single_byte_string = byte_sequence[byte_index:byte_index+1]
52
+
53
+ return result
@@ -118,3 +118,38 @@ def merge(dict1, dict2) -> dict:
118
118
  def find_key_by_value(input_dict, value):
119
119
  # This will return set of keys. If non found, will return empty set.
120
120
  return {k for k, v in input_dict.items() if v == value}
121
+
122
+
123
+ def sort_by_values(input_dict: dict, reverse: bool = False) -> dict:
124
+ """
125
+ The function will sort a dictionary by its values.
126
+ Example:
127
+ # Define dictionary.
128
+ test = {
129
+ 'key1': '1',
130
+ 'key2': '2',
131
+ 'key3': '8',
132
+ 'key4': '37',
133
+ 'key5': '5',
134
+ 'key6': '23'
135
+ }
136
+
137
+ # Sort dictionary.
138
+ sorted_dict = sort_by_values(test, reverse=True)
139
+
140
+ # Result:
141
+ # {
142
+ # 'key4': '37',
143
+ # 'key6': '23',
144
+ # 'key3': '8',
145
+ # 'key5': '5',
146
+ # 'key2': '2',
147
+ # 'key1': '1'
148
+ # }
149
+
150
+ :param input_dict: dict, the dictionary to sort.
151
+ :param reverse: bool, if True, the dictionary will be sorted in reverse order.
152
+ :return: dict, the sorted dictionary.
153
+ """
154
+
155
+ return dict(sorted(input_dict.items(), key=lambda item: item[1], reverse=reverse))
@@ -70,3 +70,33 @@ def find_key_by_value(input_dict: dict, string1: str, string2: str = str(), key_
70
70
  final_key_list.append(temp_list)
71
71
 
72
72
  return final_key_list
73
+
74
+
75
+ def find_key_of_last_nest(input_dict: dict) -> list:
76
+ """
77
+ Returns list of keys of the last nest in the dictionary.
78
+ :param input_dict: dict, the dictionary to search.
79
+ :return: list of keys in order.
80
+ """
81
+
82
+ for key, value in input_dict.items():
83
+ if isinstance(value, dict):
84
+ return [key] + find_key_of_last_nest(value)
85
+ elif isinstance(value, list):
86
+ return [key] + find_key_of_last_nest(value[0])
87
+ else:
88
+ return [key]
89
+
90
+
91
+ def find_key_of_last_dict(input_dict: dict) -> list:
92
+ """
93
+ Returns list of keys of the last dictionary in the dict.
94
+ :param input_dict: dict, the dictionary to search.
95
+ :return: list of keys in order.
96
+ """
97
+
98
+ for key, value in input_dict.items():
99
+ if isinstance(value, dict):
100
+ return [key] + find_key_of_last_dict(value)
101
+ else:
102
+ return []
@@ -0,0 +1,70 @@
1
+ from enum import Enum
2
+ import os
3
+
4
+
5
+ class ComparisonOperator(Enum):
6
+ """
7
+ Enum class that was created for 'scan_directory_and_return_list' function.
8
+ Specifically for 'file_name_check_tuple[1]' - second entry.
9
+ """
10
+ EQ = '__eq__'
11
+ CONTAINS = '__contains__'
12
+
13
+
14
+ def __comparison_usage_example(
15
+ directory_fullpath: str,
16
+ file_name_check_tuple: tuple = tuple()):
17
+ """
18
+ The function receives a filesystem directory as string, scans it recursively for files and returns list of
19
+ full paths to that file (including).
20
+ If 'file_name_check_tuple' specified, the function will return only list of files that answer to the input
21
+ of that tuple.
22
+ Recursive.
23
+
24
+ :param directory_fullpath: string to full path to directory on the filesystem to scan.
25
+ :param file_name_check_tuple: tuple that should contain 2 entries:
26
+ file_name_check_tuple[0]: string that will contain part of file name to check or full file name with extension.
27
+ file_name_check_tuple[1]: 'ComparisonOperator' object to test against found file name with extension.
28
+ 'ComparisonOperator.EQ' will check for specific file name (eg: 'config_file.ini').
29
+ 'ComparisonOperator.CONTAINS' will check if file name contains part of the string (eg: 'config')(eg: '.ini')
30
+
31
+ :return: list of all found filenames with full file paths, list with relative folders to file excluding the
32
+ main folder.
33
+
34
+ Usage:
35
+ from atomicshop.filesystem import get_file_paths_and_relative_directories, ComparisonOperator
36
+
37
+ # Get full paths of all the 'engine_config.ini' files.
38
+ engine_config_path_list = get_file_paths_and_relative_directories(
39
+ directory_fullpath=some_directory_path,
40
+ file_name_check_tuple=(config_file_name, ComparisonOperator.EQ))
41
+ """
42
+
43
+ # Type checking.
44
+ if file_name_check_tuple:
45
+ if not isinstance(file_name_check_tuple[1], ComparisonOperator):
46
+ raise TypeError(f'Second entry of tuple "file_name_check_tuple" is not of "ComparisonOperator" type.')
47
+
48
+ # "Walk" over all the directories and subdirectories - make list of full file paths inside the directory
49
+ # recursively.
50
+ for dirpath, subdirs, files in os.walk(directory_fullpath):
51
+ # Iterate through all the file names that were found in the folder.
52
+ for file in files:
53
+ # If 'file_name_check_tuple' was passed.
54
+ if file_name_check_tuple:
55
+ # Get separate variables from the tuple.
56
+ # 'check_string' is a string that will be checked against 'file' iteration, which also a string.
57
+ # 'comparison_operator' is 'ComparisonOperator' Enum object, that contains the string method
58
+ # operator that will be used against the 'check_string'.
59
+ check_string, comparison_operator = file_name_check_tuple
60
+ # 'getattr' adds the string comparison method to the 'file' string. Example:
61
+ # file.__eq__
62
+ # 'comparison_operator' is the Enum class representation and '.value' method is the string
63
+ # representation of '__eq__'.
64
+ # and after that comes the check string to check against:
65
+ # file.__eq__(check_string)
66
+ if getattr(file, comparison_operator.value)(check_string):
67
+ return
68
+ # If 'file_name_check_tuple' wasn't passed, then get all the files.
69
+ else:
70
+ return
@@ -159,11 +159,16 @@ def match_pattern_against_string(pattern: str, check_string: str, prefix_suffix:
159
159
  # Replace the wildcard string '*' with regex wildcard string '.+'.
160
160
  # In regex '.' is a wildcard, but only for 1 character, if you need more than 1 character you should add '+'.
161
161
  pattern_re = pattern.replace(wildcard_str, wildcard_re)
162
+ pattern_no_wildcard = pattern.replace(wildcard_str, '')
162
163
 
163
164
  # Search for pattern, if found, return 'True'.
164
165
  if search_pattern(pattern_re):
165
166
  return True
166
167
 
168
+ # 'regex' doesn't think that '.*' is a match for 'test', so we'll check for this case separately.
169
+ if search_pattern(pattern_no_wildcard):
170
+ return True
171
+
167
172
  # If it wasn't found in previous check,
168
173
  # then we'll continue checking without prefix and suffix if 'prefix_suffix' is 'True'.
169
174
  if prefix_suffix:
@@ -268,6 +273,14 @@ def is_numeric_only(string: str) -> bool:
268
273
  return string.isdigit()
269
274
 
270
275
 
276
+ def is_alphabetic_only(string: str) -> bool:
277
+ """
278
+ Function to check if string contains only alphabetic characters.
279
+ """
280
+
281
+ return string.isalpha()
282
+
283
+
271
284
  def replace_words_with_values_from_dict(
272
285
  sentence: str, dictionary: dict, contains: bool = False, case_insensitive: bool = False) -> str:
273
286
  """
@@ -305,3 +318,40 @@ def replace_words_with_values_from_dict(
305
318
  joined_sentence: str = ' '.join(sentence_parts)
306
319
 
307
320
  return joined_sentence
321
+
322
+
323
+ def multiple_splits_by_delimiters(full_string: str, delimiters: list) -> list[str]:
324
+ """
325
+ Function splits the string by multiple delimiters.
326
+ :param full_string: string, to split.
327
+ :param delimiters: list, of delimiters.
328
+ :return: list of strings.
329
+ """
330
+
331
+ # Base case: no more delimiters, return the string itself in a list.
332
+ if not delimiters:
333
+ return [full_string]
334
+
335
+ # Take the first delimiter.
336
+ delimiter = delimiters[0]
337
+ # Split the string on the current delimiter.
338
+ split_strings = full_string.split(delimiter)
339
+ # Get the remaining delimiters.
340
+ remaining_delimiters = delimiters[1:]
341
+
342
+ result = []
343
+ for substring in split_strings:
344
+ # Recursively call the whole function with each substring and the remaining delimiters.
345
+ result.extend(multiple_splits_by_delimiters(substring, remaining_delimiters))
346
+ return result
347
+
348
+
349
+ def check_if_suffix_is_in_string(string: str, suffix: str) -> bool:
350
+ """
351
+ Function checks if 'suffix' is in the end of 'string'.
352
+ :param string: string, to check.
353
+ :param suffix: string, to check.
354
+ :return: boolean.
355
+ """
356
+
357
+ return string.endswith(suffix)
atomicshop/datetimes.py CHANGED
@@ -183,6 +183,42 @@ def create_date_range_for_year_month(
183
183
  return create_date_range(first_date, last_date)
184
184
 
185
185
 
186
+ def get_difference_between_dates_in_days(first_datetime, reference_datetime) -> int:
187
+ """
188
+ Get the difference between two dates in days.
189
+ The first day is '0'.
190
+
191
+ :param first_datetime: datetime.datetime object.
192
+ :param reference_datetime: datetime.datetime object.
193
+
194
+ :return: integer, the difference between the two dates in days.
195
+ """
196
+
197
+ # Get the difference between the two dates.
198
+ difference = reference_datetime - first_datetime
199
+
200
+ # Get the difference in days.
201
+ return difference.days
202
+
203
+
204
+ def get_difference_between_dates_in_hours(first_datetime, reference_datetime) -> int:
205
+ """
206
+ Get the difference between two dates in hours.
207
+ The first hour is '0'.
208
+
209
+ :param first_datetime: datetime.datetime object.
210
+ :param reference_datetime: datetime.datetime object.
211
+
212
+ :return: integer, the difference between the two dates in hours.
213
+ """
214
+
215
+ # Get the difference between the two dates.
216
+ difference = first_datetime - reference_datetime
217
+
218
+ # Get the difference in hours.
219
+ return difference.days * 24 + difference.seconds // (60 * 60)
220
+
221
+
186
222
  def get_seconds_random(minimum_seconds, maximum_seconds):
187
223
  return random.randint(minimum_seconds, maximum_seconds)
188
224
 
atomicshop/domains.py CHANGED
@@ -56,11 +56,16 @@ def get_registered_domain(domain: str) -> str:
56
56
  :return: string of main registered domain with tld only.
57
57
  """
58
58
  # Extract all the parts from domain with 'tldextract'.
59
- extracted_domain_parts = tldextract.TLDExtract(suffix_list_urls=str())(domain)
59
+ # By default, TLD Extract will use the online database, to use the offline database empty Sequence[str] must be
60
+ # passed to 'suffix_list_urls' option.
61
+ # Empty sequence in TLDExtract itself is defined as '()'.
62
+ # extracted_domain_parts = tldextract.TLDExtract(suffix_list_urls=str())(domain)
63
+ extracted_domain_parts = tldextract.TLDExtract(suffix_list_urls=())(domain)
60
64
 
61
65
  # If 'suffix' is empty, it means that the tld is not in 'tldextract' offline database or there is no tld at all,
62
66
  # for example: some-domain-without-tld
63
67
  if not extracted_domain_parts.suffix:
64
68
  return domain
65
69
  else:
66
- return f'{extracted_domain_parts.registered_domain}.{extracted_domain_parts.suffix}'
70
+ # return f'{extracted_domain_parts.registered_domain}.{extracted_domain_parts.suffix}'
71
+ return extracted_domain_parts.registered_domain
@@ -1,4 +1,3 @@
1
- # v1.0.4 - 21.03.2023 18:10
2
1
  import json
3
2
 
4
3
  from .file_io import read_file_decorator, write_file_decorator
@@ -0,0 +1,24 @@
1
+ import tomllib
2
+
3
+ from .file_io import read_file_decorator
4
+
5
+
6
+ @read_file_decorator
7
+ def read_toml_file(file_path: str,
8
+ file_mode: str = 'rb',
9
+ encoding=None,
10
+ file_object=None,
11
+ **kwargs) -> dict:
12
+ """
13
+ Read the toml file and return its content as dictionary.
14
+
15
+ :param file_path: String with full file path to file.
16
+ :param file_mode: string, file reading mode. Examples: 'r', 'rb'. Default is 'r'.
17
+ :param encoding: string, encoding of the file. Default is 'None'.
18
+ :param file_object: file object of the 'open()' function in the decorator. Decorator executes the 'with open()'
19
+ statement and passes to this function. That's why the default is 'None', since we get it from the decorator.
20
+ :return: dict.
21
+ """
22
+
23
+ # Read the file to variable
24
+ return tomllib.load(file_object)
@@ -0,0 +1,58 @@
1
+ import pandas
2
+
3
+ from .file_io import write_file_decorator
4
+
5
+
6
+ @write_file_decorator
7
+ def write_xlsx(
8
+ spread_sheets: dict,
9
+ file_path: str,
10
+ file_mode: str = 'w',
11
+ index: bool = False,
12
+ file_object=None,
13
+ **kwargs
14
+ ) -> None:
15
+ """
16
+ Write data to xlsx file. Including several spreadsheets if specified.
17
+
18
+ :param file_path: string, full path to the file to write.
19
+ :param spread_sheets: dict. Each dict is a spreadsheet. The keys are the names of the sheets and the
20
+ values are the dicts to write to the sheets.
21
+
22
+ Example:
23
+ spread_sheets = {
24
+ 'sheet1': {
25
+ 'col1': [1, 2, 3],
26
+ 'col2': [4, 5, 6]
27
+ },
28
+ 'sheet2': {
29
+ 'col1': [7, 8, 9],
30
+ 'col2': [10, 11, 12]
31
+ }
32
+ }
33
+ :param file_mode: string, file writing mode. Examples: 'x', 'w', 'wb'.
34
+ :param index: boolean, if True, the index of the data frame will be written to the file on the left-most column.
35
+ The index meaning that each row will have a number, starting from 0.
36
+ :param file_object: file object of the 'open()' function in the decorator. Decorator executes the 'with open()'
37
+ statement and passes to this function. That's why the default is 'None', since we get it from the decorator.
38
+ :return:
39
+ """
40
+
41
+ # Create dict of data frames.
42
+ spread_sheets = {sheet_name: pandas.DataFrame(sheet_data) for sheet_name, sheet_data in spread_sheets.items()}
43
+
44
+ # Save the file.
45
+ with pandas.ExcelWriter(file_path, engine='openpyxl') as writer:
46
+ for sheet_name, sheet_data in spread_sheets.items():
47
+ sheet_data.to_excel(writer, sheet_name=sheet_name, index=index)
48
+
49
+ # # Create the writer object.
50
+ # writer = pandas.ExcelWriter(file_path, engine='xlsxwriter')
51
+ #
52
+ # # Iterate through the spreadsheets.
53
+ # for sheet_name, sheet_data in spread_sheets.items():
54
+ # # Write the sheet.
55
+ # sheet_data.to_excel(writer, sheet_name=sheet_name)
56
+ #
57
+ # # Save the file.
58
+ # writer.save()
atomicshop/filesystem.py CHANGED
@@ -3,10 +3,9 @@ import pathlib
3
3
  from pathlib import Path, PurePath, PureWindowsPath
4
4
  import glob
5
5
  import shutil
6
- from enum import Enum
7
6
 
8
7
  from .print_api import print_api
9
- from .basics import strings
8
+ from .basics import strings, list_of_dicts
10
9
 
11
10
 
12
11
  def get_working_directory() -> str:
@@ -258,33 +257,35 @@ def get_file_names_from_directory(directory_path: str) -> list:
258
257
  return file_list
259
258
 
260
259
 
261
- class ComparisonOperator(Enum):
262
- """
263
- Enum class that was created for 'scan_directory_and_return_list' function.
264
- Specifically for 'file_name_check_tuple[1]' - second entry.
265
- """
266
- EQ = '__eq__'
267
- CONTAINS = '__contains__'
268
-
269
-
270
- def get_file_paths_and_relative_directories(directory_fullpath: str,
271
- file_name_check_tuple: tuple = tuple(),
272
- relative_file_name_as_directory: bool = False):
260
+ def get_file_paths_and_relative_directories(
261
+ directory_fullpath: str,
262
+ recursive: bool = True,
263
+ file_name_check_pattern: str = '*',
264
+ relative_file_name_as_directory: bool = False,
265
+ add_last_modified_time: bool = False,
266
+ sort_by_last_modified_time: bool = False
267
+ ):
273
268
  """
269
+ Recursive, by option.
274
270
  The function receives a filesystem directory as string, scans it recursively for files and returns list of
275
271
  full paths to that file (including).
276
272
  If 'file_name_check_tuple' specified, the function will return only list of files that answer to the input
277
273
  of that tuple.
278
- Recursive.
279
274
 
280
275
  :param directory_fullpath: string to full path to directory on the filesystem to scan.
281
- :param file_name_check_tuple: tuple that should contain 2 entries:
282
- file_name_check_tuple[0]: string that will contain part of file name to check or full file name with extension.
283
- file_name_check_tuple[1]: 'ComparisonOperator' object to test against found file name with extension.
284
- 'ComparisonOperator.EQ' will check for specific file name (eg: 'config_file.ini').
285
- 'ComparisonOperator.CONTAINS' will check if file name contains part of the string (eg: 'config')(eg: '.ini')
276
+ :param recursive: boolean.
277
+ 'True', then the function will scan recursively in subdirectories.
278
+ 'False', then the function will scan only in the directory that was passed.
279
+ :param file_name_check_pattern: string, if specified, the function will return only files that match the pattern.
280
+ The string can contain part of file name to check or full file name with extension.
281
+ Can contain wildcards.
286
282
  :param relative_file_name_as_directory: boolean that will set if 'relative_directory_list' should contain
287
283
  file name with extension for each entry.
284
+ :param add_last_modified_time: boolean, if 'True', then the function will add last modified time of the file
285
+ to the output list.
286
+ :param sort_by_last_modified_time: boolean, if 'True', then the function will sort the output list by last
287
+ modified time of the file.
288
+
288
289
  :return: list of all found filenames with full file paths, list with relative folders to file excluding the
289
290
  main folder.
290
291
  """
@@ -294,59 +295,57 @@ def get_file_paths_and_relative_directories(directory_fullpath: str,
294
295
  Function gets the full file path, adds it to the found 'object_list' and gets the relative path to that
295
296
  file, against the main path to directory that was passed to the parent function.
296
297
  """
298
+
299
+ file_result: dict = dict()
300
+
297
301
  # Get full file path of the file.
298
- file_path = os.path.join(dirpath, file)
299
- object_list.append(file_path)
302
+ file_result['path'] = os.path.join(dirpath, file)
300
303
 
301
304
  # if 'relative_file_name_as_directory' was passed.
302
305
  if relative_file_name_as_directory:
303
306
  # Output the path with filename.
304
- relative_directory = _get_relative_output_path_from_input_path(directory_fullpath, dirpath, file)
307
+ file_result['relative_dir'] = _get_relative_output_path_from_input_path(directory_fullpath, dirpath, file)
305
308
  # if 'relative_file_name_as_directory' wasn't passed.
306
309
  else:
307
310
  # Output the path without filename.
308
- relative_directory = _get_relative_output_path_from_input_path(directory_fullpath, dirpath)
311
+ file_result['relative_dir'] = _get_relative_output_path_from_input_path(directory_fullpath, dirpath)
309
312
 
310
313
  # Remove separator from the beginning if exists.
311
- relative_directory = relative_directory.removeprefix(os.sep)
314
+ file_result['relative_dir'] = file_result['relative_dir'].removeprefix(os.sep)
315
+
316
+ # If 'add_last_modified_time' was passed.
317
+ if add_last_modified_time:
318
+ # Get last modified time of the file.
319
+ file_result['last_modified'] = get_file_modified_time(file_result['path'])
312
320
 
313
- relative_paths_list.append(relative_directory)
321
+ object_list.append(file_result)
314
322
 
315
- # Type checking.
316
- if file_name_check_tuple:
317
- if not isinstance(file_name_check_tuple[1], ComparisonOperator):
318
- raise TypeError(f'Second entry of tuple "file_name_check_tuple" is not of "ComparisonOperator" type.')
323
+ if sort_by_last_modified_time and not add_last_modified_time:
324
+ raise ValueError('Parameter "sort_by_last_modified_time" cannot be "True" if parameter '
325
+ '"add_last_modified_time" is not "True".')
319
326
 
320
327
  # === Function main ================
321
328
  # Define locals.
322
329
  object_list: list = list()
323
- relative_paths_list: list = list()
324
330
 
325
331
  # "Walk" over all the directories and subdirectories - make list of full file paths inside the directory
326
332
  # recursively.
327
333
  for dirpath, subdirs, files in os.walk(directory_fullpath):
328
334
  # Iterate through all the file names that were found in the folder.
329
335
  for file in files:
330
- # If 'file_name_check_tuple' was passed.
331
- if file_name_check_tuple:
332
- # Get separate variables from the tuple.
333
- # 'check_string' is a string that will be checked against 'file' iteration, which also a string.
334
- # 'comparison_operator' is 'ComparisonOperator' Enum object, that contains the string method
335
- # operator that will be used against the 'check_string'.
336
- check_string, comparison_operator = file_name_check_tuple
337
- # 'getattr' adds the string comparison method to the 'file' string. Example:
338
- # file.__eq__
339
- # 'comparison_operator' is the Enum class representation and '.value' method is the string
340
- # representation of '__eq__'.
341
- # and after that comes the check string to check against:
342
- # file.__eq__(check_string)
343
- if getattr(file, comparison_operator.value)(check_string):
344
- get_file()
345
- # If 'file_name_check_tuple' wasn't passed, then get all the files.
346
- else:
336
+ # If 'file_name_check_pattern' was passed.
337
+ if strings.match_pattern_against_string(file_name_check_pattern, file):
347
338
  get_file()
348
339
 
349
- return object_list, relative_paths_list
340
+ if not recursive:
341
+ break
342
+
343
+ # If 'sort_by_last_modified_time' was passed.
344
+ if sort_by_last_modified_time:
345
+ # Sort the list by last modified time.
346
+ object_list = list_of_dicts.sort_by_keys(object_list, key_list=['last_modified'])
347
+
348
+ return object_list
350
349
 
351
350
 
352
351
  def _get_relative_output_path_from_input_path(main_directory: str, file_directory: str, file_name: str = str()):
@@ -434,3 +433,32 @@ def get_files_and_folders(directory_path: str, string_contains: str = str()):
434
433
  """
435
434
  files_folders_list: list = glob.glob(f'{directory_path}{os.sep}*{string_contains}')
436
435
  return files_folders_list
436
+
437
+
438
+ def get_file_modified_time(file_path: str) -> float:
439
+ """
440
+ The function returns the time of last modification of the file in seconds since the epoch.
441
+
442
+ :param file_path: string, full path to file.
443
+ :return: float, time of last modification of the file in seconds since the epoch.
444
+ """
445
+ return os.path.getmtime(file_path)
446
+
447
+
448
+ def change_last_modified_date_of_file(file_path: str, new_date: float) -> None:
449
+ """
450
+ The function changes the last modified date of the file.
451
+
452
+ Example:
453
+ import os
454
+ import time
455
+ file_path = "C:\\Users\\file.txt"
456
+ new_timestamp = time.mktime(time.strptime('2023-10-03 12:00:00', '%Y-%m-%d %H:%M:%S'))
457
+ os.utime(file_path, (new_timestamp, new_timestamp))
458
+
459
+ :param file_path: string, full path to file.
460
+ :param new_date: float, time of last modification of the file in seconds since the epoch.
461
+ :return: None.
462
+ """
463
+
464
+ os.utime(file_path, (new_date, new_date))
@@ -104,7 +104,7 @@ def initialize_mitm_server(config_static):
104
104
  system_logger.info("Importing engine modules.")
105
105
 
106
106
  # Get full paths of all the 'engine_config.ini' files.
107
- engine_config_path_list, _ = get_file_paths_and_relative_directories(
107
+ engine_config_path_list = get_file_paths_and_relative_directories(
108
108
  directory_fullpath=config_static.ENGINES_DIRECTORY_PATH,
109
109
  file_name_check_tuple=(config_static.ENGINE_CONFIG_FILE_NAME, ComparisonOperator.EQ))
110
110
 
@@ -114,7 +114,7 @@ def initialize_mitm_server(config_static):
114
114
  for engine_config_path in engine_config_path_list:
115
115
  # Initialize engine.
116
116
  current_module = ModuleCategory(config_static.WORKING_DIRECTORY)
117
- current_module.fill_engine_fields_from_config(engine_config_path)
117
+ current_module.fill_engine_fields_from_config(engine_config_path['path'])
118
118
  current_module.initialize_engine(logs_path=config['log']['logs_path'],
119
119
  logger=system_logger)
120
120