py.lib.aw_config

Yohn Y. 2026-06-23 Parent:ed99d4db56a7 Child:260bceaa2d62

16:34b98b38a9f0 Go to Latest

py.lib.aw_config/src/aw_config/file.py

.. 0.202606.2 + Возможность удаления конфигурации из менеджера

History
awgur@0 1 # coding: utf-8
awgur@0 2 """\
awgur@0 3 Функции, помогающие в разборе конфигурационного файла
awgur@0 4 """
awgur@0 5 import toml
awgur@0 6 from os.path import exists
awgur@0 7 from typing import Union, Any
awgur@0 8
awgur@0 9 from .error import Error
awgur@0 10 from .type_helpers import BaseTypeHelper
awgur@7 11 from ._lib import _UNDEFINED
awgur@0 12
awgur@0 13
awgur@0 14 class ConfigFileError(Error):
awgur@0 15 """\
awgur@0 16 Базовое исключение при обработке конфигурационного файла
awgur@0 17 """
awgur@0 18
awgur@0 19
awgur@0 20 class ConfigSectionNotFound(ConfigFileError):
awgur@0 21 """\
awgur@0 22 Отсутствие нужного раздела в конфигурации
awgur@0 23 """
awgur@0 24
awgur@0 25
awgur@0 26 class ConfigFileContent(object):
awgur@0 27 """\
awgur@0 28 Работа с содержимым файла конфигурации
awgur@0 29 """
awgur@0 30 def __init__(self, file_data, section_name=None):
awgur@0 31 """\
awgur@0 32 :param file_data: Разобранное в словарь содержимое файла конфигурации
awgur@0 33 :param section_name: Имя секции в конфигурации
awgur@0 34 """
awgur@0 35 self.d = file_data
awgur@0 36 self.section = section_name
awgur@0 37
awgur@0 38 def _format_err_msg(self, msg: str) -> str:
awgur@0 39 if self.section is not None:
awgur@0 40 return f'Раздел конфигурации "{self.section}": {msg}'
awgur@0 41
awgur@0 42 else:
awgur@0 43 return msg
awgur@0 44
awgur@0 45 def __enter__(self):
awgur@0 46 return self
awgur@0 47
awgur@0 48 def __exit__(self, exc_type, exc_val, exc_tb):
awgur@0 49 pass
awgur@0 50
awgur@0 51 def get_value(self, name: str,
awgur@2 52 val_type: Union[type, BaseTypeHelper] = str,
awgur@7 53 default: Any = _UNDEFINED,
awgur@0 54 mandatory: bool = True):
awgur@0 55 """\
awgur@0 56 Получить из текущего раздела конфигурации нужный элемент
awgur@15 57
awgur@0 58 :param name: имя параметра
awgur@0 59 :param val_type: тип параметра
awgur@0 60 :param default: значение параметра по умолчанию
awgur@0 61 :param mandatory: является ли параметр обязательным. Если ``False``, то в случае отсутствия
awgur@0 62 параметра будет использовано значение None
awgur@0 63 """
awgur@0 64 if name not in self.d:
awgur@12 65 if default is not _UNDEFINED:
awgur@12 66 return default
awgur@12 67
awgur@12 68 elif mandatory:
awgur@0 69 raise ConfigFileError(self._format_err_msg(f'Параметр "{name}" в файле отсутствует'))
awgur@0 70
awgur@0 71 else:
awgur@12 72 return None
awgur@0 73
awgur@0 74 else:
awgur@0 75 _buf = self.d[name]
awgur@0 76 try:
awgur@0 77 return val_type(_buf)
awgur@0 78
awgur@0 79 except (ValueError, TypeError) as e:
awgur@0 80 raise ConfigFileError(self._format_err_msg(f'Не удалось привести значение "{_buf}" к типу '
awgur@0 81 f'{repr(val_type)}: {e}'))
awgur@0 82
awgur@15 83 def _get_section_name(self, *names: str) -> str:
awgur@15 84 """\
awgur@15 85 Поучить имя текущего раздели или подраздела по заданному имени
awgur@15 86
awgur@15 87 :type names: str
awgur@15 88 :param names: Последовательные имена подразделов для получения доступа к глубокой иерархии
awgur@15 89 :returns: Имя подраздела
awgur@15 90 """
awgur@15 91 res = self.section if self.section is not None else ''
awgur@15 92 if not names:
awgur@15 93 return res
awgur@15 94
awgur@15 95 else:
awgur@15 96 if res:
awgur@15 97 res += '.'
awgur@15 98
awgur@15 99 return res + '.'.join(names)
awgur@15 100
awgur@15 101 def _get_part_by_path(self, *names: str):
awgur@15 102 """\
awgur@15 103 Получить конфигурационный элемент по заданному пути
awgur@15 104 """
awgur@15 105 if not names:
awgur@15 106 raise ValueError(f'Не заданно имя запрашиваемого раздела')
awgur@15 107
awgur@15 108 upper_path = names[:-1]
awgur@15 109 last_path_el = names[-1]
awgur@15 110 cur_level = self.d
awgur@15 111 cur_section = self._get_section_name()
awgur@15 112
awgur@15 113 for el in upper_path:
awgur@15 114 cur_section = el if not cur_section else f'{cur_section}.{el}'
awgur@15 115
awgur@15 116 if el in cur_level:
awgur@15 117 cur_level = cur_level[el]
awgur@15 118
awgur@15 119 else:
awgur@15 120 raise ConfigSectionNotFound(f'Раздел "{cur_section}" не найден')
awgur@15 121
awgur@15 122 cur_section = last_path_el if not cur_section else f'{cur_section}.{last_path_el}'
awgur@15 123 if last_path_el in cur_level:
awgur@15 124 return cur_level[last_path_el]
awgur@15 125
awgur@15 126 else:
awgur@15 127 raise ConfigSectionNotFound(f'Раздел "{cur_section}" не найден')
awgur@15 128
awgur@15 129
awgur@0 130 def get_section(self, name: str, mandatory: bool = True):
awgur@0 131 """\
awgur@0 132 Получить раздел конфигурационного файла из текущего или корневого раздела
awgur@15 133
awgur@0 134 :param name: имя раздела
awgur@0 135 :param mandatory: является ли раздел обязательным или он может отсутствовать в конфигурации. Во втором
awgur@0 136 случае, будет создан пустой словарь и использован в качестве данных раздела.
awgur@15 137 :returns: Экземпляр того-же класса, только для новой секции
awgur@0 138 """
awgur@15 139 section_name = self._get_section_name(name)
awgur@15 140 try:
awgur@15 141 section_val = self._get_part_by_path(name)
awgur@15 142
awgur@15 143 if not isinstance(section_val, dict):
awgur@15 144 raise ConfigFileError(f'Параметр конфигурации "{section_name}" не является разделом')
awgur@15 145
awgur@15 146 else:
awgur@15 147 return ConfigFileContent(file_data=section_val, section_name=f'{section_name}')
awgur@15 148
awgur@15 149 except ConfigSectionNotFound as e:
awgur@0 150 if mandatory:
awgur@15 151 raise e
awgur@15 152
awgur@15 153 else:
awgur@15 154 return ConfigFileContent(file_data={}, section_name=f'{section_name}')
awgur@15 155
awgur@15 156 def get_section_by_path(self, *names: str, mandatory: bool = True):
awgur@15 157 """\
awgur@15 158 Поучить секцию конфигурации по явно заданному пути
awgur@15 159
awgur@15 160 :type names: str
awgur@15 161 :param names: Имя секции на текущем уровне. Можно задавать несколько
awgur@15 162 для прохода по всей иерархии необходимой иерархии одним вызовом.
awgur@15 163
awgur@15 164 :type mandatory: bool
awgur@15 165 :param mandatory: Является ли искомая секция обязательной?
awgur@15 166
awgur@15 167 :returns: Экземпляр того-же класса, только для новой секции
awgur@15 168 """
awgur@15 169 section_name = self._get_section_name(*names)
awgur@15 170 try:
awgur@15 171 section_val = self._get_part_by_path(*names)
awgur@15 172
awgur@15 173 if not isinstance(section_val, dict):
awgur@15 174 raise ConfigFileError(f'Параметр конфигурации "{section_name}" не является разделом')
awgur@15 175
awgur@15 176 else:
awgur@15 177 return ConfigFileContent(file_data=section_val, section_name=f'{section_name}')
awgur@15 178
awgur@15 179 except ConfigSectionNotFound as e:
awgur@15 180 if mandatory:
awgur@15 181 raise e
awgur@0 182
awgur@0 183 else:
awgur@15 184 return ConfigFileContent(file_data={}, section_name=f'{section_name}')
awgur@15 185
awgur@15 186 def get_list_section(self, name: str, mandatory: bool = True):
awgur@15 187 """\
awgur@15 188 Для случая, когда раздел конфигурации задан массивом toml ([[${! SOME_SECTION !}]])
awgur@15 189
awgur@15 190 :type name: str
awgur@15 191 :param name: Имя раздела
awgur@15 192
awgur@15 193 :type mandatory: bool
awgur@15 194 :param mandatory: Данный раздел обязателен и не может отсутствовать
awgur@0 195
awgur@15 196 :returns: Спискол из экземпляров данного класса, нацеленные на элементы массива хэш-таблиц файла toml
awgur@15 197 """
awgur@15 198 section_name = self._get_section_name(name)
awgur@15 199 try:
awgur@15 200 section_val = self._get_part_by_path(name)
awgur@0 201
awgur@15 202 if isinstance(section_val, (list, tuple)):
awgur@15 203 res = []
awgur@15 204 for el in section_val:
awgur@15 205 if isinstance(el, dict):
awgur@15 206 res.append(ConfigFileContent(file_data=el, section_name=f'{section_name}'))
awgur@15 207
awgur@15 208 else:
awgur@15 209 raise ConfigFileError(f'Параметр конфигурации "{section_name}"'
awgur@15 210 f' со значением "{el}"'
awgur@15 211 f' не является разделом')
awgur@15 212 return res
awgur@15 213
awgur@15 214 elif isinstance(section_val, dict):
awgur@15 215 return [
awgur@15 216 ConfigFileContent(file_data=section_val, section_name=f'{section_name}')
awgur@15 217 ]
awgur@0 218
awgur@0 219 else:
awgur@15 220 raise ConfigFileError(f'Параметр конфигурации "{section_name}" не является разделом')
awgur@15 221
awgur@15 222 except ConfigSectionNotFound as e:
awgur@15 223 if mandatory:
awgur@15 224 raise e
awgur@15 225
awgur@15 226 else:
awgur@15 227 return [
awgur@15 228 ConfigFileContent(file_data={}, section_name=f'{section_name}')
awgur@15 229 ]
awgur@0 230
awgur@0 231
awgur@0 232 class ConfigFile(object):
awgur@0 233 """\
awgur@0 234 Работа с самим файлом конфигурации
awgur@0 235 """
awgur@0 236 def __init__(self, file_name: str):
awgur@0 237 if not exists(file_name):
awgur@0 238 raise ConfigFileError(f'Файл конфигурации "{file_name}" не существует')
awgur@0 239
awgur@0 240 self.file_name = file_name
awgur@0 241
awgur@0 242 def get(self):
awgur@0 243 """\
awgur@0 244 Производим разбор файла конфигурации и создаём корневой раздел конфигурации
awgur@0 245 """
awgur@3 246 try:
awgur@3 247 return ConfigFileContent(toml.load(self.file_name))
awgur@3 248
awgur@3 249 except OSError as e:
awgur@3 250 raise ConfigFileError(f'Ошибка чтения файла "{self.file_name}": {e}')
awgur@3 251
awgur@3 252 except toml.TomlDecodeError as e:
awgur@3 253 raise ConfigFileError(f'Ошибка в разборе файла конфигурации: {e}')
awgur@3 254
awgur@3 255 except ValueError as e:
awgur@3 256 raise ConfigFileError(f'Ошибка разбора файла конфигурации: {ConfigFileError.fmt_error(e)}')
awgur@0 257
awgur@0 258 def __exit__(self, exc_type, exc_val, exc_tb):
awgur@0 259 pass
awgur@0 260
awgur@0 261 def __enter__(self):
awgur@0 262 return self.get()