py.lib

Yohn Y. 2022-08-20 Parent:ae0107755941 Child:b8d559c989d6

38:4f4cc2fc9805 Browse Files

. Полный рефакторинг кода модулей dataclass_utils.py и config_parse_helper.py. Теперь логика предсказуема. + функция dataobj_extract не просто бездумно загоняет данные в класс данных, но имеет функционал проверки данных с возбуждением исключения при разнице (по умолчанию) и принудительного приведения типов.

config_parse_helper.py dataclass_utils.py type_utils/__init__.py type_utils/config_parse_helper.py type_utils/dataclass_utils.py type_utils/json_tools.py type_utils/type_descriptor.py

     1.1 --- a/config_parse_helper.py	Fri Aug 19 02:36:30 2022 +0300
     1.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.3 @@ -1,517 +0,0 @@
     1.4 -# coding: utf-8
     1.5 -from configparser import ConfigParser
     1.6 -from dataclasses import is_dataclass, fields, Field, MISSING
     1.7 -from typing import Iterable, Optional, Dict, Any, List, Callable, Union
     1.8 -from threading import RLock
     1.9 -from os import getenv
    1.10 -
    1.11 -
    1.12 -class ConfigParseHelperError(Exception):
    1.13 -    """\
    1.14 -    Ошибки в модуле помощника разборщика конфигураций
    1.15 -    """
    1.16 -
    1.17 -
    1.18 -class NoSectionNotification(ConfigParseHelperError):
    1.19 -    """\
    1.20 -    Оповещение об отсутствующей секции конфигурации
    1.21 -    """
    1.22 -
    1.23 -
    1.24 -class TypeDescriber:
    1.25 -    """\
    1.26 -    Реализует паттерн "адаптер" поверх типов, для упрощения приведения значений типов к объявленным формам
    1.27 -    """
    1.28 -    def __init__(self, name, cast, like, is_complex=False):
    1.29 -        self.__name__ = name
    1.30 -        self.cast = cast
    1.31 -        self.like = like
    1.32 -        self.is_complex = is_complex
    1.33 -
    1.34 -    def check(self, instance):
    1.35 -        if self.like is None:
    1.36 -            return False
    1.37 -
    1.38 -        else:
    1.39 -            return check_instance(instance, self.like)
    1.40 -
    1.41 -    def __repr__(self):
    1.42 -        return f'<TypeDescriber({self.__name__}, {self.like})>'
    1.43 -
    1.44 -    def __call__(self, val):
    1.45 -        if val is None:
    1.46 -            return None
    1.47 -
    1.48 -        elif not self.is_complex and check_instance(val, self.like):
    1.49 -            return val
    1.50 -
    1.51 -        else:
    1.52 -            return self.cast(val)
    1.53 -
    1.54 -
    1.55 -def check_instance(obj, types):
    1.56 -    if types is None:
    1.57 -        return False
    1.58 -
    1.59 -    elif isinstance(types, (tuple, list)):
    1.60 -        _flag = False
    1.61 -        for t in types:
    1.62 -            if check_instance(obj, t):
    1.63 -                _flag = True
    1.64 -                break
    1.65 -
    1.66 -        return _flag
    1.67 -
    1.68 -    elif isinstance(types, TypeDescriber):
    1.69 -        return types.check(obj)
    1.70 -
    1.71 -    else:
    1.72 -        return isinstance(obj, types)
    1.73 -
    1.74 -
    1.75 -def cast_iterator(t: Union[type, TypeDescriber], lst: Iterable):
    1.76 -    """\
    1.77 -    Обрабатывает последовательности единого типа.
    1.78 -    """
    1.79 -    for i in lst:
    1.80 -        if check_instance(i, t):
    1.81 -            yield i
    1.82 -
    1.83 -        else:
    1.84 -            try:
    1.85 -                yield t(i)
    1.86 -
    1.87 -            except (TypeError, ValueError) as e:
    1.88 -                raise ValueError(f'Не удалось привести значение к нужному типу: '
    1.89 -                                 f'тип={t.__name__}; знач={i}')
    1.90 -
    1.91 -
    1.92 -def multi_item_tuple(tt, val):
    1.93 -    """\
    1.94 -    Обрабатывает кортежи, состоящие из нескольких значений типов (кортежи элементов разных типов)
    1.95 -    :param tt: Последовательность составляющих кортеж типов
    1.96 -    :param val: итерируемый объект, хранящий значения в указанном порядке.
    1.97 -    """
    1.98 -    val = list(val)
    1.99 -    t_len = len(tt)
   1.100 -    if t_len == 0:
   1.101 -        raise ValueError('При вызове процедуры конвертации котежей, не были указаны типы')
   1.102 -
   1.103 -    if len(val) != t_len:
   1.104 -        raise ValueError(f'Значение не содержит положенных {t_len} элементов: {len(val)} - {val}')
   1.105 -
   1.106 -    res = []
   1.107 -
   1.108 -    for i in range(t_len):
   1.109 -        if check_instance(val[i], tt[i]):
   1.110 -            res.append(val[i])
   1.111 -
   1.112 -        else:
   1.113 -            try:
   1.114 -                res.append(tt[i](val[i]))
   1.115 -
   1.116 -            except (TypeError, ValueError) as e:
   1.117 -                raise ValueError(f'Не удалось привести значение к нужному типу: '
   1.118 -                                 f'тип={tt[i].__name__}; знач={val[i]}')
   1.119 -
   1.120 -    return tuple(res)
   1.121 -
   1.122 -
   1.123 -def union_processor(tt, val):
   1.124 -    """\
   1.125 -    Пытается привести значение к одному из указанных типов
   1.126 -    :param tt: список возможных типов
   1.127 -    :param val: приводимое значение
   1.128 -    """
   1.129 -    if val is None:
   1.130 -        return val
   1.131 -
   1.132 -    res = None
   1.133 -    ex = []
   1.134 -
   1.135 -    if len(tt) == 0:
   1.136 -        raise ValueError('Не указан ни один тип в составном типе Union')
   1.137 -
   1.138 -    sorted_types_begin = [ t for t in tt if t in (int, float, bool)]
   1.139 -    sorted_types_body = [ t for t in tt if t not in (int, float, bool, str)]
   1.140 -    sorted_types_end = [ t for t in tt if t == str]
   1.141 -
   1.142 -    for t in sorted_types_begin + sorted_types_body + sorted_types_end:
   1.143 -        _t = get_type_describer(t)
   1.144 -        try:
   1.145 -            res = _t(val)
   1.146 -            break
   1.147 -
   1.148 -        except (TypeError, ValueError) as e:
   1.149 -            ex.append(f'{t}: {e}')
   1.150 -
   1.151 -    if res is None:
   1.152 -        raise ValueError('Не удалось привести значение не к одному из типов:\n' + '\n'.join(ex))
   1.153 -
   1.154 -    else:
   1.155 -        return res
   1.156 -
   1.157 -
   1.158 -def dict_processor(tt, val):
   1.159 -    if len(tt) != 2:
   1.160 -        raise ValueError(f'Попытка воссоздать словарь со странным количеством аргументов типа: {tt}')
   1.161 -
   1.162 -    try:
   1.163 -        _d = dict(val)
   1.164 -
   1.165 -    except (TypeError, ValueError) as e:
   1.166 -        raise ValueError(f'Не удалось воссоздать словарь из представленного значения: {e}')
   1.167 -
   1.168 -    _p = []
   1.169 -
   1.170 -    for k, v in _d.items():
   1.171 -        try:
   1.172 -            _p.append((tt[0](k), tt[1](v)))
   1.173 -
   1.174 -        except (TypeError, ValueError) as e:
   1.175 -            raise ValueError(f'Не удалось привести значения элемента словаря к требуемому типу: '
   1.176 -                             f'key="{k}" value="{v}"')
   1.177 -
   1.178 -    return dict(_p)
   1.179 -
   1.180 -
   1.181 -def get_type_describer(t) -> TypeDescriber:
   1.182 -    if type(t).__name__ == '_GenericAlias':
   1.183 -        try:
   1.184 -            _args = t.__args__
   1.185 -
   1.186 -        except AttributeError:
   1.187 -            raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
   1.188 -                            f'типа "_GenericAlias": {t}')
   1.189 -
   1.190 -        if t.__name__ == 'List':
   1.191 -            try:
   1.192 -                _t = _args[0]
   1.193 -
   1.194 -            except IndexError:
   1.195 -                raise ValueError(f'Тип {t} не содержит в себе типа своих элементов')
   1.196 -
   1.197 -            return TypeDescriber(
   1.198 -                name=f'{t.__name__}[{_t.__name__}]',
   1.199 -                cast=lambda x: list(cast_iterator(get_type_describer(_t).cast, x)),
   1.200 -                like=list,
   1.201 -                is_complex=True
   1.202 -            )
   1.203 -
   1.204 -        elif t.__name__ == 'Tuple':
   1.205 -            if not _args:
   1.206 -                raise ValueError(f'Тип {t} не содержит в себе пояснений по составу хранящихся в нём типов')
   1.207 -
   1.208 -            if len(_args) == 1:
   1.209 -                _t = _args[0]
   1.210 -
   1.211 -                return TypeDescriber(
   1.212 -                    name=f'Tuple[{_t.__name__}]',
   1.213 -                    cast=lambda x: list(cast_iterator(get_type_describer(_t).cast, x)),
   1.214 -                    like=tuple,
   1.215 -                    is_complex=True
   1.216 -                )
   1.217 -
   1.218 -            else:
   1.219 -                _name = ', '.join(map(lambda x: x.__name__, _args))
   1.220 -                _cast_args = tuple(get_type_describer(i) for i in _args)
   1.221 -
   1.222 -                return TypeDescriber(
   1.223 -                    name=f'Tuple[{_name}]',
   1.224 -                    cast=lambda x: multi_item_tuple(_cast_args, x),
   1.225 -                    like=tuple,
   1.226 -                    is_complex=True
   1.227 -                )
   1.228 -
   1.229 -        elif t.__name__ == 'Dict':
   1.230 -            if len(_args) != 2:
   1.231 -                raise ValueError(f'Неожиданное количество значений типа в составном типе Dict: '
   1.232 -                                 f'{len(_args)} values=({_args})')
   1.233 -
   1.234 -            _name = ', '.join(map(lambda x: x.__name__, _args))
   1.235 -
   1.236 -            return TypeDescriber(
   1.237 -                name=f'Dict[{_name}]',
   1.238 -                cast=lambda x: dict_processor(_args, x),
   1.239 -                like=dict,
   1.240 -                is_complex=True
   1.241 -            )
   1.242 -
   1.243 -        else:
   1.244 -            raise ValueError(f'Неизвестный представитель типа "_GenericAlias": {t.__name__}')
   1.245 -
   1.246 -    elif type(t).__name__ == '_UnionGenericAlias':
   1.247 -        if t.__name__ not in ('Union', 'Optional'):
   1.248 -            raise TypeError(f'Неизвестный подтип _UnionGenericAlias: {t.__name__}')
   1.249 -
   1.250 -        try:
   1.251 -            _args = t.__args__
   1.252 -
   1.253 -        except AttributeError:
   1.254 -            raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
   1.255 -                            f'типа "_UnionGenericAlias": {t}')
   1.256 -
   1.257 -        if len(_args) == 0:
   1.258 -            raise ValueError('Не указан ни один тип в конструкции Union')
   1.259 -
   1.260 -        _cast_args = tuple(map(get_type_describer, _args))
   1.261 -        _args_name = ', '.join(map(lambda x: x.__name__, _args))
   1.262 -
   1.263 -        return TypeDescriber(
   1.264 -            name=f'Union[{_args_name}]',
   1.265 -            cast=lambda x: union_processor(_cast_args, x),
   1.266 -            like=None
   1.267 -        )
   1.268 -
   1.269 -    else:
   1.270 -        return TypeDescriber(
   1.271 -            name=t.__name__,
   1.272 -            cast=t,
   1.273 -            like=t
   1.274 -        )
   1.275 -
   1.276 -
   1.277 -class CPHSectionBase:
   1.278 -    """\
   1.279 -    Базовый класс обработки секции конфигурационного файла
   1.280 -    """
   1.281 -
   1.282 -    def get(self, config_prop_name: str, dc_prop_name: str):
   1.283 -        """\
   1.284 -        Получить свойство из конфигурационного файла
   1.285 -        """
   1.286 -        raise NotImplemented()
   1.287 -
   1.288 -    def __enter__(self):
   1.289 -        return self
   1.290 -
   1.291 -    def __exit__(self, exc_type, exc_val, exc_tb):
   1.292 -        raise NotImplemented()
   1.293 -
   1.294 -
   1.295 -class CPHAbsentSection(CPHSectionBase):
   1.296 -    """\
   1.297 -    Класс создаваемый на отсутствующую секцию конфигурационного файла
   1.298 -    """
   1.299 -    def get(self, config_prop_name: str, dc_prop_name: str):
   1.300 -        raise NoSectionNotification()
   1.301 -
   1.302 -    def __exit__(self, exc_type, exc_val, exc_tb):
   1.303 -        if exc_type == NoSectionNotification:
   1.304 -            return True
   1.305 -
   1.306 -
   1.307 -class CPHParamGetter(CPHSectionBase):
   1.308 -    def __init__(self, parser_helper_object):
   1.309 -        self.ph = parser_helper_object
   1.310 -        self.params = {}
   1.311 -
   1.312 -    def _add_param(self, param_name: str, param_val: Any, parser: Optional[Callable[[Any], Any]] = None):
   1.313 -        """\
   1.314 -        Непосредственное добавление полученного параметра со всеми проверками.
   1.315 -        """
   1.316 -
   1.317 -        if parser is not None and param_val is not None:
   1.318 -            param_val = parser(param_val)
   1.319 -
   1.320 -        fld = self.ph.fields.get(param_name)
   1.321 -        if not isinstance(fld, Field):
   1.322 -            raise ConfigParseHelperError(f'В классе данных отсутствует свойство "{param_name}", '
   1.323 -                                         f'которое мы должны заполнить из параметра конфигурации: {fld}')
   1.324 -
   1.325 -        if param_val is not None:
   1.326 -            type_desc = get_type_describer(fld.type)
   1.327 -            try:
   1.328 -                res = type_desc(param_val)
   1.329 -
   1.330 -            except (ValueError, TypeError) as e:
   1.331 -                raise ConfigParseHelperError(f'При приведении параметра к '
   1.332 -                                             f'заданному типу произошла ошибка: '
   1.333 -                                             f'значение="{param_val}" ошибка="{e}"')
   1.334 -
   1.335 -        else:
   1.336 -            if fld.default is not MISSING:
   1.337 -                res = fld.default
   1.338 -
   1.339 -            elif fld.default_factory is not MISSING:
   1.340 -                res = fld.default_factory()
   1.341 -
   1.342 -            else:
   1.343 -                raise ConfigParseHelperError('В конфигурации не заданна обязательная опция')
   1.344 -
   1.345 -        self.params[param_name] = res
   1.346 -
   1.347 -    def __exit__(self, exc_type, exc_val, exc_tb):
   1.348 -        if exc_type is None:
   1.349 -            self.ph.add_params(self.params)
   1.350 -
   1.351 -    def get(self, config_prop_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None):
   1.352 -        raise NoSectionNotification()
   1.353 -
   1.354 -
   1.355 -class CPHSection(CPHParamGetter):
   1.356 -    """\
   1.357 -    Класс производящий разбор конкретной секции конфигурации
   1.358 -    """
   1.359 -
   1.360 -    def __init__(self, parser_helper_object, section: str):
   1.361 -        super().__init__(parser_helper_object)
   1.362 -        self.section_name = section
   1.363 -        self.section = parser_helper_object.conf_parser[section]
   1.364 -
   1.365 -    def get(self, config_prop_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None):
   1.366 -        """\
   1.367 -        :param config_prop_name: Имя опции в файле конфигурации
   1.368 -        :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию
   1.369 -        :param parser: Исполнимый обработчик значения, перед его помещением в конфигурацию
   1.370 -        """
   1.371 -        try:
   1.372 -            self._add_param(dc_prop_name, self.section.get(config_prop_name), parser)
   1.373 -
   1.374 -        except ConfigParseHelperError as e:
   1.375 -            raise ConfigParseHelperError(f'Ошибка при разборе параметра "{config_prop_name}" '
   1.376 -                                         f'в секции "{self.section_name}": {e}')
   1.377 -
   1.378 -
   1.379 -class CPHEnvParser(CPHParamGetter):
   1.380 -    """\
   1.381 -    Класс для разбора переменных окружения в том же ключе, что и файла конфигурации
   1.382 -    """
   1.383 -
   1.384 -    def get(self, env_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None):
   1.385 -        """\
   1.386 -        :param env_name: Имя переменной окружения, хранящей опцию
   1.387 -        :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию
   1.388 -        :param parser: Исполнимый обработчик значения, перед его помещением в конфигурацию
   1.389 -        """
   1.390 -
   1.391 -        try:
   1.392 -            self._add_param(dc_prop_name, getenv(env_name), parser)
   1.393 -
   1.394 -        except ConfigParseHelperError as e:
   1.395 -            raise ConfigParseHelperError(f'Ошибка при получении значения из переменной окружения "{env_name}": {e}')
   1.396 -
   1.397 -
   1.398 -class CPHObjectsListGetter:
   1.399 -    """\
   1.400 -    Помощник для случаев, когда в наборе секций хранится однотипный набор объектов
   1.401 -    """
   1.402 -    def __init__(self, config_object_class: type, config_parser: ConfigParser, sections: Iterable[str]):
   1.403 -        self.sections = sections
   1.404 -        self.conf_parser = config_parser
   1.405 -
   1.406 -        if not is_dataclass(config_object_class):
   1.407 -            raise ConfigParseHelperError(f'Представленный в качестве представления объекта конфигурации '
   1.408 -                                         f'класс не является классом данных: {config_object_class.__name__}')
   1.409 -
   1.410 -        self.res_obj = config_object_class
   1.411 -        self.fields = dict((fld.name, fld) for fld in fields(config_object_class))
   1.412 -        self.obj_list = []
   1.413 -        self.ident_list = []
   1.414 -
   1.415 -    def add_params(self, params: Dict[str, Any]):
   1.416 -        try:
   1.417 -            self.obj_list.append(self.res_obj(**params))
   1.418 -
   1.419 -        except (ValueError, TypeError) as e:
   1.420 -            raise ConfigParseHelperError(f'Ошибка создания объекта объекта конфигурации, '
   1.421 -                                         f'списка объектов конфигурации: {e}')
   1.422 -
   1.423 -    def get(self, config_prop_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None):
   1.424 -        """\
   1.425 -        Подготавливаем список соответствия названий параметров в секции конкретным свойствам данного
   1.426 -        в помощник класса
   1.427 -
   1.428 -        :param config_prop_name: Имя опции в файле конфигурации
   1.429 -        :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию
   1.430 -        :param parser: Исполнимый обработчик значения, перед его помещением в конфигурацию
   1.431 -        """
   1.432 -
   1.433 -        self.ident_list.append((config_prop_name, dc_prop_name, parser))
   1.434 -
   1.435 -    def get_config_objects(self) -> List[object]:
   1.436 -        for section in self.sections:
   1.437 -            try:
   1.438 -                with CPHSection(self, section) as section_helper:
   1.439 -                    for conf_prop, dc_prop, parser in self.ident_list:
   1.440 -                        section_helper.get(conf_prop, dc_prop, parser)
   1.441 -
   1.442 -            except ConfigParseHelperError as e:
   1.443 -                raise ConfigParseHelperError(f'Ошибка при разборе секции "{section}": {e}')
   1.444 -
   1.445 -        res = self.obj_list[:]
   1.446 -        self.obj_list.clear()
   1.447 -
   1.448 -        return res
   1.449 -
   1.450 -
   1.451 -class ConfigParseHelper:
   1.452 -    """\
   1.453 -    Помощник разбора конфигурации
   1.454 -    """
   1.455 -    def __init__(self, config_object_class: type, required_sections: Optional[Iterable[str]] = None):
   1.456 -        """\
   1.457 -        :param config_object_class: Dataclass, который мы подготовили как хранилище параметров конфигурации
   1.458 -        :param required_sections: Перечисление секций конфигурации, которые мы требуем, чтобы были в файле
   1.459 -        """
   1.460 -
   1.461 -        if required_sections is not None:
   1.462 -            self.req_sections = set(required_sections)
   1.463 -
   1.464 -        else:
   1.465 -            self.req_sections = set()
   1.466 -
   1.467 -        if not is_dataclass(config_object_class):
   1.468 -            raise ConfigParseHelperError(f'Представленный в качестве объекта конфигурации класс не является '
   1.469 -                                         f'классом данных: {config_object_class.__name__}')
   1.470 -
   1.471 -        self.res_obj = config_object_class
   1.472 -        self.fields = dict((fld.name, fld) for fld in fields(config_object_class))
   1.473 -        self.conf_parser: Optional[ConfigParser] = None
   1.474 -        self.config_params = {}
   1.475 -        self.config_params_lock = RLock()
   1.476 -
   1.477 -    def add_params(self, params: Dict[str, Any]):
   1.478 -        self.config_params_lock.acquire()
   1.479 -        try:
   1.480 -            self.config_params.update(params)
   1.481 -
   1.482 -        finally:
   1.483 -            self.config_params_lock.release()
   1.484 -
   1.485 -    def section(self, section_name: str) -> CPHSectionBase:
   1.486 -        if self.conf_parser is None:
   1.487 -            raise ConfigParseHelperError(f'Прежде чем приступать к разбору файла конфигурации стоит его загрузить')
   1.488 -
   1.489 -        if self.conf_parser.has_section(section_name):
   1.490 -            return CPHSection(self, section_name)
   1.491 -
   1.492 -        else:
   1.493 -            return CPHAbsentSection()
   1.494 -
   1.495 -    def load(self, filename: str):
   1.496 -        res = ConfigParser()
   1.497 -        try:
   1.498 -            res.read(filename)
   1.499 -
   1.500 -        except (TypeError, IOError, OSError, ValueError) as e:
   1.501 -            raise ConfigParseHelperError(f'Не удалось загрузить файл конфигурации: файл="{filename}" '
   1.502 -                                         f'ошибка="{e}"')
   1.503 -
   1.504 -        missing_sections = self.req_sections - set(res.sections())
   1.505 -
   1.506 -        if missing_sections:
   1.507 -            missing_sections = ', '.join(missing_sections)
   1.508 -            raise ConfigParseHelperError(f'В конфигурационном файле отсутствуют секции: {missing_sections}')
   1.509 -
   1.510 -        self.conf_parser = res
   1.511 -
   1.512 -    def get_config(self):
   1.513 -        try:
   1.514 -            return self.res_obj(**self.config_params)
   1.515 -
   1.516 -        except (ValueError, TypeError) as e:
   1.517 -            raise ConfigParseHelperError(f'Не удалось инициализировать объект конфигурации: {e}')
   1.518 -
   1.519 -    def get_sections(self):
   1.520 -        return self.conf_parser.sections()
     2.1 --- a/dataclass_utils.py	Fri Aug 19 02:36:30 2022 +0300
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,345 +0,0 @@
     2.4 -# coding: utf-8
     2.5 -
     2.6 -from dataclasses import fields, is_dataclass, asdict
     2.7 -from typing import Union, Dict, Any, Iterable
     2.8 -
     2.9 -
    2.10 -def _dict_has(obj: Dict[str, Any], key: str) -> bool:
    2.11 -    return key in obj
    2.12 -
    2.13 -
    2.14 -def _dict_get(obj: Dict[str, Any], key: str) -> Any:
    2.15 -    return obj[key]
    2.16 -
    2.17 -
    2.18 -def _obj_has(obj: object, key: str) -> bool:
    2.19 -    return hasattr(obj, key)
    2.20 -
    2.21 -
    2.22 -def _obj_get(obj: object, key: str) -> Any:
    2.23 -    return getattr(obj, key)
    2.24 -
    2.25 -
    2.26 -class TypeDescriber:
    2.27 -    """\
    2.28 -    Реализует паттерн "адаптер" поверх типов, для упрощения приведения значений типов к объявленным формам
    2.29 -    """
    2.30 -    def __init__(self, name, cast, like, is_complex=False):
    2.31 -        self.__name__ = name
    2.32 -        self.cast = cast
    2.33 -        self.like = like
    2.34 -        self.is_complex = is_complex
    2.35 -
    2.36 -    def check(self, instance):
    2.37 -        if self.like is None:
    2.38 -            return False
    2.39 -
    2.40 -        else:
    2.41 -            return check_instance(instance, self.like)
    2.42 -
    2.43 -    def __repr__(self):
    2.44 -        return f'<TypeDescriber({self.__name__}, {self.like})>'
    2.45 -
    2.46 -    def __call__(self, val):
    2.47 -        if val is None:
    2.48 -            return None
    2.49 -
    2.50 -        elif not self.is_complex and check_instance(val, self.like):
    2.51 -            return val
    2.52 -
    2.53 -        else:
    2.54 -            return self.cast(val)
    2.55 -
    2.56 -
    2.57 -def check_instance(obj, types):
    2.58 -    if types is None:
    2.59 -        return False
    2.60 -
    2.61 -    elif isinstance(types, (tuple, list)):
    2.62 -        _flag = False
    2.63 -        for t in types:
    2.64 -            if check_instance(obj, t):
    2.65 -                _flag = True
    2.66 -                break
    2.67 -
    2.68 -        return _flag
    2.69 -
    2.70 -    elif isinstance(types, TypeDescriber):
    2.71 -        return types.check(obj)
    2.72 -
    2.73 -    else:
    2.74 -        return isinstance(obj, types)
    2.75 -
    2.76 -
    2.77 -def cast_iterator(t: Union[type, TypeDescriber], lst: Iterable):
    2.78 -    """\
    2.79 -    Обрабатывает последовательности единого типа.
    2.80 -    """
    2.81 -    for i in lst:
    2.82 -        if check_instance(i, t):
    2.83 -            yield i
    2.84 -
    2.85 -        else:
    2.86 -            try:
    2.87 -                yield t(i)
    2.88 -
    2.89 -            except (TypeError, ValueError) as e:
    2.90 -                raise ValueError(f'Не удалось привести значение к нужному типу: '
    2.91 -                                 f'тип={t.__name__}; знач={i}')
    2.92 -
    2.93 -
    2.94 -def multi_item_tuple(tt, val):
    2.95 -    """\
    2.96 -    Обрабатывает кортежи, состоящие из нескольких значений типов (кортежи элементов разных типов)
    2.97 -    :param tt: Последовательность составляющих кортеж типов
    2.98 -    :param val: итерируемый объект, хранящий значения в указанном порядке.
    2.99 -    """
   2.100 -    val = list(val)
   2.101 -    t_len = len(tt)
   2.102 -    if t_len == 0:
   2.103 -        raise ValueError('При вызове процедуры конвертации котежей, не были указаны типы')
   2.104 -
   2.105 -    if len(val) != t_len:
   2.106 -        raise ValueError(f'Значение не содержит положенных {t_len} элементов: {len(val)} - {val}')
   2.107 -
   2.108 -    res = []
   2.109 -
   2.110 -    for i in range(t_len):
   2.111 -        if check_instance(val[i], tt[i]):
   2.112 -            res.append(val[i])
   2.113 -
   2.114 -        else:
   2.115 -            try:
   2.116 -                res.append(tt[i](val[i]))
   2.117 -
   2.118 -            except (TypeError, ValueError) as e:
   2.119 -                raise ValueError(f'Не удалось привести значение к нужному типу: '
   2.120 -                                 f'тип={tt[i].__name__}; знач={val[i]}')
   2.121 -
   2.122 -    return tuple(res)
   2.123 -
   2.124 -
   2.125 -def union_processor(tt, val):
   2.126 -    """\
   2.127 -    Пытается привести значение к одному из указанных типов
   2.128 -    :param tt: список возможных типов
   2.129 -    :param val: приводимое значение
   2.130 -    """
   2.131 -    if val is None:
   2.132 -        return val
   2.133 -
   2.134 -    res = None
   2.135 -    ex = []
   2.136 -
   2.137 -    if len(tt) == 0:
   2.138 -        raise ValueError('Не указан ни один тип в составном типе Union')
   2.139 -
   2.140 -    sorted_types_begin = [ t for t in tt if t in (int, float, bool)]
   2.141 -    sorted_types_body = [ t for t in tt if t not in (int, float, bool, str)]
   2.142 -    sorted_types_end = [ t for t in tt if t == str]
   2.143 -
   2.144 -    for t in sorted_types_begin + sorted_types_body + sorted_types_end:
   2.145 -        _t = get_type_describer(t)
   2.146 -        try:
   2.147 -            res = _t(val)
   2.148 -            break
   2.149 -
   2.150 -        except (TypeError, ValueError) as e:
   2.151 -            ex.append(f'{t}: {e}')
   2.152 -
   2.153 -    if res is None:
   2.154 -        raise ValueError('Не удалось привести значение не к одному из типов:\n' + '\n'.join(ex))
   2.155 -
   2.156 -    else:
   2.157 -        return res
   2.158 -
   2.159 -
   2.160 -def dict_processor(tt, val):
   2.161 -    if len(tt) != 2:
   2.162 -        raise ValueError(f'Попытка воссоздать словарь со странным количеством аргументов типа: {tt}')
   2.163 -
   2.164 -    try:
   2.165 -        _d = dict(val)
   2.166 -
   2.167 -    except (TypeError, ValueError) as e:
   2.168 -        raise ValueError(f'Не удалось воссоздать словарь из представленного значения: {e}')
   2.169 -
   2.170 -    _p = []
   2.171 -
   2.172 -    tt_cast = [get_type_describer(t) for t in tt]
   2.173 -
   2.174 -    for k, v in _d.items():
   2.175 -        try:
   2.176 -            _p.append((tt_cast[0](k), tt_cast[1](v)))
   2.177 -
   2.178 -        except (TypeError, ValueError) as e:
   2.179 -            raise ValueError(f'Не удалось привести значения элемента словаря к требуемому типу: '
   2.180 -                             f'key="{k}" value="{v}"')
   2.181 -
   2.182 -    return dict(_p)
   2.183 -
   2.184 -
   2.185 -def get_type_describer(t) -> TypeDescriber:
   2.186 -    if type(t).__name__ == '_GenericAlias':
   2.187 -        try:
   2.188 -            _args = t.__args__
   2.189 -
   2.190 -        except AttributeError:
   2.191 -            raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
   2.192 -                            f'типа "_GenericAlias": {t}')
   2.193 -
   2.194 -        if t.__name__ == 'List':
   2.195 -            try:
   2.196 -                _t = _args[0]
   2.197 -
   2.198 -            except IndexError:
   2.199 -                raise ValueError(f'Тип {t} не содержит в себе типа своих элементов')
   2.200 -
   2.201 -            return TypeDescriber(
   2.202 -                name=f'{t.__name__}[{_t.__name__}]',
   2.203 -                cast=lambda x: list(cast_iterator(get_type_describer(_t).cast, x)),
   2.204 -                like=list,
   2.205 -                is_complex=True
   2.206 -            )
   2.207 -
   2.208 -        elif t.__name__ == 'Tuple':
   2.209 -            if not _args:
   2.210 -                raise ValueError(f'Тип {t} не содержит в себе пояснений по составу хранящихся в нём типов')
   2.211 -
   2.212 -            if len(_args) == 1:
   2.213 -                _t = _args[0]
   2.214 -
   2.215 -                return TypeDescriber(
   2.216 -                    name=f'Tuple[{_t.__name__}]',
   2.217 -                    cast=lambda x: list(cast_iterator(get_type_describer(_t).cast, x)),
   2.218 -                    like=tuple,
   2.219 -                    is_complex=True
   2.220 -                )
   2.221 -
   2.222 -            else:
   2.223 -                _name = ', '.join(map(lambda x: x.__name__, _args))
   2.224 -                _cast_args = tuple(get_type_describer(i) for i in _args)
   2.225 -
   2.226 -                return TypeDescriber(
   2.227 -                    name=f'Tuple[{_name}]',
   2.228 -                    cast=lambda x: multi_item_tuple(_cast_args, x),
   2.229 -                    like=tuple,
   2.230 -                    is_complex=True
   2.231 -                )
   2.232 -
   2.233 -        elif t.__name__ == 'Dict':
   2.234 -            if len(_args) != 2:
   2.235 -                raise ValueError(f'Неожиданное количество значений типа в составном типе Dict: '
   2.236 -                                 f'{len(_args)} values=({_args})')
   2.237 -
   2.238 -            _name = ', '.join(map(lambda x: x.__name__, _args))
   2.239 -
   2.240 -            return TypeDescriber(
   2.241 -                name=f'Dict[{_name}]',
   2.242 -                cast=lambda x: dict_processor(_args, x),
   2.243 -                like=dict,
   2.244 -                is_complex=True
   2.245 -            )
   2.246 -
   2.247 -        else:
   2.248 -            raise ValueError(f'Неизвестный представитель типа "_GenericAlias": {t.__name__}')
   2.249 -
   2.250 -    elif type(t).__name__ == '_UnionGenericAlias':
   2.251 -        if t.__name__ not in ('Union', 'Optional'):
   2.252 -            raise TypeError(f'Неизвестный подтип _UnionGenericAlias: {t.__name__}')
   2.253 -
   2.254 -        try:
   2.255 -            _args = t.__args__
   2.256 -
   2.257 -        except AttributeError:
   2.258 -            raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
   2.259 -                            f'типа "_UnionGenericAlias": {t}')
   2.260 -
   2.261 -        if len(_args) == 0:
   2.262 -            raise ValueError('Не указан ни один тип в конструкции Union')
   2.263 -
   2.264 -        _cast_args = tuple(map(get_type_describer, [ i for i in _args if i is not None]))
   2.265 -        _args_name = ', '.join(map(lambda x: x.__name__, _args))
   2.266 -
   2.267 -        return TypeDescriber(
   2.268 -            name=f'Union[{_args_name}]',
   2.269 -            cast=lambda x: union_processor(_cast_args, x),
   2.270 -            like=None
   2.271 -        )
   2.272 -
   2.273 -    else:
   2.274 -        return TypeDescriber(
   2.275 -            name=t.__name__,
   2.276 -            cast=t,
   2.277 -            like=t
   2.278 -        )
   2.279 -
   2.280 -
   2.281 -def dataobj_extract(obj: Union[object, Dict[str, Any]], dataclass_type: type) -> object:
   2.282 -    """\
   2.283 -    Извлекает объект данных из предоставленного объекта, путём получения из него
   2.284 -    указанных в классе данных аттрибутов и поиска их в данном объекте.
   2.285 -    """
   2.286 -
   2.287 -    params = {}
   2.288 -
   2.289 -    if isinstance(obj, dict):
   2.290 -        _has = _dict_has
   2.291 -        _get = _dict_get
   2.292 -
   2.293 -    else:
   2.294 -        _has = _obj_has
   2.295 -        _get = _obj_get
   2.296 -
   2.297 -    if not is_dataclass(dataclass_type):
   2.298 -        raise ValueError(f'Не относится к классам данных: {dataclass_type.__name__}')
   2.299 -
   2.300 -    for fld in fields(dataclass_type):
   2.301 -        if _has(obj, fld.name):
   2.302 -            val = _get(obj, fld.name)
   2.303 -            type_desc = get_type_describer(fld.type)
   2.304 -            if val is not None:
   2.305 -                try:
   2.306 -                    val = type_desc(val)
   2.307 -
   2.308 -                except (ValueError, TypeError) as e:
   2.309 -                    raise ValueError(f'Аттрибут {fld.name} не может быть получен из значения "{val}"'
   2.310 -                                     f' с типом {type(val).__name__} поскольку не может быть преобразован в'
   2.311 -                                     f' тип {type_desc}, заданный в классе данных: {e}')
   2.312 -
   2.313 -            params[fld.name] = val
   2.314 -
   2.315 -    try:
   2.316 -        res = dataclass_type(**params)
   2.317 -
   2.318 -    except (ValueError, TypeError) as e:
   2.319 -        _params = ', '.join(map(lambda x: f'{x[0]}="{x[1]}"', params.items()))
   2.320 -        raise ValueError(f'Не удалось получить объект'
   2.321 -                         f' класс {dataclass_type.__name__}'
   2.322 -                         f' из параметров: {_params}'
   2.323 -                         f' ошибка: {e}')
   2.324 -
   2.325 -    return res
   2.326 -
   2.327 -
   2.328 -def json_type_sanitizer(val):
   2.329 -    """\
   2.330 -    Преобразует значение ``val`` в пригодное для преобразования в json значение.
   2.331 -    """
   2.332 -
   2.333 -    val_t = type(val)
   2.334 -
   2.335 -    if is_dataclass(val):
   2.336 -        return json_type_sanitizer(asdict(val))
   2.337 -
   2.338 -    elif val_t in (int, float, str, bool) or val is None:
   2.339 -        return val
   2.340 -
   2.341 -    elif val_t in (list, tuple):
   2.342 -        return list(map(json_type_sanitizer, val))
   2.343 -
   2.344 -    elif val_t == dict:
   2.345 -        return dict((key, json_type_sanitizer(d_val)) for key, d_val in val.items())
   2.346 -
   2.347 -    else:
   2.348 -        return str(val)
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/type_utils/__init__.py	Sat Aug 20 23:56:16 2022 +0300
     3.3 @@ -0,0 +1,5 @@
     3.4 +# coding: utf-8
     3.5 +"""\
     3.6 +Модуль, выполняющий различные операции над сложными типами. Должен быть вставлен в проект полностью, без изъятий.
     3.7 +"""
     3.8 +
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/type_utils/config_parse_helper.py	Sat Aug 20 23:56:16 2022 +0300
     4.3 @@ -0,0 +1,272 @@
     4.4 +# coding: utf-8
     4.5 +"""\
     4.6 +Получение из конфигурационного файла параметров в виде класса данных.
     4.7 +
     4.8 +НЕ РАБОТАЕТ БЕЗ ОСТАЛЬНОГО МОДУЛЯ type_utils
     4.9 +"""
    4.10 +
    4.11 +from configparser import ConfigParser
    4.12 +from dataclasses import is_dataclass, fields, Field, MISSING
    4.13 +from typing import Iterable, Optional, Dict, Any, List, Callable, Union
    4.14 +from threading import RLock
    4.15 +from os import getenv
    4.16 +
    4.17 +from .type_descriptor import get_type_describer
    4.18 +
    4.19 +
    4.20 +class ConfigParseHelperError(Exception):
    4.21 +    """\
    4.22 +    Ошибки в модуле помощника разборщика конфигураций
    4.23 +    """
    4.24 +
    4.25 +
    4.26 +class NoSectionNotification(ConfigParseHelperError):
    4.27 +    """\
    4.28 +    Оповещение об отсутствующей секции конфигурации
    4.29 +    """
    4.30 +
    4.31 +
    4.32 +class CPHSectionBase:
    4.33 +    """\
    4.34 +    Базовый класс обработки секции конфигурационного файла
    4.35 +    """
    4.36 +
    4.37 +    def get(self, config_prop_name: str, dc_prop_name: str):
    4.38 +        """\
    4.39 +        Получить свойство из конфигурационного файла
    4.40 +        """
    4.41 +        raise NotImplemented()
    4.42 +
    4.43 +    def __enter__(self):
    4.44 +        return self
    4.45 +
    4.46 +    def __exit__(self, exc_type, exc_val, exc_tb):
    4.47 +        raise NotImplemented()
    4.48 +
    4.49 +
    4.50 +class CPHAbsentSection(CPHSectionBase):
    4.51 +    """\
    4.52 +    Класс создаваемый на отсутствующую секцию конфигурационного файла
    4.53 +    """
    4.54 +    def get(self, config_prop_name: str, dc_prop_name: str):
    4.55 +        raise NoSectionNotification()
    4.56 +
    4.57 +    def __exit__(self, exc_type, exc_val, exc_tb):
    4.58 +        if exc_type == NoSectionNotification:
    4.59 +            return True
    4.60 +
    4.61 +
    4.62 +class CPHParamGetter(CPHSectionBase):
    4.63 +    def __init__(self, parser_helper_object):
    4.64 +        self.ph = parser_helper_object
    4.65 +        self.params = {}
    4.66 +
    4.67 +    def _add_param(self, param_name: str, param_val: Any, parser: Optional[Callable[[Any], Any]] = None):
    4.68 +        """\
    4.69 +        Непосредственное добавление полученного параметра со всеми проверками.
    4.70 +        """
    4.71 +
    4.72 +        if parser is not None and param_val is not None:
    4.73 +            param_val = parser(param_val)
    4.74 +
    4.75 +        fld = self.ph.fields.get(param_name)
    4.76 +        if not isinstance(fld, Field):
    4.77 +            raise ConfigParseHelperError(f'В классе данных отсутствует свойство "{param_name}", '
    4.78 +                                         f'которое мы должны заполнить из параметра конфигурации: {fld}')
    4.79 +
    4.80 +        if param_val is not None:
    4.81 +            type_desc = get_type_describer(fld.type)
    4.82 +            try:
    4.83 +                res = type_desc(param_val)
    4.84 +
    4.85 +            except (ValueError, TypeError) as e:
    4.86 +                raise ConfigParseHelperError(f'При приведении параметра к '
    4.87 +                                             f'заданному типу произошла ошибка: '
    4.88 +                                             f'значение="{param_val}" ошибка="{e}"')
    4.89 +
    4.90 +        else:
    4.91 +            if fld.default is not MISSING:
    4.92 +                res = fld.default
    4.93 +
    4.94 +            elif fld.default_factory is not MISSING:
    4.95 +                res = fld.default_factory()
    4.96 +
    4.97 +            else:
    4.98 +                raise ConfigParseHelperError('В конфигурации не заданна обязательная опция')
    4.99 +
   4.100 +        self.params[param_name] = res
   4.101 +
   4.102 +    def __exit__(self, exc_type, exc_val, exc_tb):
   4.103 +        if exc_type is None:
   4.104 +            self.ph.add_params(self.params)
   4.105 +
   4.106 +    def get(self, config_prop_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None):
   4.107 +        raise NoSectionNotification()
   4.108 +
   4.109 +
   4.110 +class CPHSection(CPHParamGetter):
   4.111 +    """\
   4.112 +    Класс производящий разбор конкретной секции конфигурации
   4.113 +    """
   4.114 +
   4.115 +    def __init__(self, parser_helper_object, section: str):
   4.116 +        super().__init__(parser_helper_object)
   4.117 +        self.section_name = section
   4.118 +        self.section = parser_helper_object.conf_parser[section]
   4.119 +
   4.120 +    def get(self, config_prop_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None):
   4.121 +        """\
   4.122 +        :param config_prop_name: Имя опции в файле конфигурации
   4.123 +        :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию
   4.124 +        :param parser: Исполнимый обработчик значения, перед его помещением в конфигурацию
   4.125 +        """
   4.126 +        try:
   4.127 +            self._add_param(dc_prop_name, self.section.get(config_prop_name), parser)
   4.128 +
   4.129 +        except ConfigParseHelperError as e:
   4.130 +            raise ConfigParseHelperError(f'Ошибка при разборе параметра "{config_prop_name}" '
   4.131 +                                         f'в секции "{self.section_name}": {e}')
   4.132 +
   4.133 +
   4.134 +class CPHEnvParser(CPHParamGetter):
   4.135 +    """\
   4.136 +    Класс для разбора переменных окружения в том же ключе, что и файла конфигурации
   4.137 +    """
   4.138 +
   4.139 +    def get(self, env_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None):
   4.140 +        """\
   4.141 +        :param env_name: Имя переменной окружения, хранящей опцию
   4.142 +        :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию
   4.143 +        :param parser: Исполнимый обработчик значения, перед его помещением в конфигурацию
   4.144 +        """
   4.145 +
   4.146 +        try:
   4.147 +            self._add_param(dc_prop_name, getenv(env_name), parser)
   4.148 +
   4.149 +        except ConfigParseHelperError as e:
   4.150 +            raise ConfigParseHelperError(f'Ошибка при получении значения из переменной окружения "{env_name}": {e}')
   4.151 +
   4.152 +
   4.153 +class CPHObjectsListGetter:
   4.154 +    """\
   4.155 +    Помощник для случаев, когда в наборе секций хранится однотипный набор объектов
   4.156 +    """
   4.157 +    def __init__(self, config_object_class: type, config_parser: ConfigParser, sections: Iterable[str]):
   4.158 +        self.sections = sections
   4.159 +        self.conf_parser = config_parser
   4.160 +
   4.161 +        if not is_dataclass(config_object_class):
   4.162 +            raise ConfigParseHelperError(f'Представленный в качестве представления объекта конфигурации '
   4.163 +                                         f'класс не является классом данных: {config_object_class.__name__}')
   4.164 +
   4.165 +        self.res_obj = config_object_class
   4.166 +        self.fields = dict((fld.name, fld) for fld in fields(config_object_class))
   4.167 +        self.obj_list = []
   4.168 +        self.ident_list = []
   4.169 +
   4.170 +    def add_params(self, params: Dict[str, Any]):
   4.171 +        try:
   4.172 +            self.obj_list.append(self.res_obj(**params))
   4.173 +
   4.174 +        except (ValueError, TypeError) as e:
   4.175 +            raise ConfigParseHelperError(f'Ошибка создания объекта объекта конфигурации, '
   4.176 +                                         f'списка объектов конфигурации: {e}')
   4.177 +
   4.178 +    def get(self, config_prop_name: str, dc_prop_name: str, parser: Optional[Callable[[Any], Any]] = None):
   4.179 +        """\
   4.180 +        Подготавливаем список соответствия названий параметров в секции конкретным свойствам данного
   4.181 +        в помощник класса
   4.182 +
   4.183 +        :param config_prop_name: Имя опции в файле конфигурации
   4.184 +        :param dc_prop_name: Имя свойства в классе данных, хранящем конфигурацию
   4.185 +        :param parser: Исполнимый обработчик значения, перед его помещением в конфигурацию
   4.186 +        """
   4.187 +
   4.188 +        self.ident_list.append((config_prop_name, dc_prop_name, parser))
   4.189 +
   4.190 +    def get_config_objects(self) -> List[object]:
   4.191 +        for section in self.sections:
   4.192 +            try:
   4.193 +                with CPHSection(self, section) as section_helper:
   4.194 +                    for conf_prop, dc_prop, parser in self.ident_list:
   4.195 +                        section_helper.get(conf_prop, dc_prop, parser)
   4.196 +
   4.197 +            except ConfigParseHelperError as e:
   4.198 +                raise ConfigParseHelperError(f'Ошибка при разборе секции "{section}": {e}')
   4.199 +
   4.200 +        res = self.obj_list[:]
   4.201 +        self.obj_list.clear()
   4.202 +
   4.203 +        return res
   4.204 +
   4.205 +
   4.206 +class ConfigParseHelper:
   4.207 +    """\
   4.208 +    Помощник разбора конфигурации
   4.209 +    """
   4.210 +    def __init__(self, config_object_class: type, required_sections: Optional[Iterable[str]] = None):
   4.211 +        """\
   4.212 +        :param config_object_class: Dataclass, который мы подготовили как хранилище параметров конфигурации
   4.213 +        :param required_sections: Перечисление секций конфигурации, которые мы требуем, чтобы были в файле
   4.214 +        """
   4.215 +
   4.216 +        if required_sections is not None:
   4.217 +            self.req_sections = set(required_sections)
   4.218 +
   4.219 +        else:
   4.220 +            self.req_sections = set()
   4.221 +
   4.222 +        if not is_dataclass(config_object_class):
   4.223 +            raise ConfigParseHelperError(f'Представленный в качестве объекта конфигурации класс не является '
   4.224 +                                         f'классом данных: {config_object_class.__name__}')
   4.225 +
   4.226 +        self.res_obj = config_object_class
   4.227 +        self.fields = dict((fld.name, fld) for fld in fields(config_object_class))
   4.228 +        self.conf_parser: Optional[ConfigParser] = None
   4.229 +        self.config_params = {}
   4.230 +        self.config_params_lock = RLock()
   4.231 +
   4.232 +    def add_params(self, params: Dict[str, Any]):
   4.233 +        self.config_params_lock.acquire()
   4.234 +        try:
   4.235 +            self.config_params.update(params)
   4.236 +
   4.237 +        finally:
   4.238 +            self.config_params_lock.release()
   4.239 +
   4.240 +    def section(self, section_name: str) -> CPHSectionBase:
   4.241 +        if self.conf_parser is None:
   4.242 +            raise ConfigParseHelperError(f'Прежде чем приступать к разбору файла конфигурации стоит его загрузить')
   4.243 +
   4.244 +        if self.conf_parser.has_section(section_name):
   4.245 +            return CPHSection(self, section_name)
   4.246 +
   4.247 +        else:
   4.248 +            return CPHAbsentSection()
   4.249 +
   4.250 +    def load(self, filename: str):
   4.251 +        res = ConfigParser()
   4.252 +        try:
   4.253 +            res.read(filename)
   4.254 +
   4.255 +        except (TypeError, IOError, OSError, ValueError) as e:
   4.256 +            raise ConfigParseHelperError(f'Не удалось загрузить файл конфигурации: файл="{filename}" '
   4.257 +                                         f'ошибка="{e}"')
   4.258 +
   4.259 +        missing_sections = self.req_sections - set(res.sections())
   4.260 +
   4.261 +        if missing_sections:
   4.262 +            missing_sections = ', '.join(missing_sections)
   4.263 +            raise ConfigParseHelperError(f'В конфигурационном файле отсутствуют секции: {missing_sections}')
   4.264 +
   4.265 +        self.conf_parser = res
   4.266 +
   4.267 +    def get_config(self):
   4.268 +        try:
   4.269 +            return self.res_obj(**self.config_params)
   4.270 +
   4.271 +        except (ValueError, TypeError) as e:
   4.272 +            raise ConfigParseHelperError(f'Не удалось инициализировать объект конфигурации: {e}')
   4.273 +
   4.274 +    def get_sections(self):
   4.275 +        return self.conf_parser.sections()
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/type_utils/dataclass_utils.py	Sat Aug 20 23:56:16 2022 +0300
     5.3 @@ -0,0 +1,89 @@
     5.4 +# coding: utf-8
     5.5 +"""\
     5.6 +Наполнение заданного класса данных из предоставленного словаря или объекта.
     5.7 +
     5.8 +НЕ РАБОТАЕТ БЕЗ ОСТАЛЬНОГО МОДУЛЯ type_utils
     5.9 +"""
    5.10 +
    5.11 +from dataclasses import fields, is_dataclass, asdict
    5.12 +from typing import Union, Dict, Any, Iterable
    5.13 +from .type_descriptor import get_type_describer
    5.14 +
    5.15 +
    5.16 +def _dict_has(obj: Dict[str, Any], key: str) -> bool:
    5.17 +    return key in obj
    5.18 +
    5.19 +
    5.20 +def _dict_get(obj: Dict[str, Any], key: str) -> Any:
    5.21 +    return obj[key]
    5.22 +
    5.23 +
    5.24 +def _obj_has(obj: object, key: str) -> bool:
    5.25 +    return hasattr(obj, key)
    5.26 +
    5.27 +
    5.28 +def _obj_get(obj: object, key: str) -> Any:
    5.29 +    return getattr(obj, key)
    5.30 +
    5.31 +
    5.32 +def dataobj_extract(obj: Union[object, Dict[str, Any]], dataclass_type: type, type_convert: bool = False) -> object:
    5.33 +    """\
    5.34 +    Извлекает объект данных из предоставленного объекта, путём получения из него
    5.35 +    указанных в классе данных аттрибутов и поиска их в данном объекте.
    5.36 +    :param obj: Объект, из которого берутся данные для класса данных
    5.37 +    :param dataclass_type: Класс данных, наполняемый из ``obj``
    5.38 +    :param type_convert: Признак конвертирования данных. Если задан, выполняет попытку сконвертировать имеющееся
    5.39 +                         в параметре значение в тип, указанных в классе данных. Чудес не бывает, и процесс может
    5.40 +                         ошибиться особенно с Union
    5.41 +    """
    5.42 +
    5.43 +    params = {}
    5.44 +
    5.45 +    if isinstance(obj, dict):
    5.46 +        _has = _dict_has
    5.47 +        _get = _dict_get
    5.48 +
    5.49 +    else:
    5.50 +        _has = _obj_has
    5.51 +        _get = _obj_get
    5.52 +
    5.53 +    if not is_dataclass(dataclass_type):
    5.54 +        raise ValueError(f'Не относится к классам данных: {dataclass_type.__name__}')
    5.55 +
    5.56 +    for fld in fields(dataclass_type):
    5.57 +        if _has(obj, fld.name):
    5.58 +            val = _get(obj, fld.name)
    5.59 +            type_desc = get_type_describer(fld.type)
    5.60 +            if val is not None:
    5.61 +                if not type_desc.check(val):
    5.62 +                    if not type_convert:
    5.63 +                        raise ValueError(f'Аттрибут {fld.name} не может быть получен из значения "{val}"'
    5.64 +                                         f' с типом {type(val).__name__} поскольку не может быть преобразован в'
    5.65 +                                         f' тип {type_desc}, заданный в классе данных: {type_desc.event_description}')
    5.66 +
    5.67 +                    try:
    5.68 +                        val = type_desc(val)
    5.69 +
    5.70 +                    except (ValueError, TypeError) as e:
    5.71 +                        raise ValueError(f'Аттрибут {fld.name} не может быть получен из значения "{val}"'
    5.72 +                                         f' с типом {type(val).__name__} поскольку не может быть преобразован в'
    5.73 +                                         f' тип {type_desc}, заданный в классе данных: {e}')
    5.74 +
    5.75 +            elif not (type_desc.is_nullable or type_convert):
    5.76 +                raise ValueError(f'Передан "None" в поле "{fld.name}", хотя он не ожидался')
    5.77 +
    5.78 +            params[fld.name] = val
    5.79 +
    5.80 +    try:
    5.81 +        res = dataclass_type(**params)
    5.82 +
    5.83 +    except (ValueError, TypeError) as e:
    5.84 +        _params = ', '.join(map(lambda x: f'{x[0]}="{x[1]}"', params.items()))
    5.85 +        raise ValueError(f'Не удалось получить объект'
    5.86 +                         f' класс {dataclass_type.__name__}'
    5.87 +                         f' из параметров: {_params}'
    5.88 +                         f' ошибка: {e}')
    5.89 +
    5.90 +    return res
    5.91 +
    5.92 +
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/type_utils/json_tools.py	Sat Aug 20 23:56:16 2022 +0300
     6.3 @@ -0,0 +1,25 @@
     6.4 +# coding: utf-8
     6.5 +from dataclasses import is_dataclass, asdict
     6.6 +
     6.7 +
     6.8 +def json_type_sanitizer(val):
     6.9 +    """\
    6.10 +    Преобразует значение ``val`` в пригодное для преобразования в json значение.
    6.11 +    """
    6.12 +
    6.13 +    val_t = type(val)
    6.14 +
    6.15 +    if is_dataclass(val):
    6.16 +        return json_type_sanitizer(asdict(val))
    6.17 +
    6.18 +    elif val_t in (int, float, str, bool) or val is None:
    6.19 +        return val
    6.20 +
    6.21 +    elif val_t in (list, tuple):
    6.22 +        return list(map(json_type_sanitizer, val))
    6.23 +
    6.24 +    elif val_t == dict:
    6.25 +        return dict((key, json_type_sanitizer(d_val)) for key, d_val in val.items())
    6.26 +
    6.27 +    else:
    6.28 +        return str(val)
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/type_utils/type_descriptor.py	Sat Aug 20 23:56:16 2022 +0300
     7.3 @@ -0,0 +1,367 @@
     7.4 +# coding: utf-8
     7.5 +
     7.6 +class TypeDescriptorInterface:
     7.7 +    """\
     7.8 +    Определяем общий интерфейс для описателей типов
     7.9 +    """
    7.10 +    def __init__(self, name, type_class, is_nullable=False):
    7.11 +        self.__name__ = name
    7.12 +        self.t_class = type_class
    7.13 +        self.is_nullable = is_nullable
    7.14 +        self.event_description = ''
    7.15 +        self.union_sort_order = 3
    7.16 +
    7.17 +    def raise_nullable(self, msg=''):
    7.18 +        msg = msg if not msg else f': {msg}'
    7.19 +
    7.20 +        if not self.is_nullable:
    7.21 +            raise ValueError(f'Передача значения "None" переменной, которая этого не ожидает{msg}')
    7.22 +
    7.23 +    def __call__(self, val):
    7.24 +        """\
    7.25 +        Реализация приведения типа
    7.26 +        """
    7.27 +        raise NotImplemented()
    7.28 +
    7.29 +    def __repr__(self):
    7.30 +        if self.t_class is None:
    7.31 +            return self.__name__
    7.32 +
    7.33 +        else:
    7.34 +            return f'{self.__name__}({self.t_class.__name__})'
    7.35 +
    7.36 +    def check(self, val):
    7.37 +        """\
    7.38 +        Реализация проверки соответствия типу
    7.39 +        """
    7.40 +        raise NotImplemented()
    7.41 +
    7.42 +
    7.43 +class BaseTypeDescriptor(TypeDescriptorInterface):
    7.44 +    """\
    7.45 +    Базовый класс, объявляющий общий интерфейс классов адаптеров проверки и преобразования типов
    7.46 +    """
    7.47 +    def check(self, val):
    7.48 +        event_description = ''
    7.49 +        res = False
    7.50 +        if val is None:
    7.51 +            event_description = 'Передано значение "None"'
    7.52 +            res = self.is_nullable
    7.53 +
    7.54 +        elif isinstance(self.t_class, TypeDescriptorInterface):
    7.55 +            res = self.t_class.check(val)
    7.56 +            event_description = self.t_class.event_description
    7.57 +
    7.58 +        else:
    7.59 +            event_description = f'Требуется тип "{self.t_class.__name__}" а получаем "{type(val).__name__}"'
    7.60 +            res = isinstance(val, self.t_class)
    7.61 +
    7.62 +        if not res:
    7.63 +            self.event_description = event_description
    7.64 +
    7.65 +        return res
    7.66 +
    7.67 +    def check_fail(self, msg):
    7.68 +        self.event_description = msg
    7.69 +        return False
    7.70 +
    7.71 +
    7.72 +class ScalarTypeDescriptor(BaseTypeDescriptor):
    7.73 +    """\
    7.74 +    Реализация адаптера над простыми типами
    7.75 +    """
    7.76 +    def __init__(self, type_class, is_nullable=False):
    7.77 +        super().__init__(type_class.__name__, type_class, is_nullable)
    7.78 +        if type_class in (int, float, bool):
    7.79 +            self.union_sort_order = 0
    7.80 +
    7.81 +        elif type_class == str:
    7.82 +            self.union_sort_order = 2
    7.83 +
    7.84 +        else:
    7.85 +            self.union_sort_order = 1
    7.86 +
    7.87 +    def __call__(self, val):
    7.88 +        if val is None:
    7.89 +            return self.raise_nullable()
    7.90 +
    7.91 +        if self.check(val):
    7.92 +            return val
    7.93 +
    7.94 +        return self.t_class(val)
    7.95 +
    7.96 +
    7.97 +class IterableTypeDescriptor(BaseTypeDescriptor):
    7.98 +    """\
    7.99 +    Реализация адаптера над последовательностями
   7.100 +    """
   7.101 +    def __init__(self, iterator_class, value_class, is_nullable=False):
   7.102 +        super().__init__(
   7.103 +            name=f'{iterator_class.__name__}[{value_class.__name__}]',
   7.104 +            type_class=value_class,
   7.105 +            is_nullable=is_nullable
   7.106 +        )
   7.107 +
   7.108 +        self.iterator_class = iterator_class
   7.109 +
   7.110 +    def check(self, vals):
   7.111 +        idx = 0
   7.112 +        for val in vals:
   7.113 +            if not super().check(val):
   7.114 +                return self.check_fail(f'Элемент {idx}: {self.event_description}')
   7.115 +
   7.116 +            else:
   7.117 +                idx += 1
   7.118 +
   7.119 +        return True
   7.120 +
   7.121 +    def __call__(self, vals):
   7.122 +        if vals is None:
   7.123 +            return self.raise_nullable()
   7.124 +
   7.125 +        res = []
   7.126 +        for val in vals:
   7.127 +            if val is None:
   7.128 +                self.raise_nullable(f'Элемент "{len(res)}"')
   7.129 +
   7.130 +            try:
   7.131 +                res.append(self.t_class(val))
   7.132 +
   7.133 +            except (TypeError, ValueError) as e:
   7.134 +                raise ValueError(f'Не удалось преобразовать элемент "{len(res)}" со значением "{val}" '
   7.135 +                                 f'в результирующий тип: {e}')
   7.136 +
   7.137 +        return self.iterator_class(res)
   7.138 +
   7.139 +
   7.140 +class TupleTypeDescriptor(BaseTypeDescriptor):
   7.141 +    """\
   7.142 +    Адаптер над кортежами
   7.143 +    """
   7.144 +    def __init__(self, type_classes, is_nullable=False):
   7.145 +        if not isinstance(type_classes, (list, tuple)) or not type_classes:
   7.146 +            raise TypeError(f'В конструктор прокси для обработки кортежей не передано типов кортежа')
   7.147 +
   7.148 +        names = ', '.join([i.__name__ for i in type_classes])
   7.149 +
   7.150 +        super().__init__(f'Tuple[{names}]', None, is_nullable)
   7.151 +
   7.152 +        self.type_classes = tuple(map(
   7.153 +            lambda x: x if isinstance(x, TypeDescriptorInterface) else ScalarTypeDescriptor(x),
   7.154 +            type_classes
   7.155 +        ))
   7.156 +
   7.157 +    def check(self, vals):
   7.158 +        if not isinstance(vals, tuple):
   7.159 +            return self.check_fail(f'Переданная переменная не является кортежем, а относится к типу: '
   7.160 +                                   f'{type(vals).__name__}')
   7.161 +
   7.162 +        if len(vals) != len(self.type_classes):
   7.163 +            return self.check_fail(f'Не достаточно элементов в кортеже: '
   7.164 +                                   f'имеется={len(vals)} нужно={len(self.type_classes)}')
   7.165 +
   7.166 +        for i in range(len(self.type_classes)):
   7.167 +            if not self.type_classes[i].check(vals[i]):
   7.168 +                return self.check_fail(f'Элемент {i}, значение "{vals[i]}": '
   7.169 +                                       f'{self.type_classes[i].event_description}')
   7.170 +
   7.171 +    def __call__(self, vals):
   7.172 +        if vals is None:
   7.173 +            return self.raise_nullable()
   7.174 +
   7.175 +        if not isinstance(vals, tuple):
   7.176 +            raise ValueError(f'Переданная переменная не является кортежем, а относится к типу: '
   7.177 +                             f'{type(vals).__name__}')
   7.178 +
   7.179 +        if len(vals) != len(self.type_classes):
   7.180 +            raise ValueError(f'Не достаточно элементов в кортеже: '
   7.181 +                             f'имеется={len(vals)} нужно={len(self.type_classes)}')
   7.182 +
   7.183 +        res = []
   7.184 +        for i in range(len(self.type_classes)):
   7.185 +            try:
   7.186 +                res.append(self.type_classes[i](vals[i]))
   7.187 +
   7.188 +            except (ValueError, TypeError) as e:
   7.189 +                raise ValueError(f'Не удалось привести к результирующему виду '
   7.190 +                                 f'элемент {i} со значением "{vals[i]}": {e}')
   7.191 +
   7.192 +        return tuple(res)
   7.193 +
   7.194 +
   7.195 +class UnionTypeDescriptor(BaseTypeDescriptor):
   7.196 +    """\
   7.197 +    Адаптер для объединения типов
   7.198 +    """
   7.199 +    def __init__(self, type_classes, is_nullable=False):
   7.200 +        if not isinstance(type_classes, (list, tuple)) or not type_classes:
   7.201 +            raise TypeError(f'В конструктор прокси для обработки объединений типов не передано типов')
   7.202 +
   7.203 +        names = ', '.join([i.__name__ for i in type_classes])
   7.204 +
   7.205 +        super().__init__(f'Union[{names}]', None, is_nullable)
   7.206 +
   7.207 +        self.type_classes = tuple(sorted(map(
   7.208 +            lambda x: x if isinstance(x, BaseTypeDescriptor) else ScalarTypeDescriptor(x),
   7.209 +            type_classes
   7.210 +        ), key=lambda x: x.union_sort_order))
   7.211 +
   7.212 +    def check(self, val):
   7.213 +        for t in self.type_classes:
   7.214 +            if t.check(val):
   7.215 +                return True
   7.216 +
   7.217 +        self.event_description = f'Значение "{val}" типа "{type(val).__name__}" не соответствует ' \
   7.218 +                                 f'ни одному типу из моего набора'
   7.219 +        return False
   7.220 +
   7.221 +    def __call__(self, val):
   7.222 +        if val is None:
   7.223 +            return self.raise_nullable()
   7.224 +
   7.225 +        if self.check(val):
   7.226 +            return val
   7.227 +
   7.228 +        errs = []
   7.229 +
   7.230 +        for t in self.type_classes:
   7.231 +            try:
   7.232 +                return t(val)
   7.233 +
   7.234 +            except (TypeError, ValueError) as e:
   7.235 +                errs.append(f'{repr(t)}: {e}')
   7.236 +
   7.237 +        raise ValueError(f'Не удалось преобразовать значение "{val}" типа "{type(val).__name__}" '
   7.238 +                         f'ни к одному из имеющихся типов: ' + '\n'.join(errs))
   7.239 +
   7.240 +
   7.241 +class DictTypeDescriptor(BaseTypeDescriptor):
   7.242 +    """\
   7.243 +    Адаптер словарей
   7.244 +    """
   7.245 +    def __init__(self, key_class, value_class, is_nullable=False):
   7.246 +        super().__init__(f'Dict[{key_class.__name__}, {value_class.__name__}]', None, is_nullable)
   7.247 +
   7.248 +        if isinstance(key_class, BaseTypeDescriptor):
   7.249 +            self.key_class = key_class
   7.250 +
   7.251 +        else:
   7.252 +            self.key_class = ScalarTypeDescriptor(key_class)
   7.253 +
   7.254 +        if isinstance(value_class, BaseTypeDescriptor):
   7.255 +            self.value_class = value_class
   7.256 +
   7.257 +        else:
   7.258 +            self.value_class = ScalarTypeDescriptor(value_class)
   7.259 +
   7.260 +    def check(self, val):
   7.261 +        try:
   7.262 +            d = dict(val)
   7.263 +
   7.264 +        except (TypeError, ValueError) as e:
   7.265 +            self.event_description = f'Не удалось преобразовать переданное значение в словарь: {e}'
   7.266 +            return False
   7.267 +
   7.268 +        for k, v in d.items():
   7.269 +            if not self.key_class.check(k):
   7.270 +                return self.check_fail(f'В паре ["{k}": "{v}"] ключ не соответствует ожидаемому типу: ' 
   7.271 +                                       f'{self.key_class.event_description}')
   7.272 +
   7.273 +            if not self.value_class.check(v):
   7.274 +                return self.check_fail(f'В паре ["{k}": "{v}"] значение не соответствует ожидаемому типу: '
   7.275 +                                       f'{self.value_class.event_description}')
   7.276 +
   7.277 +        return True
   7.278 +
   7.279 +    def __call__(self, val):
   7.280 +        try:
   7.281 +            d = dict(val)
   7.282 +
   7.283 +        except (TypeError, ValueError) as e:
   7.284 +            raise ValueError(f'Не удалось преобразовать переданное значение в словарь: {e}')
   7.285 +
   7.286 +        p = []
   7.287 +        for k, v in d.items():
   7.288 +            p.append((self.key_class(k), self.value_class(v)))
   7.289 +
   7.290 +        return dict(p)
   7.291 +
   7.292 +
   7.293 +def get_type_describer(t) -> BaseTypeDescriptor:
   7.294 +    if type(t).__name__ == '_GenericAlias':
   7.295 +        try:
   7.296 +            _args = t.__args__
   7.297 +
   7.298 +        except AttributeError:
   7.299 +            raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
   7.300 +                            f'типа "_GenericAlias": {t}')
   7.301 +
   7.302 +        if t.__name__ == 'List':
   7.303 +            try:
   7.304 +                _t = _args[0]
   7.305 +
   7.306 +            except IndexError:
   7.307 +                raise ValueError(f'Тип {t} не содержит в себе типа своих элементов')
   7.308 +
   7.309 +            return IterableTypeDescriptor(
   7.310 +                iterator_class=list,
   7.311 +                value_class=get_type_describer(_t)
   7.312 +            )
   7.313 +
   7.314 +        elif t.__name__ == 'Tuple':
   7.315 +            if not _args:
   7.316 +                raise ValueError(f'Тип {t} не содержит в себе пояснений по составу хранящихся в нём типов')
   7.317 +
   7.318 +            if len(_args) == 1:
   7.319 +                _t = _args[0]
   7.320 +
   7.321 +                return IterableTypeDescriptor(
   7.322 +                    iterator_class=tuple,
   7.323 +                    value_class=get_type_describer(_t)
   7.324 +                )
   7.325 +
   7.326 +            else:
   7.327 +                return TupleTypeDescriptor(
   7.328 +                    type_classes=_args
   7.329 +                )
   7.330 +
   7.331 +        elif t.__name__ == 'Dict':
   7.332 +            if len(_args) != 2:
   7.333 +                raise ValueError(f'Неожиданное количество значений типа в составном типе Dict: '
   7.334 +                                 f'{len(_args)} values=({_args})')
   7.335 +
   7.336 +            return DictTypeDescriptor(
   7.337 +                key_class=get_type_describer(_args[0]),
   7.338 +                value_class=get_type_describer(_args[1])
   7.339 +            )
   7.340 +
   7.341 +        else:
   7.342 +            raise ValueError(f'Неизвестный представитель типа "_GenericAlias": {t.__name__}')
   7.343 +
   7.344 +    elif type(t).__name__ == '_UnionGenericAlias':
   7.345 +        if t.__name__ not in ('Union', 'Optional'):
   7.346 +            raise TypeError(f'Неизвестный подтип _UnionGenericAlias: {t.__name__}')
   7.347 +
   7.348 +        nullable = False
   7.349 +        if t.__name__ == 'Optional':
   7.350 +            nullable = True
   7.351 +
   7.352 +        try:
   7.353 +            _args = t.__args__
   7.354 +
   7.355 +        except AttributeError:
   7.356 +            raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
   7.357 +                            f'типа "_UnionGenericAlias": {t}')
   7.358 +
   7.359 +        if len(_args) == 0:
   7.360 +            raise ValueError('Не указан ни один тип в конструкции Union')
   7.361 +
   7.362 +        type_classes = tuple(map(lambda x: get_type_describer(x), [ i for i in _args if i is not None]))
   7.363 +
   7.364 +        return UnionTypeDescriptor(
   7.365 +            type_classes=type_classes,
   7.366 +            is_nullable=nullable
   7.367 +        )
   7.368 +
   7.369 +    else:
   7.370 +        return ScalarTypeDescriptor(type_class=t)