py.lib
33:57f63bf31fd8
Go to Latest
py.lib/config_parse_helper.py
* С разбегу влетел в type hinting, пришлось обкладывать костылями. И этот процесс судя по всему длительный, и место больное будет долго...
2 from configparser import ConfigParser
3 from dataclasses import is_dataclass, fields, Field, MISSING
4 from typing import Iterable, Optional, Dict, Any, List
5 from threading import RLock
9 class ConfigParseHelperError(Exception):
11 Ошибки в модуле помощника разборщика конфигураций
15 class NoSectionNotification(ConfigParseHelperError):
17 Оповещение об отсутствующей секции конфигурации
21 def difficult_type_recognizer(type_obj, value):
23 Магия борющаяся с костылями type hinting
25 if type(type_obj).__name__ == '_GenericAlias':
27 hasattr(type_obj, '__args__')
28 and type(type_obj.__args__) == tuple
31 if type_obj.__name__ == 'List':
32 return list(map(type_obj.__args__[0], value))
34 elif type_obj.__name__ == 'Tuple':
35 return tuple(map(type_obj.__args__[0], value))
38 raise ValueError('Неизвестный тип')
41 ValueError('Неизвестный тип')
43 elif type(type_obj).__name__ == '_UnionGenericAlias':
44 if type_obj.__name__ not in ('Union', 'Optional'):
45 raise TypeError(f'Неизвестный подтип _UnionGenericAlias: {type_obj.__name__}')
48 hasattr(type_obj, '__args__')
49 and type(type_obj.__args__) == tuple
52 raise TypeError(f'Не ясно как работать с типом не вижу аргументов: {type_obj.__name__}')
54 for _t in type_obj.__args__:
55 if _t.__name__ == 'NoneType':
61 except (TypeError, ValueError):
64 raise ValueError('Не удалось привести значение к типу')
69 Базовый класс обработки секции конфигурационного файла
72 def get(self, config_prop_name: str, dc_prop_name: str):
74 Получить свойство из конфигурационного файла
76 raise NotImplemented()
81 def __exit__(self, exc_type, exc_val, exc_tb):
82 raise NotImplemented()
85 class CPHAbsentSection(CPHSectionBase):
87 Класс создаваемый на отсутствующую секцию конфигурационного файла
89 def get(self, config_prop_name: str, dc_prop_name: str):
90 raise NoSectionNotification()
92 def __exit__(self, exc_type, exc_val, exc_tb):
93 if exc_type == NoSectionNotification:
97 class CPHParamGetter(CPHSectionBase):
98 def __init__(self, parser_helper_object):
99 self.ph = parser_helper_object
102 def _add_param(self, param_name: str, param_val: Any):
104 Непосредственное добавление полученного параметра со всеми проверками.
107 fld = self.ph.fields.get(param_name)
108 if not isinstance(fld, Field):
109 raise ConfigParseHelperError(f'В классе данных отсутствует свойство "{param_name}", '
110 f'которое мы должны заполнить из параметра конфигурации: {fld}')
112 if param_val is not None:
114 res = fld.type(param_val)
116 except (ValueError, TypeError) as e:
118 res = difficult_type_recognizer(fld.type, param_val)
120 except (ValueError, TypeError):
121 raise ConfigParseHelperError(f'При приведении параметра к '
122 f'заданному типу произошла ошибка: '
123 f'значение="{param_val}" ошибка="{e}"')
126 if fld.default is not MISSING:
129 elif fld.default_factory is not MISSING:
130 res = fld.default_factory()
133 raise ConfigParseHelperError('В конфигурации не заданна обязательная опция')
135 self.params[param_name] = res
137 def __exit__(self, exc_type, exc_val, exc_tb):
139 self.ph.add_params(self.params)
141 def get(self, config_prop_name: str, dc_prop_name: str):
142 raise NoSectionNotification()
145 class CPHSection(CPHParamGetter):
147 Класс производящий разбор конкретной секции конфигурации
150 def __init__(self, parser_helper_object, section: str):
151 super().__init__(parser_helper_object)
152 self.section_name = section
153 self.section = parser_helper_object.conf_parser[section]
155 def get(self, config_prop_name: str, dc_prop_name: str):
157 :param config_prop_name: Имя опции в файле конфигурации
158 :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию
161 self._add_param(dc_prop_name, self.section.get(config_prop_name))
163 except ConfigParseHelperError as e:
164 raise ConfigParseHelperError(f'Ошибка при разборе параметра "{config_prop_name}" '
165 f'в секции "{self.section_name}": {e}')
168 class CPHEnvParser(CPHParamGetter):
170 Класс для разбора переменных окружения в том же ключе, что и файла конфигурации
173 def get(self, env_name: str, dc_prop_name: str):
175 :param env_name: Имя переменной окружения, хранящей опцию
176 :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию
180 self._add_param(dc_prop_name, getenv(env_name))
182 except ConfigParseHelperError as e:
183 raise ConfigParseHelperError(f'Ошибка при получении значения из переменной окружения "{env_name}": {e}')
186 class CPHObjectsListGetter:
188 Помощник для случаев, когда в наборе секций хранится однотипный набор объектов
190 def __init__(self, config_object_class: type, config_parser: ConfigParser, sections: Iterable[str]):
191 self.sections = sections
192 self.conf_parser = config_parser
194 if not is_dataclass(config_object_class):
195 raise ConfigParseHelperError(f'Представленный в качестве представления объекта конфигурации '
196 f'класс не является классом данных: {config_object_class.__name__}')
198 self.res_obj = config_object_class
199 self.fields = dict((fld.name, fld) for fld in fields(config_object_class))
203 def add_params(self, params: Dict[str, Any]):
205 self.obj_list.append(self.res_obj(**params))
207 except (ValueError, TypeError) as e:
208 raise ConfigParseHelperError(f'Ошибка создания объекта объекта конфигурации, '
209 f'списка объектов конфигурации: {e}')
211 def get(self, config_prop_name: str, dc_prop_name: str):
213 Подготавливаем список соответствия названий параметров в секции конкретным свойствам данного
216 :param config_prop_name: Имя опции в файле конфигурации
217 :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию
220 self.ident_list.append((config_prop_name, dc_prop_name))
222 def get_config_objects(self) -> List[object]:
223 for section in self.sections:
225 with CPHSection(self, section) as section_helper:
226 for conf_prop, dc_prop in self.ident_list:
227 section_helper.get(conf_prop, dc_prop)
229 except ConfigParseHelperError as e:
230 raise ConfigParseHelperError(f'Ошибка при разборе секции "{section}": {e}')
232 res = self.obj_list[:]
233 self.obj_list.clear()
238 class ConfigParseHelper:
240 Помощник разбора конфигурации
242 def __init__(self, config_object_class: type, required_sections: Optional[Iterable[str]] = None):
244 :param config_object_class: Dataclass, который мы подготовили как хранилище параметров конфигурации
245 :param required_sections: Перечисление секций конфигурации, которые мы требуем, чтобы были в файле
248 if required_sections is not None:
249 self.req_sections = set(required_sections)
252 self.req_sections = set()
254 if not is_dataclass(config_object_class):
255 raise ConfigParseHelperError(f'Представленный в качестве объекта конфигурации класс не является '
256 f'классом данных: {config_object_class.__name__}')
258 self.res_obj = config_object_class
259 self.fields = dict((fld.name, fld) for fld in fields(config_object_class))
260 self.conf_parser: Optional[ConfigParser] = None
261 self.config_params = {}
262 self.config_params_lock = RLock()
264 def add_params(self, params: Dict[str, Any]):
265 self.config_params_lock.acquire()
267 self.config_params.update(params)
270 self.config_params_lock.release()
272 def section(self, section_name: str) -> CPHSectionBase:
273 if self.conf_parser is None:
274 raise ConfigParseHelperError(f'Прежде чем приступать к разбору файла конфигурации стоит его загрузить')
276 if self.conf_parser.has_section(section_name):
277 return CPHSection(self, section_name)
280 return CPHAbsentSection()
282 def load(self, filename: str):
287 except (TypeError, IOError, OSError, ValueError) as e:
288 raise ConfigParseHelperError(f'Не удалось загрузить файл конфигурации: файл="{filename}" '
291 missing_sections = self.req_sections - set(res.sections())
294 missing_sections = ', '.join(missing_sections)
295 raise ConfigParseHelperError(f'В конфигурационном файле отсутствуют секции: {missing_sections}')
297 self.conf_parser = res
299 def get_config(self):
301 return self.res_obj(**self.config_params)
303 except (ValueError, TypeError) as e:
304 raise ConfigParseHelperError(f'Не удалось инициализировать объект конфигурации: {e}')
306 def get_sections(self):
307 return self.conf_parser.sections()