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