py.lib
42:d9a3784f681b
Go to Latest
py.lib/type_utils/config_parse_helper.py
+ Возможность удаления cookie по полному объекту (полезно, когда выставляются особые параметры на cookie, и их не удаётся удалить по имени)
3 Получение из конфигурационного файла параметров в виде класса данных.
5 НЕ РАБОТАЕТ БЕЗ ОСТАЛЬНОГО МОДУЛЯ type_utils
8 from configparser import ConfigParser
9 from dataclasses import is_dataclass, fields, Field, MISSING
10 from typing import Iterable, Optional, Dict, Any, List, Callable
11 from threading import RLock
14 from .type_descriptor import get_type_describer
17 class ConfigParseHelperError(Exception):
19 Ошибки в модуле помощника разборщика конфигураций
23 class NoSectionNotification(ConfigParseHelperError):
25 Оповещение об отсутствующей секции конфигурации
31 Базовый класс обработки секции конфигурационного файла
34 def get(self, config_prop_name: str, dc_prop_name: str):
36 Получить свойство из конфигурационного файла
38 raise NotImplemented()
43 def __exit__(self, exc_type, exc_val, exc_tb):
44 raise NotImplemented()
47 class CPHAbsentSection(CPHSectionBase):
49 Класс создаваемый на отсутствующую секцию конфигурационного файла
51 def get(self, config_prop_name: str, dc_prop_name: str):
52 raise NoSectionNotification()
54 def __exit__(self, exc_type, exc_val, exc_tb):
55 if exc_type == NoSectionNotification:
59 class CPHParamGetter(CPHSectionBase):
60 def __init__(self, parser_helper_object):
61 self.ph = parser_helper_object
64 def _add_param(self, param_name: str, param_val: Any, parser: Optional[Callable[[Any], Any]] = None):
66 Непосредственное добавление полученного параметра со всеми проверками.
69 if parser is not None and param_val is not None:
70 param_val = parser(param_val)
72 fld = self.ph.fields.get(param_name)
73 if not isinstance(fld, Field):
74 raise ConfigParseHelperError(f'В классе данных отсутствует свойство "{param_name}", '
75 f'которое мы должны заполнить из параметра конфигурации: {fld}')
77 if param_val is not None:
78 type_desc = get_type_describer(fld.type)
80 res = type_desc(param_val)
82 except (ValueError, TypeError) as e:
83 raise ConfigParseHelperError(f'При приведении параметра к '
84 f'заданному типу произошла ошибка: '
85 f'значение="{param_val}" ошибка="{e}"')
88 if fld.default is not MISSING:
91 elif fld.default_factory is not MISSING:
92 res = fld.default_factory()
95 raise ConfigParseHelperError('В конфигурации не заданна обязательная опция')
97 self.params[param_name] = res
99 def __exit__(self, exc_type, exc_val, exc_tb):
101 self.ph.add_params(self.params)
103 def get(self, config_prop_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None):
104 raise NoSectionNotification()
107 class CPHSection(CPHParamGetter):
109 Класс производящий разбор конкретной секции конфигурации
112 def __init__(self, parser_helper_object, section: str):
113 super().__init__(parser_helper_object)
114 self.section_name = section
115 self.section = parser_helper_object.conf_parser[section]
117 def get(self, config_prop_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None):
119 :param config_prop_name: Имя опции в файле конфигурации
120 :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию
121 :param parser: Исполнимый обработчик значения, перед его помещением в конфигурацию
124 self._add_param(dc_prop_name, self.section.get(config_prop_name), parser)
126 except ConfigParseHelperError as e:
127 raise ConfigParseHelperError(f'Ошибка при разборе параметра "{config_prop_name}" '
128 f'в секции "{self.section_name}": {e}')
131 class CPHEnvParser(CPHParamGetter):
133 Класс для разбора переменных окружения в том же ключе, что и файла конфигурации
136 def get(self, env_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None):
138 :param env_name: Имя переменной окружения, хранящей опцию
139 :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию
140 :param parser: Исполнимый обработчик значения, перед его помещением в конфигурацию
144 self._add_param(dc_prop_name, getenv(env_name), parser)
146 except ConfigParseHelperError as e:
147 raise ConfigParseHelperError(f'Ошибка при получении значения из переменной окружения "{env_name}": {e}')
150 class CPHObjectsListGetter:
152 Помощник для случаев, когда в наборе секций хранится однотипный набор объектов
154 def __init__(self, config_object_class: type, config_parser: ConfigParser, sections: Iterable[str]):
155 self.sections = sections
156 self.conf_parser = config_parser
158 if not is_dataclass(config_object_class):
159 raise ConfigParseHelperError(f'Представленный в качестве представления объекта конфигурации '
160 f'класс не является классом данных: {config_object_class.__name__}')
162 self.res_obj = config_object_class
163 self.fields = dict((fld.name, fld) for fld in fields(config_object_class))
167 def add_params(self, params: Dict[str, Any]):
169 self.obj_list.append(self.res_obj(**params))
171 except (ValueError, TypeError) as e:
172 raise ConfigParseHelperError(f'Ошибка создания объекта объекта конфигурации, '
173 f'списка объектов конфигурации: {e}')
175 def get(self, config_prop_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None):
177 Подготавливаем список соответствия названий параметров в секции конкретным свойствам данного
180 :param config_prop_name: Имя опции в файле конфигурации
181 :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию
182 :param parser: Исполнимый обработчик значения, перед его помещением в конфигурацию
185 self.ident_list.append((config_prop_name, dc_prop_name, parser))
187 def get_config_objects(self) -> List[object]:
188 for section in self.sections:
190 with CPHSection(self, section) as section_helper:
191 for conf_prop, dc_prop, parser in self.ident_list:
192 section_helper.get(conf_prop, dc_prop, parser)
194 except ConfigParseHelperError as e:
195 raise ConfigParseHelperError(f'Ошибка при разборе секции "{section}": {e}')
197 res = self.obj_list[:]
198 self.obj_list.clear()
203 class ConfigParseHelper:
205 Помощник разбора конфигурации
207 def __init__(self, config_object_class: type, required_sections: Optional[Iterable[str]] = None):
209 :param config_object_class: Dataclass, который мы подготовили как хранилище параметров конфигурации
210 :param required_sections: Перечисление секций конфигурации, которые мы требуем, чтобы были в файле
213 if required_sections is not None:
214 self.req_sections = set(required_sections)
217 self.req_sections = set()
219 if not is_dataclass(config_object_class):
220 raise ConfigParseHelperError(f'Представленный в качестве объекта конфигурации класс не является '
221 f'классом данных: {config_object_class.__name__}')
223 self.res_obj = config_object_class
224 self.fields = dict((fld.name, fld) for fld in fields(config_object_class))
225 self.conf_parser: Optional[ConfigParser] = None
226 self.config_params = {}
227 self.config_params_lock = RLock()
229 def add_params(self, params: Dict[str, Any]):
230 self.config_params_lock.acquire()
232 self.config_params.update(params)
235 self.config_params_lock.release()
237 def section(self, section_name: str) -> CPHSectionBase:
238 if self.conf_parser is None:
239 raise ConfigParseHelperError(f'Прежде чем приступать к разбору файла конфигурации стоит его загрузить')
241 if self.conf_parser.has_section(section_name):
242 return CPHSection(self, section_name)
245 return CPHAbsentSection()
247 def load(self, filename: str):
252 except (TypeError, IOError, OSError, ValueError) as e:
253 raise ConfigParseHelperError(f'Не удалось загрузить файл конфигурации: файл="{filename}" '
256 missing_sections = self.req_sections - set(res.sections())
259 missing_sections = ', '.join(missing_sections)
260 raise ConfigParseHelperError(f'В конфигурационном файле отсутствуют секции: {missing_sections}')
262 self.conf_parser = res
264 def get_config(self):
266 return self.res_obj(**self.config_params)
268 except (ValueError, TypeError) as e:
269 raise ConfigParseHelperError(f'Не удалось инициализировать объект конфигурации: {e}')
271 def get_sections(self):
272 return self.conf_parser.sections()