anb-python-components 1.2.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.
- anb_python_components/__init__.py +1 -0
- anb_python_components/classes/__init__.py +1 -0
- anb_python_components/classes/action_state.py +211 -0
- anb_python_components/classes/directory.py +115 -0
- anb_python_components/classes/file.py +226 -0
- anb_python_components/classes/shortcode_parser.py +195 -0
- anb_python_components/custom_types/__init__.py +3 -0
- anb_python_components/custom_types/guid.py +169 -0
- anb_python_components/custom_types/object_array.py +625 -0
- anb_python_components/custom_types/shortcode_attributes.py +128 -0
- anb_python_components/custom_types/two_dim_size.py +413 -0
- anb_python_components/custom_types/version_info.py +461 -0
- anb_python_components/enums/__init__.py +1 -0
- anb_python_components/enums/message_type.py +44 -0
- anb_python_components/enums/not_bool_action.py +24 -0
- anb_python_components/enums/type_copy_strategy.py +47 -0
- anb_python_components/exceptions/__init__.py +1 -0
- anb_python_components/exceptions/wrong_type_exception.py +20 -0
- anb_python_components/extensions/__init__.py +1 -0
- anb_python_components/extensions/array_extension.py +34 -0
- anb_python_components/extensions/bool_extension.py +87 -0
- anb_python_components/extensions/string_extension.py +259 -0
- anb_python_components/extensions/string_extension_constant.py +80 -0
- anb_python_components/extensions/type_extension.py +112 -0
- anb_python_components/models/__init__.py +1 -0
- anb_python_components/models/action_state_message.py +27 -0
- anb_python_components/models/shortcode_model.py +31 -0
- anb_python_components-1.2.1.dist-info/METADATA +12 -0
- anb_python_components-1.2.1.dist-info/RECORD +48 -0
- anb_python_components-1.2.1.dist-info/WHEEL +5 -0
- anb_python_components-1.2.1.dist-info/licenses/LICENSE +235 -0
- anb_python_components-1.2.1.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/classes/__init__.py +1 -0
- tests/classes/action_state_test.py +138 -0
- tests/classes/directory_test.py +19 -0
- tests/classes/file_test.py +79 -0
- tests/classes/shortcode_parser_test.py +105 -0
- tests/custom_types/__init__.py +1 -0
- tests/custom_types/guid_test.py +14 -0
- tests/custom_types/object_array_test.py +160 -0
- tests/custom_types/two_dim_size_test.py +37 -0
- tests/custom_types/version_info_test.py +51 -0
- tests/extensions/__init__.py +1 -0
- tests/extensions/array_extension_test.py +21 -0
- tests/extensions/bool_extension_test.py +19 -0
- tests/extensions/string_extension_test.py +55 -0
- tests/extensions/type_extension_test.py +38 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# anb_python_components/__init__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# anb_python_components/classes/__init__.py
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# anb_python_components/classes/action_state.py
|
|
2
|
+
import copy
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
from anb_python_components.exceptions.wrong_type_exception import WrongTypeException
|
|
6
|
+
from anb_python_components.models.action_state_message import ActionStateMessage, MessageType
|
|
7
|
+
|
|
8
|
+
class ActionState[T]:
|
|
9
|
+
"""
|
|
10
|
+
Класс для хранения состояния действия.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__ (self, default: T | None = None):
|
|
14
|
+
self.__messages: list[ActionStateMessage] = []
|
|
15
|
+
self.value: T | None = copy.deepcopy(default) if default is not None else None
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def get_string_error_only () -> Callable[[ActionStateMessage], bool]:
|
|
19
|
+
"""
|
|
20
|
+
Возвращает лямбду для фильтрации только сообщений типа ERROR.
|
|
21
|
+
"""
|
|
22
|
+
return lambda message: message.message_type == MessageType.ERROR
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def get_string_error_and_warning () -> Callable[[ActionStateMessage], bool]:
|
|
26
|
+
"""
|
|
27
|
+
Возвращает лямбду для фильтрации сообщений типа ERROR и WARNING.
|
|
28
|
+
"""
|
|
29
|
+
return lambda message: message.message_type in [MessageType.ERROR, MessageType.WARNING]
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def get_string_all () -> Callable[[ActionStateMessage], bool]:
|
|
33
|
+
"""
|
|
34
|
+
Возвращает лямбду для фильтрации всех сообщений.
|
|
35
|
+
"""
|
|
36
|
+
return lambda message: True
|
|
37
|
+
|
|
38
|
+
def add_message (self, message: ActionStateMessage):
|
|
39
|
+
"""
|
|
40
|
+
Добавляет сообщение в список.
|
|
41
|
+
:param message: Сообщение.
|
|
42
|
+
"""
|
|
43
|
+
if message:
|
|
44
|
+
self.__messages.append(message)
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError("Сообщение не может быть пустым.")
|
|
47
|
+
|
|
48
|
+
def add (self, message_type: MessageType, message: str, flags: dict[str, bool] | None = None):
|
|
49
|
+
"""
|
|
50
|
+
Добавляет сообщение в список.
|
|
51
|
+
:param message_type: Тип сообщения.
|
|
52
|
+
:param message: Сообщение.
|
|
53
|
+
:param flags: Флаги (необязательный).
|
|
54
|
+
"""
|
|
55
|
+
# Создаём сообщение
|
|
56
|
+
message = ActionStateMessage(message_type, message, flags)
|
|
57
|
+
|
|
58
|
+
# Добавляем в список
|
|
59
|
+
self.add_message(message)
|
|
60
|
+
|
|
61
|
+
def add_info (self, message: str, flags: dict[str, bool] | None = None):
|
|
62
|
+
"""
|
|
63
|
+
Добавляет информационное сообщение в список.
|
|
64
|
+
:param message: Сообщение.
|
|
65
|
+
:param flags: Флаги (необязательный).
|
|
66
|
+
"""
|
|
67
|
+
self.add(MessageType.INFO, message, flags)
|
|
68
|
+
|
|
69
|
+
def add_warning (self, message: str, flags: dict[str, bool] | None = None):
|
|
70
|
+
"""
|
|
71
|
+
Добавляет предупреждающее сообщение в список.
|
|
72
|
+
:param message: Сообщение.
|
|
73
|
+
:param flags: Флаги (необязательный).
|
|
74
|
+
"""
|
|
75
|
+
self.add(MessageType.WARNING, message, flags)
|
|
76
|
+
|
|
77
|
+
def add_error (self, message: str, flags: dict[str, bool] | None = None):
|
|
78
|
+
"""
|
|
79
|
+
Добавляет сообщение об ошибке в список.
|
|
80
|
+
:param message: Сообщение.
|
|
81
|
+
:param flags: Флаги (необязательный).
|
|
82
|
+
"""
|
|
83
|
+
self.add(MessageType.ERROR, message, flags)
|
|
84
|
+
|
|
85
|
+
def add_state (self, state, clear_all_before: bool = False):
|
|
86
|
+
"""
|
|
87
|
+
Добавляет другое состояние действия в текущее.
|
|
88
|
+
:param state:ActionState - Состояние действия.
|
|
89
|
+
:param clear_all_before - Очистить список перед добавлением (по умолчанию - False).
|
|
90
|
+
|
|
91
|
+
ВНИМАНИЕ! Метод не передаёт значение value состояния, а просто переносит его сообщения.
|
|
92
|
+
"""
|
|
93
|
+
# Проверяем тип
|
|
94
|
+
if not isinstance(state, ActionState):
|
|
95
|
+
# - и если не ActionState, то бросаем исключение
|
|
96
|
+
raise WrongTypeException("Неверный тип состояния действия.", "ActionState", str(type(state)), "state")
|
|
97
|
+
|
|
98
|
+
# Если нужно очистить список перед добавлением
|
|
99
|
+
if clear_all_before:
|
|
100
|
+
# - то очищаем его
|
|
101
|
+
self.__messages.clear()
|
|
102
|
+
|
|
103
|
+
# Получаем список сообщений из состояния
|
|
104
|
+
state_messages = state.get_messages()
|
|
105
|
+
|
|
106
|
+
# Перебираем все сообщения переданного состояния
|
|
107
|
+
for state_message in state_messages:
|
|
108
|
+
# - и если это сообщение состояния
|
|
109
|
+
if isinstance(state_message, ActionStateMessage):
|
|
110
|
+
# -- то добавляем его в текущий список
|
|
111
|
+
self.__messages.append(state_message)
|
|
112
|
+
|
|
113
|
+
def get_messages (self, predicate: Callable[[ActionStateMessage], bool] | None = None) -> list[ActionStateMessage]:
|
|
114
|
+
"""
|
|
115
|
+
Возвращает список сообщений с учетом условия (если указан).
|
|
116
|
+
:param predicate: Условие выборки.
|
|
117
|
+
:return: Список сообщений.
|
|
118
|
+
"""
|
|
119
|
+
# Если условие указано
|
|
120
|
+
if predicate:
|
|
121
|
+
# - то фильтруем список по нему
|
|
122
|
+
return list(filter(predicate, self.__messages))
|
|
123
|
+
else:
|
|
124
|
+
# - если нет, то просто возвращаем весь список
|
|
125
|
+
return self.__messages.copy()
|
|
126
|
+
|
|
127
|
+
def get_string_messages (
|
|
128
|
+
self, predicate: Callable[[ActionStateMessage], bool] | None = None, separator: str = "\n"
|
|
129
|
+
) -> str:
|
|
130
|
+
"""
|
|
131
|
+
Возвращает строку сообщений с учетом условия (если указано).
|
|
132
|
+
:param predicate: Условие выборки (необязательный).
|
|
133
|
+
:param separator: Разделитель строк (необязательный, по умолчанию - "\n").
|
|
134
|
+
:return: Строка сообщений.
|
|
135
|
+
"""
|
|
136
|
+
# Получаем список сообщений с учетом условия
|
|
137
|
+
messages = self.get_messages(predicate)
|
|
138
|
+
|
|
139
|
+
# Объединяем их в строку и возвращаем
|
|
140
|
+
return separator.join(map(lambda message: message.message, messages))
|
|
141
|
+
|
|
142
|
+
def has_infos (self) -> bool:
|
|
143
|
+
"""
|
|
144
|
+
Проверяет, есть ли в списке информационные сообщения.
|
|
145
|
+
:return: True, если есть, иначе False.
|
|
146
|
+
"""
|
|
147
|
+
return any(message.message_type == MessageType.INFO for message in self.__messages)
|
|
148
|
+
|
|
149
|
+
def has_warnings (self) -> bool:
|
|
150
|
+
"""
|
|
151
|
+
Проверяет, есть ли в списке предупреждающие сообщения.
|
|
152
|
+
:return: True, если есть, иначе False.
|
|
153
|
+
"""
|
|
154
|
+
return any(message.message_type == MessageType.WARNING for message in self.__messages)
|
|
155
|
+
|
|
156
|
+
def has_errors (self) -> bool:
|
|
157
|
+
"""
|
|
158
|
+
Проверяет, есть ли в списке сообщения об ошибках.
|
|
159
|
+
:return: True, если есть, иначе False.
|
|
160
|
+
"""
|
|
161
|
+
return any(message.message_type == MessageType.ERROR for message in self.__messages)
|
|
162
|
+
|
|
163
|
+
def is_success (self, ignore_warnings: bool = False) -> bool:
|
|
164
|
+
"""
|
|
165
|
+
Проверяет, успешное ли состояние действия.
|
|
166
|
+
:param ignore_warnings: Игнорировать предупреждения (по умолчанию - False).
|
|
167
|
+
:return: Успешно ли состояние действия.
|
|
168
|
+
"""
|
|
169
|
+
# Задаем начальное значение результата
|
|
170
|
+
result = True
|
|
171
|
+
|
|
172
|
+
# Если не нужно игнорировать предупреждения
|
|
173
|
+
if not ignore_warnings:
|
|
174
|
+
# - то проверяем наличие предупреждений
|
|
175
|
+
result = result and not self.has_warnings()
|
|
176
|
+
|
|
177
|
+
# Проверяем наличие ошибок
|
|
178
|
+
result = result and not self.has_errors()
|
|
179
|
+
|
|
180
|
+
# Выдаём результат
|
|
181
|
+
return result
|
|
182
|
+
|
|
183
|
+
def clear (self, predicate: Callable[[ActionStateMessage], bool] | None = None):
|
|
184
|
+
"""
|
|
185
|
+
Очищает список сообщений с учетом условия (если указан).
|
|
186
|
+
:param predicate: Функция для фильтрации сообщений для очистки.
|
|
187
|
+
"""
|
|
188
|
+
# Если условие указано
|
|
189
|
+
if predicate:
|
|
190
|
+
# - то фильтруем список
|
|
191
|
+
self.__messages = list(filter(lambda message: not predicate(message), self.__messages))
|
|
192
|
+
else:
|
|
193
|
+
# - если нет, то просто очищаем список
|
|
194
|
+
self.__messages.clear()
|
|
195
|
+
|
|
196
|
+
def count (self, predicate: Callable[[ActionStateMessage], bool] | None = None) -> int:
|
|
197
|
+
"""
|
|
198
|
+
Возвращает количество сообщений в списке с учетом условия (если указано).
|
|
199
|
+
:param predicate: Условие выборки (необязательный).
|
|
200
|
+
:return: Количество сообщений.
|
|
201
|
+
"""
|
|
202
|
+
# Если условие указано
|
|
203
|
+
if predicate:
|
|
204
|
+
# - то фильтруем список по нему
|
|
205
|
+
messages = self.get_messages(predicate)
|
|
206
|
+
|
|
207
|
+
# - и возвращаем его длину
|
|
208
|
+
return len(messages)
|
|
209
|
+
else:
|
|
210
|
+
# - если нет, то просто возвращаем длину списка
|
|
211
|
+
return len(self.__messages)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# anb_python_components/classes/directory.py
|
|
2
|
+
import os
|
|
3
|
+
import platform
|
|
4
|
+
import subprocess
|
|
5
|
+
|
|
6
|
+
from anb_python_components.classes.action_state import ActionState
|
|
7
|
+
|
|
8
|
+
class Directory:
|
|
9
|
+
"""
|
|
10
|
+
Класс для работы с директориями.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# Словарь сообщений об ошибках для удаления директории
|
|
14
|
+
REMOVE_DIRECTORY_ERROR_MESSAGES: dict[str, str] = {
|
|
15
|
+
'directory_not_exist': "Директория не существует или нет доступа на запись!",
|
|
16
|
+
'error_deleting_directory': 'Ошибка удаления каталога: %s. Код возврата: %d!',
|
|
17
|
+
'unhandled_error': 'Ошибка удаления директории %s: %s!'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def remove (directory: str, error_messages: dict[str, str] | None = None) -> ActionState[bool]:
|
|
22
|
+
"""
|
|
23
|
+
Рекурсивно удаляет директорию с соответствующим результатом.
|
|
24
|
+
|
|
25
|
+
:param directory: Путь к директории.
|
|
26
|
+
:param error_messages: Слова для отображения ошибок. По умолчанию используются сообщения из REMOVE_DIRECTORY_ERROR_MESSAGES.
|
|
27
|
+
:return: Объект ActionState с информацией о результате.
|
|
28
|
+
"""
|
|
29
|
+
# Создаем объект ActionState для хранения результата
|
|
30
|
+
result = ActionState[bool](False)
|
|
31
|
+
|
|
32
|
+
# Если не заданы сообщения об ошибках
|
|
33
|
+
if error_messages is None:
|
|
34
|
+
# - устанавливаем сообщения по умолчанию
|
|
35
|
+
error_messages = Directory.REMOVE_DIRECTORY_ERROR_MESSAGES
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
# Проверяем существование директории
|
|
39
|
+
if not Directory.is_exists(directory):
|
|
40
|
+
# - если директория не существует, добавляем ошибку
|
|
41
|
+
result.add_error(error_messages['directory_not_exist'])
|
|
42
|
+
# - возвращаем результат
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
# Определяем текущую операционную систему
|
|
46
|
+
system_os = platform.system().lower()
|
|
47
|
+
|
|
48
|
+
# Проверяем операционную систему. Если это Windows
|
|
49
|
+
if system_os == 'windows':
|
|
50
|
+
# - задаем команду для Windows
|
|
51
|
+
command = ['cmd.exe', '/C', 'rd', '/S', '/Q', directory]
|
|
52
|
+
else:
|
|
53
|
+
# - иначе задаем команду для Unix-подобных систем
|
|
54
|
+
command = ['rm', '-rf', directory]
|
|
55
|
+
|
|
56
|
+
# Запуск команды с безопасностью (используется subprocess.run)
|
|
57
|
+
process = subprocess.run(command, capture_output = True, text = True)
|
|
58
|
+
|
|
59
|
+
# Анализируем код возврата процесса и если он не равен 0
|
|
60
|
+
if process.returncode != 0:
|
|
61
|
+
# - добавляем ошибку
|
|
62
|
+
result.add_error(error_messages['error_deleting_directory'] % (directory, process.returncode))
|
|
63
|
+
# - возвращаем результат
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
# Установка успешного результата
|
|
67
|
+
result.value = True
|
|
68
|
+
|
|
69
|
+
# Возвращаем результат
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
except Exception as ex:
|
|
73
|
+
# Обработка необработанных исключений
|
|
74
|
+
result.add_error(error_messages['unhandled_error'] % (directory, str(ex)))
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def is_exists (directory: str, check_access_level: str = '') -> bool:
|
|
79
|
+
"""
|
|
80
|
+
Проверяет существование директории и доступность по правам доступа.
|
|
81
|
+
|
|
82
|
+
:param directory: Путь к директории.
|
|
83
|
+
:param check_access_level: Строка, содержащая символы 'r', 'w' и 'x', которые указывают на необходимость проверки прав доступа на чтение, запись и исполнение соответственно. Если строка пустая, проверка прав доступа не выполняется. Если нет какого-либо символа, проверка прав доступа не выполняется для этого типа прав. По умолчанию: ''.
|
|
84
|
+
:return: True, если директория существует и доступна, иначе False.
|
|
85
|
+
"""
|
|
86
|
+
# Проверяем существование директории
|
|
87
|
+
if not os.path.exists(directory):
|
|
88
|
+
# - и если директория не существует, возвращаем False
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
# Проверяем, что это именно директория, а не файл
|
|
92
|
+
if not os.path.isdir(directory):
|
|
93
|
+
# - если это не директория, возвращаем False
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
# Задаем флаги проверки прав доступа
|
|
97
|
+
access_level_check = check_access_level.lower()
|
|
98
|
+
|
|
99
|
+
# Проверяем права на чтение, если это требуется
|
|
100
|
+
if 'r' in access_level_check and not os.access(directory, os.R_OK):
|
|
101
|
+
# - если нет прав на чтение, возвращаем False
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
# Проверяем права на запись, если это требуется
|
|
105
|
+
if 'w' in access_level_check and not os.access(directory, os.W_OK):
|
|
106
|
+
# - если нет прав на запись, возвращаем False
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
# Проверяем права на исполнение, если это требуется
|
|
110
|
+
if 'x' in access_level_check and not os.access(directory, os.X_OK):
|
|
111
|
+
# - если нет прав на запись, возвращаем False
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
# Если все проверки успешны, возвращаем True
|
|
115
|
+
return True
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# anb_python_components/classes/action_state.py
|
|
2
|
+
import glob
|
|
3
|
+
import hashlib
|
|
4
|
+
import math
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from anb_python_components.classes.action_state import ActionState
|
|
8
|
+
|
|
9
|
+
class File:
|
|
10
|
+
"""
|
|
11
|
+
Класс для работы с файлами.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# Словарь сообщений об ошибках для получения размера файла
|
|
15
|
+
FILE_SIZE_ERROR_MESSAGES: dict[str, str] = {
|
|
16
|
+
'file_not_exist': 'Файл не существует!',
|
|
17
|
+
'not_a_file': 'Указанный путь не является файлом!',
|
|
18
|
+
'cannot_get_size': 'Не удалось получить размер файла!'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# Словарь локализации размеров файлов.
|
|
22
|
+
FILE_SIZE_UNITS: list[str] = ['байт', 'КБ', 'МБ', 'ГБ', 'ТБ']
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def is_exist (file_path: str) -> bool:
|
|
26
|
+
"""
|
|
27
|
+
Проверяет, существует ли файл по указанному пути.
|
|
28
|
+
:param file_path: Путь к файлу.
|
|
29
|
+
:return: bool: True, если файл существует, False в противном случае.
|
|
30
|
+
"""
|
|
31
|
+
return True if os.path.exists(file_path) and os.path.isfile(file_path) else False
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def find (directory: str, pattern: str = '*', exclude_list: set[str] = str()) -> list | bool:
|
|
35
|
+
"""
|
|
36
|
+
Ищет файлы, удовлетворяющие заданному паттерну, рекурсивно проходя по указанным директориям.
|
|
37
|
+
:param directory: Директория, в которой производится поиск.
|
|
38
|
+
:param pattern: Маска файла (по умолчанию '*').
|
|
39
|
+
:param exclude_list: Список директорий, которые нужно исключить из итогового списка. Внимание: будут исключены все поддиректории этих директорий.
|
|
40
|
+
:return: list|bool: Список найденных файлов или False, если произошла ошибка.
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
# Начальная точка поиска — указанный каталог
|
|
44
|
+
files = []
|
|
45
|
+
|
|
46
|
+
# Начинаем обход каталога и его вложенных подкаталогов
|
|
47
|
+
for root, dirs, filenames in os.walk(directory):
|
|
48
|
+
# - фильтруем директории, исключая заданные в exclude_list
|
|
49
|
+
dirs[:] = [d for d in dirs if d not in exclude_list]
|
|
50
|
+
|
|
51
|
+
# Применяем маску поиска (* или любую другую)
|
|
52
|
+
matches = glob.glob(os.path.join(root, pattern))
|
|
53
|
+
|
|
54
|
+
# Добавляем найденные файлы в общий список
|
|
55
|
+
files.extend(matches)
|
|
56
|
+
|
|
57
|
+
# Возвращаем список найденных файлов
|
|
58
|
+
return files
|
|
59
|
+
|
|
60
|
+
except OSError:
|
|
61
|
+
# Если возникает ошибка файловой операции, возвращаем False
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def extract_file_name (file_path: str) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Извлекает имя файла из полного пути к нему.
|
|
68
|
+
:param file_path: Полный путь к файлу.
|
|
69
|
+
:return: str: Имя файла.
|
|
70
|
+
"""
|
|
71
|
+
return os.path.basename(file_path)
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def extract_file_extension (file_path: str, with_dot: bool = True) -> str:
|
|
75
|
+
"""
|
|
76
|
+
Извлекает расширение файла из полного пути к нему.
|
|
77
|
+
:param file_path: Полный путь к файлу.
|
|
78
|
+
:param with_dot: Если True, точка перед расширением будет добавлена к результату, если False, точка будет удалена.
|
|
79
|
+
:return: str: Расширение файла.
|
|
80
|
+
"""
|
|
81
|
+
# Получаю расширение файла из полного пути к нему
|
|
82
|
+
_, extension = os.path.splitext(file_path)
|
|
83
|
+
|
|
84
|
+
# Если нужно добавить точку перед расширением, добавляю её
|
|
85
|
+
return extension if with_dot else extension.lstrip('.')
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def extract_file_name_without_extension (file_path: str) -> str:
|
|
89
|
+
# Имя файла без пути к нему
|
|
90
|
+
file_name_only = File.extract_file_name(file_path)
|
|
91
|
+
|
|
92
|
+
# Расширение файла
|
|
93
|
+
file_extension = File.extract_file_extension(file_path)
|
|
94
|
+
|
|
95
|
+
# Возвращаем имя файла без пути к нему и расширения
|
|
96
|
+
return file_name_only[:-len(file_extension)]
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def relative_path (full_path: str, base_path: str) -> str | bool:
|
|
100
|
+
"""
|
|
101
|
+
Возвращает относительный путь к файлу относительно заданной директории.
|
|
102
|
+
:param full_path: Полный путь к файлу.
|
|
103
|
+
:param base_path: Базовая директория.
|
|
104
|
+
:return: str|bool: Относительный путь к файлу или False, если путь не относится к заданной директории.
|
|
105
|
+
"""
|
|
106
|
+
return full_path[len(base_path):] if base_path.lower() in full_path.lower() else False
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def size (file_name: str, error_localization: dict[str, str] | None = None) -> ActionState[int]:
|
|
110
|
+
"""
|
|
111
|
+
Получает размер файла и формирует результат с возможными ошибками.
|
|
112
|
+
|
|
113
|
+
:param file_name: Путь к файлу.
|
|
114
|
+
:param error_localization: Локализации сообщений об ошибках. Ече если None, используются сообщения по умолчанию. По умолчанию: None
|
|
115
|
+
:return: Объект ActionState с размером файла или ошибками.
|
|
116
|
+
"""
|
|
117
|
+
# Создаем результат
|
|
118
|
+
result = ActionState(-1)
|
|
119
|
+
|
|
120
|
+
# Если не заданы сообщения об ошибках
|
|
121
|
+
if error_localization is None:
|
|
122
|
+
# - устанавливаем сообщения по умолчанию
|
|
123
|
+
error_localization = File.FILE_SIZE_ERROR_MESSAGES
|
|
124
|
+
|
|
125
|
+
# Проверяем существование файла
|
|
126
|
+
if not os.path.exists(file_name):
|
|
127
|
+
# - если файл не существует, добавляем ошибку
|
|
128
|
+
result.add_error(error_localization['file_not_exist'])
|
|
129
|
+
# - возвращаем результат
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
# Проверяем, что это именно файл
|
|
133
|
+
if not os.path.isfile(file_name):
|
|
134
|
+
# - если это не файл, добавляем ошибку
|
|
135
|
+
result.add_error(error_localization['not_a_file'])
|
|
136
|
+
# - возвращаем результат
|
|
137
|
+
return result
|
|
138
|
+
|
|
139
|
+
# Пробуем получить размер файла
|
|
140
|
+
try:
|
|
141
|
+
size = os.path.getsize(file_name)
|
|
142
|
+
# - если размер файла получен успешно, добавляем его в результат
|
|
143
|
+
result.value = size
|
|
144
|
+
except OSError:
|
|
145
|
+
# - если возникла ошибка при получении размера файла, добавляем ошибку
|
|
146
|
+
result.add_error(error_localization['cannot_get_size'])
|
|
147
|
+
|
|
148
|
+
# Возвращаем результат
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def size_to_string (
|
|
153
|
+
file_size: int, localize_file_size: dict[str, str] | None = None, decimal_separator: str = '.'
|
|
154
|
+
) -> str:
|
|
155
|
+
"""
|
|
156
|
+
Преобразует размер файла в строку с локализацией.
|
|
157
|
+
:param file_size: Размер файла в байтах.
|
|
158
|
+
:param localize_file_size: Словарь локализации размеров файлов. Если None, используются значения по умолчанию. По умолчанию: None.
|
|
159
|
+
:param decimal_separator: Разделитель дробной части числа. По умолчанию: '.'.
|
|
160
|
+
:return: str: Строка с размером файла.
|
|
161
|
+
"""
|
|
162
|
+
# Если не заданы локализации размеров файлов
|
|
163
|
+
if localize_file_size is None:
|
|
164
|
+
# - устанавливаем локализации по умолчанию
|
|
165
|
+
localize_file_size = File.FILE_SIZE_UNITS
|
|
166
|
+
|
|
167
|
+
# Вычисление степени для преобразования: берём минимум из 4 и результата округления до ближайшего целого числа
|
|
168
|
+
# в меньшую сторону логарифма размера файла в байтах по основанию 1024 (это показывает, сколько раз нужно
|
|
169
|
+
# разделить размер файла на 1024, чтобы получить значение в более крупных единицах измерения). Ограничение в 4
|
|
170
|
+
# необходимо для того, чтобы соответствовать единице измерения ТБ (терабайт).
|
|
171
|
+
power = min(4, math.floor(math.log(file_size, 1024))) if file_size > 0 else 0
|
|
172
|
+
|
|
173
|
+
# Преобразование размера файла: размер файла делим на 1024 в степени, равной степени $power,
|
|
174
|
+
# затем округляем полученное до 2 цифр после запятой.
|
|
175
|
+
size = round(file_size / (1024 ** power), 2)
|
|
176
|
+
|
|
177
|
+
# Возвращаем преобразованное значение вместе с единицей измерения
|
|
178
|
+
return f"{size:,.2f} {localize_file_size[power]}".replace('.', decimal_separator)
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def hash (file_name: str, hash_algorithm: str = 'sha256') -> str:
|
|
182
|
+
"""
|
|
183
|
+
Вычисляет хэш файла.
|
|
184
|
+
:param file_name: Имя файла для которого нужно вычислить хэш.
|
|
185
|
+
:param hash_algorithm: Алгоритм хэширования. По умолчанию: 'sha256'.
|
|
186
|
+
:return: Строка с хэшем.
|
|
187
|
+
"""
|
|
188
|
+
# Преобразование алгоритма в нижний регистр
|
|
189
|
+
hash_algorithm = hash_algorithm.lower()
|
|
190
|
+
|
|
191
|
+
# Определяем алгоритм хэширования
|
|
192
|
+
match hash_algorithm:
|
|
193
|
+
# md5
|
|
194
|
+
case 'md5':
|
|
195
|
+
hasher = hashlib.md5()
|
|
196
|
+
# sha1
|
|
197
|
+
case 'sha1':
|
|
198
|
+
hasher = hashlib.sha1()
|
|
199
|
+
# sha256
|
|
200
|
+
case 'sha256':
|
|
201
|
+
hasher = hashlib.sha256()
|
|
202
|
+
# sha512
|
|
203
|
+
case 'sha512':
|
|
204
|
+
hasher = hashlib.sha512()
|
|
205
|
+
# sha3_256
|
|
206
|
+
case 'sha3_256':
|
|
207
|
+
hasher = hashlib.sha3_256()
|
|
208
|
+
# sha3_512
|
|
209
|
+
case 'sha3_512':
|
|
210
|
+
hasher = hashlib.sha3_512()
|
|
211
|
+
# blake2b
|
|
212
|
+
case 'blake2':
|
|
213
|
+
hasher = hashlib.blake2b()
|
|
214
|
+
# по умолчанию
|
|
215
|
+
case _:
|
|
216
|
+
hasher = hashlib.sha256()
|
|
217
|
+
|
|
218
|
+
# Открываем файл для чтения
|
|
219
|
+
with open(file_name, 'rb') as file:
|
|
220
|
+
# - читаем файл по 4096 байт, чтобы сэкономить память
|
|
221
|
+
while chunk := file.read(4096):
|
|
222
|
+
# - добавляем прочитанные данные в хэш
|
|
223
|
+
hasher.update(chunk)
|
|
224
|
+
|
|
225
|
+
# Возвращаем hex-представление хэша
|
|
226
|
+
return hasher.hexdigest()
|