atomicshop 2.2.8__py3-none-any.whl → 2.3.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.
- atomicshop/__init__.py +1 -1
- atomicshop/basics/bytes_arrays.py +34 -1
- atomicshop/basics/dicts.py +35 -0
- atomicshop/basics/dicts_nested.py +30 -0
- atomicshop/basics/enums.py +70 -0
- atomicshop/basics/strings.py +50 -0
- atomicshop/datetimes.py +36 -1
- atomicshop/domains.py +7 -2
- atomicshop/file_io/csvs.py +21 -0
- atomicshop/file_io/jsons.py +0 -1
- atomicshop/file_io/tomls.py +24 -0
- atomicshop/file_io/xlsxs.py +58 -0
- atomicshop/filesystem.py +87 -49
- atomicshop/mitm/initialize_mitm_server.py +2 -2
- atomicshop/mitm/statistic_analyzer.py +465 -0
- atomicshop/ssh_scripts/process_from_port.py +6 -1
- atomicshop/wrappers/factw/__init__.py +0 -0
- atomicshop/wrappers/factw/fact_config.py +2 -0
- atomicshop/wrappers/factw/upload_firmware.py +80 -0
- atomicshop/wrappers/loggingw/reading.py +31 -14
- atomicshop/wrappers/socketw/socket_server_tester.py +4 -4
- {atomicshop-2.2.8.dist-info → atomicshop-2.3.0.dist-info}/METADATA +3 -1
- {atomicshop-2.2.8.dist-info → atomicshop-2.3.0.dist-info}/RECORD +26 -19
- {atomicshop-2.2.8.dist-info → atomicshop-2.3.0.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.2.8.dist-info → atomicshop-2.3.0.dist-info}/WHEEL +0 -0
- {atomicshop-2.2.8.dist-info → atomicshop-2.3.0.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
|
@@ -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
|
atomicshop/basics/dicts.py
CHANGED
|
@@ -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
|
atomicshop/basics/strings.py
CHANGED
|
@@ -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
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# v1.0.2 - 21.03.2023 18:00
|
|
2
1
|
import datetime
|
|
3
2
|
from datetime import timedelta
|
|
4
3
|
import time
|
|
@@ -184,6 +183,42 @@ def create_date_range_for_year_month(
|
|
|
184
183
|
return create_date_range(first_date, last_date)
|
|
185
184
|
|
|
186
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
|
+
|
|
187
222
|
def get_seconds_random(minimum_seconds, maximum_seconds):
|
|
188
223
|
return random.randint(minimum_seconds, maximum_seconds)
|
|
189
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
|
-
|
|
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
|
atomicshop/file_io/csvs.py
CHANGED
|
@@ -2,6 +2,7 @@ import csv
|
|
|
2
2
|
from typing import Tuple, List
|
|
3
3
|
|
|
4
4
|
from .file_io import read_file_decorator
|
|
5
|
+
from . import file_io
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
@read_file_decorator
|
|
@@ -57,3 +58,23 @@ def write_list_to_csv(csv_list: list, csv_filepath: str) -> None:
|
|
|
57
58
|
writer.writeheader()
|
|
58
59
|
# Write list of dits as rows.
|
|
59
60
|
writer.writerows(csv_list)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_header(file_path: str, print_kwargs: dict = None) -> list:
|
|
64
|
+
"""
|
|
65
|
+
Function to get header from CSV file.
|
|
66
|
+
|
|
67
|
+
:param file_path: Full file path to CSV file.
|
|
68
|
+
:param print_kwargs: Keyword arguments dict for 'print_api' function.
|
|
69
|
+
|
|
70
|
+
:return: list of strings, each string is a header field.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
if not print_kwargs:
|
|
74
|
+
print_kwargs = dict()
|
|
75
|
+
|
|
76
|
+
# Get the first line of the file as text, which is the header.
|
|
77
|
+
header = file_io.read_file(file_path, read_to_list=True, **print_kwargs)[0]
|
|
78
|
+
# Split the header to list of keys.
|
|
79
|
+
header = header.split(',')
|
|
80
|
+
return header
|
atomicshop/file_io/jsons.py
CHANGED
|
@@ -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:
|
|
@@ -44,6 +43,16 @@ def add_object_to_path(path: str, object_string: str) -> str:
|
|
|
44
43
|
return os.path.join(path, object_string)
|
|
45
44
|
|
|
46
45
|
|
|
46
|
+
def get_file_name_with_extension(file_path: str) -> str:
|
|
47
|
+
"""
|
|
48
|
+
The function will return file name with extension of the file.
|
|
49
|
+
|
|
50
|
+
:param file_path: string, full file path.
|
|
51
|
+
:return: string.
|
|
52
|
+
"""
|
|
53
|
+
return str(Path(file_path).name)
|
|
54
|
+
|
|
55
|
+
|
|
47
56
|
def get_list_of_directories_in_file_path(
|
|
48
57
|
file_path: str, get_last_part: bool = True, convert_drive_to_string: bool = False) -> list:
|
|
49
58
|
"""
|
|
@@ -248,33 +257,35 @@ def get_file_names_from_directory(directory_path: str) -> list:
|
|
|
248
257
|
return file_list
|
|
249
258
|
|
|
250
259
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def get_file_paths_and_relative_directories(directory_fullpath: str,
|
|
261
|
-
file_name_check_tuple: tuple = tuple(),
|
|
262
|
-
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
|
+
):
|
|
263
268
|
"""
|
|
269
|
+
Recursive, by option.
|
|
264
270
|
The function receives a filesystem directory as string, scans it recursively for files and returns list of
|
|
265
271
|
full paths to that file (including).
|
|
266
272
|
If 'file_name_check_tuple' specified, the function will return only list of files that answer to the input
|
|
267
273
|
of that tuple.
|
|
268
|
-
Recursive.
|
|
269
274
|
|
|
270
275
|
:param directory_fullpath: string to full path to directory on the filesystem to scan.
|
|
271
|
-
:param
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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.
|
|
276
282
|
:param relative_file_name_as_directory: boolean that will set if 'relative_directory_list' should contain
|
|
277
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
|
+
|
|
278
289
|
:return: list of all found filenames with full file paths, list with relative folders to file excluding the
|
|
279
290
|
main folder.
|
|
280
291
|
"""
|
|
@@ -284,59 +295,57 @@ def get_file_paths_and_relative_directories(directory_fullpath: str,
|
|
|
284
295
|
Function gets the full file path, adds it to the found 'object_list' and gets the relative path to that
|
|
285
296
|
file, against the main path to directory that was passed to the parent function.
|
|
286
297
|
"""
|
|
298
|
+
|
|
299
|
+
file_result: dict = dict()
|
|
300
|
+
|
|
287
301
|
# Get full file path of the file.
|
|
288
|
-
|
|
289
|
-
object_list.append(file_path)
|
|
302
|
+
file_result['path'] = os.path.join(dirpath, file)
|
|
290
303
|
|
|
291
304
|
# if 'relative_file_name_as_directory' was passed.
|
|
292
305
|
if relative_file_name_as_directory:
|
|
293
306
|
# Output the path with filename.
|
|
294
|
-
|
|
307
|
+
file_result['relative_dir'] = _get_relative_output_path_from_input_path(directory_fullpath, dirpath, file)
|
|
295
308
|
# if 'relative_file_name_as_directory' wasn't passed.
|
|
296
309
|
else:
|
|
297
310
|
# Output the path without filename.
|
|
298
|
-
|
|
311
|
+
file_result['relative_dir'] = _get_relative_output_path_from_input_path(directory_fullpath, dirpath)
|
|
299
312
|
|
|
300
313
|
# Remove separator from the beginning if exists.
|
|
301
|
-
|
|
314
|
+
file_result['relative_dir'] = file_result['relative_dir'].removeprefix(os.sep)
|
|
302
315
|
|
|
303
|
-
|
|
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'])
|
|
304
320
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
321
|
+
object_list.append(file_result)
|
|
322
|
+
|
|
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".')
|
|
309
326
|
|
|
310
327
|
# === Function main ================
|
|
311
328
|
# Define locals.
|
|
312
329
|
object_list: list = list()
|
|
313
|
-
relative_paths_list: list = list()
|
|
314
330
|
|
|
315
331
|
# "Walk" over all the directories and subdirectories - make list of full file paths inside the directory
|
|
316
332
|
# recursively.
|
|
317
333
|
for dirpath, subdirs, files in os.walk(directory_fullpath):
|
|
318
334
|
# Iterate through all the file names that were found in the folder.
|
|
319
335
|
for file in files:
|
|
320
|
-
# If '
|
|
321
|
-
if
|
|
322
|
-
# Get separate variables from the tuple.
|
|
323
|
-
# 'check_string' is a string that will be checked against 'file' iteration, which also a string.
|
|
324
|
-
# 'comparison_operator' is 'ComparisonOperator' Enum object, that contains the string method
|
|
325
|
-
# operator that will be used against the 'check_string'.
|
|
326
|
-
check_string, comparison_operator = file_name_check_tuple
|
|
327
|
-
# 'getattr' adds the string comparison method to the 'file' string. Example:
|
|
328
|
-
# file.__eq__
|
|
329
|
-
# 'comparison_operator' is the Enum class representation and '.value' method is the string
|
|
330
|
-
# representation of '__eq__'.
|
|
331
|
-
# and after that comes the check string to check against:
|
|
332
|
-
# file.__eq__(check_string)
|
|
333
|
-
if getattr(file, comparison_operator.value)(check_string):
|
|
334
|
-
get_file()
|
|
335
|
-
# If 'file_name_check_tuple' wasn't passed, then get all the files.
|
|
336
|
-
else:
|
|
336
|
+
# If 'file_name_check_pattern' was passed.
|
|
337
|
+
if strings.match_pattern_against_string(file_name_check_pattern, file):
|
|
337
338
|
get_file()
|
|
338
339
|
|
|
339
|
-
|
|
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
|
|
340
349
|
|
|
341
350
|
|
|
342
351
|
def _get_relative_output_path_from_input_path(main_directory: str, file_directory: str, file_name: str = str()):
|
|
@@ -424,3 +433,32 @@ def get_files_and_folders(directory_path: str, string_contains: str = str()):
|
|
|
424
433
|
"""
|
|
425
434
|
files_folders_list: list = glob.glob(f'{directory_path}{os.sep}*{string_contains}')
|
|
426
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))
|