py.lib
2022-08-19
Parent:f1a05e880961
py.lib/config_parse_helper.py
. Исправление ошибок и рефакторинг
| awgur@31 | 1 # coding: utf-8 |
| awgur@31 | 2 from configparser import ConfigParser |
| awgur@31 | 3 from dataclasses import is_dataclass, fields, Field, MISSING |
| awgur@36 | 4 from typing import Iterable, Optional, Dict, Any, List, Callable, Union |
| awgur@31 | 5 from threading import RLock |
| awgur@31 | 6 from os import getenv |
| awgur@31 | 7 |
| awgur@31 | 8 |
| awgur@31 | 9 class ConfigParseHelperError(Exception): |
| awgur@31 | 10 """\ |
| awgur@31 | 11 Ошибки в модуле помощника разборщика конфигураций |
| awgur@31 | 12 """ |
| awgur@31 | 13 |
| awgur@31 | 14 |
| awgur@31 | 15 class NoSectionNotification(ConfigParseHelperError): |
| awgur@31 | 16 """\ |
| awgur@31 | 17 Оповещение об отсутствующей секции конфигурации |
| awgur@31 | 18 """ |
| awgur@31 | 19 |
| awgur@31 | 20 |
| awgur@36 | 21 class TypeDescriber: |
| awgur@33 | 22 """\ |
| awgur@36 | 23 Реализует паттерн "адаптер" поверх типов, для упрощения приведения значений типов к объявленным формам |
| awgur@33 | 24 """ |
| awgur@36 | 25 def __init__(self, name, cast, like, is_complex=False): |
| awgur@36 | 26 self.__name__ = name |
| awgur@36 | 27 self.cast = cast |
| awgur@36 | 28 self.like = like |
| awgur@36 | 29 self.is_complex = is_complex |
| awgur@36 | 30 |
| awgur@37 | 31 def check(self, instance): |
| awgur@36 | 32 if self.like is None: |
| awgur@36 | 33 return False |
| awgur@33 | 34 |
| awgur@36 | 35 else: |
| awgur@37 | 36 return check_instance(instance, self.like) |
| awgur@33 | 37 |
| awgur@36 | 38 def __repr__(self): |
| awgur@36 | 39 return f'<TypeDescriber({self.__name__}, {self.like})>' |
| awgur@36 | 40 |
| awgur@36 | 41 def __call__(self, val): |
| awgur@36 | 42 if val is None: |
| awgur@36 | 43 return None |
| awgur@33 | 44 |
| awgur@37 | 45 elif not self.is_complex and check_instance(val, self.like): |
| awgur@37 | 46 return val |
| awgur@37 | 47 |
| awgur@33 | 48 else: |
| awgur@37 | 49 return self.cast(val) |
| awgur@37 | 50 |
| awgur@37 | 51 |
| awgur@37 | 52 def check_instance(obj, types): |
| awgur@37 | 53 if types is None: |
| awgur@37 | 54 return False |
| awgur@37 | 55 |
| awgur@37 | 56 elif isinstance(types, (tuple, list)): |
| awgur@37 | 57 _flag = False |
| awgur@37 | 58 for t in types: |
| awgur@37 | 59 if check_instance(obj, t): |
| awgur@37 | 60 _flag = True |
| awgur@37 | 61 break |
| awgur@37 | 62 |
| awgur@37 | 63 return _flag |
| awgur@37 | 64 |
| awgur@37 | 65 elif isinstance(types, TypeDescriber): |
| awgur@37 | 66 return types.check(obj) |
| awgur@37 | 67 |
| awgur@37 | 68 else: |
| awgur@37 | 69 return isinstance(obj, types) |
| awgur@36 | 70 |
| awgur@36 | 71 |
| awgur@36 | 72 def cast_iterator(t: Union[type, TypeDescriber], lst: Iterable): |
| awgur@36 | 73 """\ |
| awgur@36 | 74 Обрабатывает последовательности единого типа. |
| awgur@36 | 75 """ |
| awgur@36 | 76 for i in lst: |
| awgur@37 | 77 if check_instance(i, t): |
| awgur@36 | 78 yield i |
| awgur@36 | 79 |
| awgur@37 | 80 else: |
| awgur@37 | 81 try: |
| awgur@37 | 82 yield t(i) |
| awgur@36 | 83 |
| awgur@37 | 84 except (TypeError, ValueError) as e: |
| awgur@37 | 85 raise ValueError(f'Не удалось привести значение к нужному типу: ' |
| awgur@37 | 86 f'тип={t.__name__}; знач={i}') |
| awgur@36 | 87 |
| awgur@36 | 88 |
| awgur@36 | 89 def multi_item_tuple(tt, val): |
| awgur@36 | 90 """\ |
| awgur@36 | 91 Обрабатывает кортежи, состоящие из нескольких значений типов (кортежи элементов разных типов) |
| awgur@36 | 92 :param tt: Последовательность составляющих кортеж типов |
| awgur@36 | 93 :param val: итерируемый объект, хранящий значения в указанном порядке. |
| awgur@36 | 94 """ |
| awgur@36 | 95 val = list(val) |
| awgur@36 | 96 t_len = len(tt) |
| awgur@36 | 97 if t_len == 0: |
| awgur@36 | 98 raise ValueError('При вызове процедуры конвертации котежей, не были указаны типы') |
| awgur@36 | 99 |
| awgur@36 | 100 if len(val) != t_len: |
| awgur@36 | 101 raise ValueError(f'Значение не содержит положенных {t_len} элементов: {len(val)} - {val}') |
| awgur@36 | 102 |
| awgur@36 | 103 res = [] |
| awgur@36 | 104 |
| awgur@36 | 105 for i in range(t_len): |
| awgur@37 | 106 if check_instance(val[i], tt[i]): |
| awgur@37 | 107 res.append(val[i]) |
| awgur@36 | 108 |
| awgur@37 | 109 else: |
| awgur@37 | 110 try: |
| awgur@37 | 111 res.append(tt[i](val[i])) |
| awgur@36 | 112 |
| awgur@37 | 113 except (TypeError, ValueError) as e: |
| awgur@37 | 114 raise ValueError(f'Не удалось привести значение к нужному типу: ' |
| awgur@37 | 115 f'тип={tt[i].__name__}; знач={val[i]}') |
| awgur@36 | 116 |
| awgur@36 | 117 return tuple(res) |
| awgur@36 | 118 |
| awgur@33 | 119 |
| awgur@36 | 120 def union_processor(tt, val): |
| awgur@36 | 121 """\ |
| awgur@36 | 122 Пытается привести значение к одному из указанных типов |
| awgur@36 | 123 :param tt: список возможных типов |
| awgur@36 | 124 :param val: приводимое значение |
| awgur@36 | 125 """ |
| awgur@36 | 126 if val is None: |
| awgur@36 | 127 return val |
| awgur@36 | 128 |
| awgur@36 | 129 res = None |
| awgur@36 | 130 ex = [] |
| awgur@36 | 131 |
| awgur@36 | 132 if len(tt) == 0: |
| awgur@36 | 133 raise ValueError('Не указан ни один тип в составном типе Union') |
| awgur@36 | 134 |
| awgur@37 | 135 sorted_types_begin = [ t for t in tt if t in (int, float, bool)] |
| awgur@37 | 136 sorted_types_body = [ t for t in tt if t not in (int, float, bool, str)] |
| awgur@37 | 137 sorted_types_end = [ t for t in tt if t == str] |
| awgur@37 | 138 |
| awgur@37 | 139 for t in sorted_types_begin + sorted_types_body + sorted_types_end: |
| awgur@37 | 140 _t = get_type_describer(t) |
| awgur@36 | 141 try: |
| awgur@37 | 142 res = _t(val) |
| awgur@36 | 143 break |
| awgur@36 | 144 |
| awgur@36 | 145 except (TypeError, ValueError) as e: |
| awgur@36 | 146 ex.append(f'{t}: {e}') |
| awgur@36 | 147 |
| awgur@36 | 148 if res is None: |
| awgur@36 | 149 raise ValueError('Не удалось привести значение не к одному из типов:\n' + '\n'.join(ex)) |
| awgur@36 | 150 |
| awgur@36 | 151 else: |
| awgur@36 | 152 return res |
| awgur@36 | 153 |
| awgur@36 | 154 |
| awgur@36 | 155 def dict_processor(tt, val): |
| awgur@36 | 156 if len(tt) != 2: |
| awgur@36 | 157 raise ValueError(f'Попытка воссоздать словарь со странным количеством аргументов типа: {tt}') |
| awgur@36 | 158 |
| awgur@36 | 159 try: |
| awgur@36 | 160 _d = dict(val) |
| awgur@36 | 161 |
| awgur@36 | 162 except (TypeError, ValueError) as e: |
| awgur@36 | 163 raise ValueError(f'Не удалось воссоздать словарь из представленного значения: {e}') |
| awgur@36 | 164 |
| awgur@36 | 165 _p = [] |
| awgur@36 | 166 |
| awgur@36 | 167 for k, v in _d.items(): |
| awgur@36 | 168 try: |
| awgur@36 | 169 _p.append((tt[0](k), tt[1](v))) |
| awgur@36 | 170 |
| awgur@36 | 171 except (TypeError, ValueError) as e: |
| awgur@36 | 172 raise ValueError(f'Не удалось привести значения элемента словаря к требуемому типу: ' |
| awgur@36 | 173 f'key="{k}" value="{v}"') |
| awgur@33 | 174 |
| awgur@36 | 175 return dict(_p) |
| awgur@36 | 176 |
| awgur@36 | 177 |
| awgur@36 | 178 def get_type_describer(t) -> TypeDescriber: |
| awgur@36 | 179 if type(t).__name__ == '_GenericAlias': |
| awgur@36 | 180 try: |
| awgur@36 | 181 _args = t.__args__ |
| awgur@36 | 182 |
| awgur@36 | 183 except AttributeError: |
| awgur@36 | 184 raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного ' |
| awgur@36 | 185 f'типа "_GenericAlias": {t}') |
| awgur@36 | 186 |
| awgur@36 | 187 if t.__name__ == 'List': |
| awgur@36 | 188 try: |
| awgur@36 | 189 _t = _args[0] |
| awgur@36 | 190 |
| awgur@36 | 191 except IndexError: |
| awgur@36 | 192 raise ValueError(f'Тип {t} не содержит в себе типа своих элементов') |
| awgur@36 | 193 |
| awgur@36 | 194 return TypeDescriber( |
| awgur@36 | 195 name=f'{t.__name__}[{_t.__name__}]', |
| awgur@36 | 196 cast=lambda x: list(cast_iterator(get_type_describer(_t).cast, x)), |
| awgur@36 | 197 like=list, |
| awgur@36 | 198 is_complex=True |
| awgur@36 | 199 ) |
| awgur@36 | 200 |
| awgur@36 | 201 elif t.__name__ == 'Tuple': |
| awgur@36 | 202 if not _args: |
| awgur@36 | 203 raise ValueError(f'Тип {t} не содержит в себе пояснений по составу хранящихся в нём типов') |
| awgur@36 | 204 |
| awgur@36 | 205 if len(_args) == 1: |
| awgur@36 | 206 _t = _args[0] |
| awgur@36 | 207 |
| awgur@36 | 208 return TypeDescriber( |
| awgur@36 | 209 name=f'Tuple[{_t.__name__}]', |
| awgur@36 | 210 cast=lambda x: list(cast_iterator(get_type_describer(_t).cast, x)), |
| awgur@36 | 211 like=tuple, |
| awgur@36 | 212 is_complex=True |
| awgur@36 | 213 ) |
| awgur@36 | 214 |
| awgur@36 | 215 else: |
| awgur@36 | 216 _name = ', '.join(map(lambda x: x.__name__, _args)) |
| awgur@36 | 217 _cast_args = tuple(get_type_describer(i) for i in _args) |
| awgur@33 | 218 |
| awgur@36 | 219 return TypeDescriber( |
| awgur@36 | 220 name=f'Tuple[{_name}]', |
| awgur@36 | 221 cast=lambda x: multi_item_tuple(_cast_args, x), |
| awgur@36 | 222 like=tuple, |
| awgur@36 | 223 is_complex=True |
| awgur@36 | 224 ) |
| awgur@36 | 225 |
| awgur@36 | 226 elif t.__name__ == 'Dict': |
| awgur@36 | 227 if len(_args) != 2: |
| awgur@36 | 228 raise ValueError(f'Неожиданное количество значений типа в составном типе Dict: ' |
| awgur@36 | 229 f'{len(_args)} values=({_args})') |
| awgur@36 | 230 |
| awgur@36 | 231 _name = ', '.join(map(lambda x: x.__name__, _args)) |
| awgur@36 | 232 |
| awgur@36 | 233 return TypeDescriber( |
| awgur@36 | 234 name=f'Dict[{_name}]', |
| awgur@36 | 235 cast=lambda x: dict_processor(_args, x), |
| awgur@36 | 236 like=dict, |
| awgur@36 | 237 is_complex=True |
| awgur@36 | 238 ) |
| awgur@36 | 239 |
| awgur@36 | 240 else: |
| awgur@36 | 241 raise ValueError(f'Неизвестный представитель типа "_GenericAlias": {t.__name__}') |
| awgur@33 | 242 |
| awgur@36 | 243 elif type(t).__name__ == '_UnionGenericAlias': |
| awgur@36 | 244 if t.__name__ not in ('Union', 'Optional'): |
| awgur@36 | 245 raise TypeError(f'Неизвестный подтип _UnionGenericAlias: {t.__name__}') |
| awgur@36 | 246 |
| awgur@36 | 247 try: |
| awgur@36 | 248 _args = t.__args__ |
| awgur@36 | 249 |
| awgur@36 | 250 except AttributeError: |
| awgur@36 | 251 raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного ' |
| awgur@36 | 252 f'типа "_UnionGenericAlias": {t}') |
| awgur@36 | 253 |
| awgur@36 | 254 if len(_args) == 0: |
| awgur@36 | 255 raise ValueError('Не указан ни один тип в конструкции Union') |
| awgur@33 | 256 |
| awgur@36 | 257 _cast_args = tuple(map(get_type_describer, _args)) |
| awgur@36 | 258 _args_name = ', '.join(map(lambda x: x.__name__, _args)) |
| awgur@33 | 259 |
| awgur@36 | 260 return TypeDescriber( |
| awgur@36 | 261 name=f'Union[{_args_name}]', |
| awgur@36 | 262 cast=lambda x: union_processor(_cast_args, x), |
| awgur@36 | 263 like=None |
| awgur@36 | 264 ) |
| awgur@36 | 265 |
| awgur@36 | 266 else: |
| awgur@36 | 267 return TypeDescriber( |
| awgur@36 | 268 name=t.__name__, |
| awgur@36 | 269 cast=t, |
| awgur@36 | 270 like=t |
| awgur@36 | 271 ) |
| awgur@33 | 272 |
| awgur@33 | 273 |
| awgur@31 | 274 class CPHSectionBase: |
| awgur@31 | 275 """\ |
| awgur@31 | 276 Базовый класс обработки секции конфигурационного файла |
| awgur@31 | 277 """ |
| awgur@31 | 278 |
| awgur@31 | 279 def get(self, config_prop_name: str, dc_prop_name: str): |
| awgur@31 | 280 """\ |
| awgur@31 | 281 Получить свойство из конфигурационного файла |
| awgur@31 | 282 """ |
| awgur@31 | 283 raise NotImplemented() |
| awgur@31 | 284 |
| awgur@31 | 285 def __enter__(self): |
| awgur@31 | 286 return self |
| awgur@31 | 287 |
| awgur@31 | 288 def __exit__(self, exc_type, exc_val, exc_tb): |
| awgur@31 | 289 raise NotImplemented() |
| awgur@31 | 290 |
| awgur@31 | 291 |
| awgur@31 | 292 class CPHAbsentSection(CPHSectionBase): |
| awgur@31 | 293 """\ |
| awgur@31 | 294 Класс создаваемый на отсутствующую секцию конфигурационного файла |
| awgur@31 | 295 """ |
| awgur@31 | 296 def get(self, config_prop_name: str, dc_prop_name: str): |
| awgur@31 | 297 raise NoSectionNotification() |
| awgur@31 | 298 |
| awgur@31 | 299 def __exit__(self, exc_type, exc_val, exc_tb): |
| awgur@31 | 300 if exc_type == NoSectionNotification: |
| awgur@31 | 301 return True |
| awgur@31 | 302 |
| awgur@31 | 303 |
| awgur@31 | 304 class CPHParamGetter(CPHSectionBase): |
| awgur@31 | 305 def __init__(self, parser_helper_object): |
| awgur@31 | 306 self.ph = parser_helper_object |
| awgur@31 | 307 self.params = {} |
| awgur@31 | 308 |
| awgur@34 | 309 def _add_param(self, param_name: str, param_val: Any, parser: Optional[Callable[[Any], Any]] = None): |
| awgur@31 | 310 """\ |
| awgur@31 | 311 Непосредственное добавление полученного параметра со всеми проверками. |
| awgur@31 | 312 """ |
| awgur@31 | 313 |
| awgur@34 | 314 if parser is not None and param_val is not None: |
| awgur@34 | 315 param_val = parser(param_val) |
| awgur@34 | 316 |
| awgur@31 | 317 fld = self.ph.fields.get(param_name) |
| awgur@31 | 318 if not isinstance(fld, Field): |
| awgur@31 | 319 raise ConfigParseHelperError(f'В классе данных отсутствует свойство "{param_name}", ' |
| awgur@31 | 320 f'которое мы должны заполнить из параметра конфигурации: {fld}') |
| awgur@31 | 321 |
| awgur@31 | 322 if param_val is not None: |
| awgur@36 | 323 type_desc = get_type_describer(fld.type) |
| awgur@31 | 324 try: |
| awgur@36 | 325 res = type_desc(param_val) |
| awgur@31 | 326 |
| awgur@31 | 327 except (ValueError, TypeError) as e: |
| awgur@36 | 328 raise ConfigParseHelperError(f'При приведении параметра к ' |
| awgur@36 | 329 f'заданному типу произошла ошибка: ' |
| awgur@36 | 330 f'значение="{param_val}" ошибка="{e}"') |
| awgur@31 | 331 |
| awgur@31 | 332 else: |
| awgur@31 | 333 if fld.default is not MISSING: |
| awgur@31 | 334 res = fld.default |
| awgur@31 | 335 |
| awgur@31 | 336 elif fld.default_factory is not MISSING: |
| awgur@31 | 337 res = fld.default_factory() |
| awgur@31 | 338 |
| awgur@31 | 339 else: |
| awgur@31 | 340 raise ConfigParseHelperError('В конфигурации не заданна обязательная опция') |
| awgur@31 | 341 |
| awgur@31 | 342 self.params[param_name] = res |
| awgur@31 | 343 |
| awgur@31 | 344 def __exit__(self, exc_type, exc_val, exc_tb): |
| awgur@31 | 345 if exc_type is None: |
| awgur@31 | 346 self.ph.add_params(self.params) |
| awgur@31 | 347 |
| awgur@34 | 348 def get(self, config_prop_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None): |
| awgur@31 | 349 raise NoSectionNotification() |
| awgur@31 | 350 |
| awgur@31 | 351 |
| awgur@31 | 352 class CPHSection(CPHParamGetter): |
| awgur@31 | 353 """\ |
| awgur@31 | 354 Класс производящий разбор конкретной секции конфигурации |
| awgur@31 | 355 """ |
| awgur@31 | 356 |
| awgur@31 | 357 def __init__(self, parser_helper_object, section: str): |
| awgur@31 | 358 super().__init__(parser_helper_object) |
| awgur@31 | 359 self.section_name = section |
| awgur@31 | 360 self.section = parser_helper_object.conf_parser[section] |
| awgur@31 | 361 |
| awgur@34 | 362 def get(self, config_prop_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None): |
| awgur@31 | 363 """\ |
| awgur@31 | 364 :param config_prop_name: Имя опции в файле конфигурации |
| awgur@31 | 365 :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию |
| awgur@34 | 366 :param parser: Исполнимый обработчик значения, перед его помещением в конфигурацию |
| awgur@31 | 367 """ |
| awgur@31 | 368 try: |
| awgur@34 | 369 self._add_param(dc_prop_name, self.section.get(config_prop_name), parser) |
| awgur@31 | 370 |
| awgur@31 | 371 except ConfigParseHelperError as e: |
| awgur@31 | 372 raise ConfigParseHelperError(f'Ошибка при разборе параметра "{config_prop_name}" ' |
| awgur@31 | 373 f'в секции "{self.section_name}": {e}') |
| awgur@31 | 374 |
| awgur@31 | 375 |
| awgur@31 | 376 class CPHEnvParser(CPHParamGetter): |
| awgur@31 | 377 """\ |
| awgur@31 | 378 Класс для разбора переменных окружения в том же ключе, что и файла конфигурации |
| awgur@31 | 379 """ |
| awgur@31 | 380 |
| awgur@34 | 381 def get(self, env_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None): |
| awgur@31 | 382 """\ |
| awgur@31 | 383 :param env_name: Имя переменной окружения, хранящей опцию |
| awgur@31 | 384 :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию |
| awgur@34 | 385 :param parser: Исполнимый обработчик значения, перед его помещением в конфигурацию |
| awgur@31 | 386 """ |
| awgur@31 | 387 |
| awgur@31 | 388 try: |
| awgur@34 | 389 self._add_param(dc_prop_name, getenv(env_name), parser) |
| awgur@31 | 390 |
| awgur@31 | 391 except ConfigParseHelperError as e: |
| awgur@31 | 392 raise ConfigParseHelperError(f'Ошибка при получении значения из переменной окружения "{env_name}": {e}') |
| awgur@31 | 393 |
| awgur@31 | 394 |
| awgur@32 | 395 class CPHObjectsListGetter: |
| awgur@32 | 396 """\ |
| awgur@32 | 397 Помощник для случаев, когда в наборе секций хранится однотипный набор объектов |
| awgur@32 | 398 """ |
| awgur@32 | 399 def __init__(self, config_object_class: type, config_parser: ConfigParser, sections: Iterable[str]): |
| awgur@32 | 400 self.sections = sections |
| awgur@32 | 401 self.conf_parser = config_parser |
| awgur@32 | 402 |
| awgur@32 | 403 if not is_dataclass(config_object_class): |
| awgur@32 | 404 raise ConfigParseHelperError(f'Представленный в качестве представления объекта конфигурации ' |
| awgur@32 | 405 f'класс не является классом данных: {config_object_class.__name__}') |
| awgur@32 | 406 |
| awgur@32 | 407 self.res_obj = config_object_class |
| awgur@32 | 408 self.fields = dict((fld.name, fld) for fld in fields(config_object_class)) |
| awgur@32 | 409 self.obj_list = [] |
| awgur@32 | 410 self.ident_list = [] |
| awgur@32 | 411 |
| awgur@32 | 412 def add_params(self, params: Dict[str, Any]): |
| awgur@32 | 413 try: |
| awgur@32 | 414 self.obj_list.append(self.res_obj(**params)) |
| awgur@32 | 415 |
| awgur@32 | 416 except (ValueError, TypeError) as e: |
| awgur@32 | 417 raise ConfigParseHelperError(f'Ошибка создания объекта объекта конфигурации, ' |
| awgur@32 | 418 f'списка объектов конфигурации: {e}') |
| awgur@32 | 419 |
| awgur@34 | 420 def get(self, config_prop_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None): |
| awgur@32 | 421 """\ |
| awgur@32 | 422 Подготавливаем список соответствия названий параметров в секции конкретным свойствам данного |
| awgur@32 | 423 в помощник класса |
| awgur@32 | 424 |
| awgur@32 | 425 :param config_prop_name: Имя опции в файле конфигурации |
| awgur@32 | 426 :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию |
| awgur@34 | 427 :param parser: Исполнимый обработчик значения, перед его помещением в конфигурацию |
| awgur@32 | 428 """ |
| awgur@32 | 429 |
| awgur@34 | 430 self.ident_list.append((config_prop_name, dc_prop_name, parser)) |
| awgur@32 | 431 |
| awgur@32 | 432 def get_config_objects(self) -> List[object]: |
| awgur@32 | 433 for section in self.sections: |
| awgur@32 | 434 try: |
| awgur@32 | 435 with CPHSection(self, section) as section_helper: |
| awgur@34 | 436 for conf_prop, dc_prop, parser in self.ident_list: |
| awgur@34 | 437 section_helper.get(conf_prop, dc_prop, parser) |
| awgur@32 | 438 |
| awgur@32 | 439 except ConfigParseHelperError as e: |
| awgur@32 | 440 raise ConfigParseHelperError(f'Ошибка при разборе секции "{section}": {e}') |
| awgur@32 | 441 |
| awgur@32 | 442 res = self.obj_list[:] |
| awgur@32 | 443 self.obj_list.clear() |
| awgur@32 | 444 |
| awgur@32 | 445 return res |
| awgur@32 | 446 |
| awgur@32 | 447 |
| awgur@31 | 448 class ConfigParseHelper: |
| awgur@32 | 449 """\ |
| awgur@32 | 450 Помощник разбора конфигурации |
| awgur@32 | 451 """ |
| awgur@31 | 452 def __init__(self, config_object_class: type, required_sections: Optional[Iterable[str]] = None): |
| awgur@31 | 453 """\ |
| awgur@31 | 454 :param config_object_class: Dataclass, который мы подготовили как хранилище параметров конфигурации |
| awgur@31 | 455 :param required_sections: Перечисление секций конфигурации, которые мы требуем, чтобы были в файле |
| awgur@31 | 456 """ |
| awgur@31 | 457 |
| awgur@31 | 458 if required_sections is not None: |
| awgur@31 | 459 self.req_sections = set(required_sections) |
| awgur@31 | 460 |
| awgur@31 | 461 else: |
| awgur@31 | 462 self.req_sections = set() |
| awgur@31 | 463 |
| awgur@31 | 464 if not is_dataclass(config_object_class): |
| awgur@31 | 465 raise ConfigParseHelperError(f'Представленный в качестве объекта конфигурации класс не является ' |
| awgur@31 | 466 f'классом данных: {config_object_class.__name__}') |
| awgur@31 | 467 |
| awgur@31 | 468 self.res_obj = config_object_class |
| awgur@31 | 469 self.fields = dict((fld.name, fld) for fld in fields(config_object_class)) |
| awgur@31 | 470 self.conf_parser: Optional[ConfigParser] = None |
| awgur@31 | 471 self.config_params = {} |
| awgur@31 | 472 self.config_params_lock = RLock() |
| awgur@31 | 473 |
| awgur@31 | 474 def add_params(self, params: Dict[str, Any]): |
| awgur@31 | 475 self.config_params_lock.acquire() |
| awgur@31 | 476 try: |
| awgur@31 | 477 self.config_params.update(params) |
| awgur@31 | 478 |
| awgur@31 | 479 finally: |
| awgur@31 | 480 self.config_params_lock.release() |
| awgur@31 | 481 |
| awgur@31 | 482 def section(self, section_name: str) -> CPHSectionBase: |
| awgur@31 | 483 if self.conf_parser is None: |
| awgur@31 | 484 raise ConfigParseHelperError(f'Прежде чем приступать к разбору файла конфигурации стоит его загрузить') |
| awgur@31 | 485 |
| awgur@31 | 486 if self.conf_parser.has_section(section_name): |
| awgur@31 | 487 return CPHSection(self, section_name) |
| awgur@31 | 488 |
| awgur@31 | 489 else: |
| awgur@31 | 490 return CPHAbsentSection() |
| awgur@31 | 491 |
| awgur@31 | 492 def load(self, filename: str): |
| awgur@31 | 493 res = ConfigParser() |
| awgur@31 | 494 try: |
| awgur@31 | 495 res.read(filename) |
| awgur@31 | 496 |
| awgur@31 | 497 except (TypeError, IOError, OSError, ValueError) as e: |
| awgur@31 | 498 raise ConfigParseHelperError(f'Не удалось загрузить файл конфигурации: файл="{filename}" ' |
| awgur@31 | 499 f'ошибка="{e}"') |
| awgur@31 | 500 |
| awgur@31 | 501 missing_sections = self.req_sections - set(res.sections()) |
| awgur@31 | 502 |
| awgur@31 | 503 if missing_sections: |
| awgur@31 | 504 missing_sections = ', '.join(missing_sections) |
| awgur@31 | 505 raise ConfigParseHelperError(f'В конфигурационном файле отсутствуют секции: {missing_sections}') |
| awgur@31 | 506 |
| awgur@31 | 507 self.conf_parser = res |
| awgur@31 | 508 |
| awgur@31 | 509 def get_config(self): |
| awgur@31 | 510 try: |
| awgur@31 | 511 return self.res_obj(**self.config_params) |
| awgur@31 | 512 |
| awgur@31 | 513 except (ValueError, TypeError) as e: |
| awgur@31 | 514 raise ConfigParseHelperError(f'Не удалось инициализировать объект конфигурации: {e}') |
| awgur@32 | 515 |
| awgur@32 | 516 def get_sections(self): |
| awgur@32 | 517 return self.conf_parser.sections() |