py.lib

Yohn Y. 2022-08-19 Parent:ab4cf9f4f10a Child:ae0107755941

36:f1a05e880961 Browse Files

. Рефакторинг имени в dataclass_utils.py * Изменение по типу модуля dataclass_utils.py в модуле config_parse_helper.py

config_parse_helper.py dataclass_utils.py

     1.1 --- a/config_parse_helper.py	Thu Aug 18 23:57:48 2022 +0300
     1.2 +++ b/config_parse_helper.py	Fri Aug 19 00:07:55 2022 +0300
     1.3 @@ -1,7 +1,7 @@
     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
     1.8 +from typing import Iterable, Optional, Dict, Any, List, Callable, Union
     1.9  from threading import RLock
    1.10  from os import getenv
    1.11  
    1.12 @@ -18,50 +18,225 @@
    1.13      """
    1.14  
    1.15  
    1.16 -def difficult_type_recognizer(type_obj, value):
    1.17 +class TypeDescriber:
    1.18      """\
    1.19 -    Магия борющаяся с костылями type hinting
    1.20 +    Реализует паттерн "адаптер" поверх типов, для упрощения приведения значений типов к объявленным формам
    1.21      """
    1.22 -    if type(type_obj).__name__ == '_GenericAlias':
    1.23 -        if (
    1.24 -                hasattr(type_obj, '__args__')
    1.25 -                and type(type_obj.__args__) == tuple
    1.26 -                and type_obj.__args__
    1.27 -        ):
    1.28 -            if type_obj.__name__ == 'List':
    1.29 -                return list(map(type_obj.__args__[0], value))
    1.30 +    def __init__(self, name, cast, like, is_complex=False):
    1.31 +        self.__name__ = name
    1.32 +        self.cast = cast
    1.33 +        self.like = like
    1.34 +        self.is_complex = is_complex
    1.35 +
    1.36 +    def __instancecheck__(self, instance):
    1.37 +        if self.like is None:
    1.38 +            return False
    1.39  
    1.40 -            elif type_obj.__name__ == 'Tuple':
    1.41 -                return tuple(map(type_obj.__args__[0], value))
    1.42 +        else:
    1.43 +            return isinstance(instance, self.like)
    1.44  
    1.45 -            else:
    1.46 -                raise ValueError('Неизвестный тип')
    1.47 +    def __repr__(self):
    1.48 +        return f'<TypeDescriber({self.__name__}, {self.like})>'
    1.49 +
    1.50 +    def __call__(self, val):
    1.51 +        if val is None:
    1.52 +            return None
    1.53  
    1.54          else:
    1.55 -            ValueError('Неизвестный тип')
    1.56 +           return self.cast(val)
    1.57 +
    1.58 +
    1.59 +def cast_iterator(t: Union[type, TypeDescriber], lst: Iterable):
    1.60 +    """\
    1.61 +    Обрабатывает последовательности единого типа.
    1.62 +    """
    1.63 +    for i in lst:
    1.64 +        if isinstance(i, t):
    1.65 +            yield i
    1.66 +
    1.67 +        try:
    1.68 +            yield t(i)
    1.69 +
    1.70 +        except (TypeError, ValueError) as e:
    1.71 +            raise ValueError(f'Не удалось привести значение к нужному типу: тип={t.__name__}; знач={i}')
    1.72 +
    1.73 +
    1.74 +def multi_item_tuple(tt, val):
    1.75 +    """\
    1.76 +    Обрабатывает кортежи, состоящие из нескольких значений типов (кортежи элементов разных типов)
    1.77 +    :param tt: Последовательность составляющих кортеж типов
    1.78 +    :param val: итерируемый объект, хранящий значения в указанном порядке.
    1.79 +    """
    1.80 +    val = list(val)
    1.81 +    t_len = len(tt)
    1.82 +    if t_len == 0:
    1.83 +        raise ValueError('При вызове процедуры конвертации котежей, не были указаны типы')
    1.84 +
    1.85 +    if len(val) != t_len:
    1.86 +        raise ValueError(f'Значение не содержит положенных {t_len} элементов: {len(val)} - {val}')
    1.87 +
    1.88 +    res = []
    1.89 +
    1.90 +    for i in range(t_len):
    1.91 +        if isinstance(val[i], tt[i]):
    1.92 +            yield val[i]
    1.93 +
    1.94 +        try:
    1.95 +            res.append(tt[i](val[i]))
    1.96 +
    1.97 +        except (TypeError, ValueError) as e:
    1.98 +            raise ValueError(f'Не удалось привести значение к нужному типу: тип={tt[i].__name__}; знач={val[i]}')
    1.99 +
   1.100 +    return tuple(res)
   1.101 +
   1.102  
   1.103 -    elif type(type_obj).__name__ == '_UnionGenericAlias':
   1.104 -        if type_obj.__name__ not in ('Union', 'Optional'):
   1.105 -            raise TypeError(f'Неизвестный подтип _UnionGenericAlias: {type_obj.__name__}')
   1.106 +def union_processor(tt, val):
   1.107 +    """\
   1.108 +    Пытается привести значение к одному из указанных типов
   1.109 +    :param tt: список возможных типов
   1.110 +    :param val: приводимое значение
   1.111 +    """
   1.112 +    if val is None:
   1.113 +        return val
   1.114 +
   1.115 +    res = None
   1.116 +    ex = []
   1.117 +
   1.118 +    if len(tt) == 0:
   1.119 +        raise ValueError('Не указан ни один тип в составном типе Union')
   1.120 +
   1.121 +    for t in tt:
   1.122 +        try:
   1.123 +            res = t(val)
   1.124 +            break
   1.125 +
   1.126 +        except (TypeError, ValueError) as e:
   1.127 +            ex.append(f'{t}: {e}')
   1.128 +
   1.129 +    if res is None:
   1.130 +        raise ValueError('Не удалось привести значение не к одному из типов:\n' + '\n'.join(ex))
   1.131 +
   1.132 +    else:
   1.133 +        return res
   1.134 +
   1.135 +
   1.136 +def dict_processor(tt, val):
   1.137 +    if len(tt) != 2:
   1.138 +        raise ValueError(f'Попытка воссоздать словарь со странным количеством аргументов типа: {tt}')
   1.139 +
   1.140 +    try:
   1.141 +        _d = dict(val)
   1.142 +
   1.143 +    except (TypeError, ValueError) as e:
   1.144 +        raise ValueError(f'Не удалось воссоздать словарь из представленного значения: {e}')
   1.145 +
   1.146 +    _p = []
   1.147 +
   1.148 +    for k, v in _d.items():
   1.149 +        try:
   1.150 +            _p.append((tt[0](k), tt[1](v)))
   1.151 +
   1.152 +        except (TypeError, ValueError) as e:
   1.153 +            raise ValueError(f'Не удалось привести значения элемента словаря к требуемому типу: '
   1.154 +                             f'key="{k}" value="{v}"')
   1.155  
   1.156 -        if not (
   1.157 -            hasattr(type_obj, '__args__')
   1.158 -            and type(type_obj.__args__) == tuple
   1.159 -            and type_obj.__args__
   1.160 -        ):
   1.161 -            raise TypeError(f'Не ясно как работать с типом не вижу аргументов: {type_obj.__name__}')
   1.162 +    return dict(_p)
   1.163 +
   1.164 +
   1.165 +def get_type_describer(t) -> TypeDescriber:
   1.166 +    if type(t).__name__ == '_GenericAlias':
   1.167 +        try:
   1.168 +            _args = t.__args__
   1.169 +
   1.170 +        except AttributeError:
   1.171 +            raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
   1.172 +                            f'типа "_GenericAlias": {t}')
   1.173 +
   1.174 +        if t.__name__ == 'List':
   1.175 +            try:
   1.176 +                _t = _args[0]
   1.177 +
   1.178 +            except IndexError:
   1.179 +                raise ValueError(f'Тип {t} не содержит в себе типа своих элементов')
   1.180 +
   1.181 +            return TypeDescriber(
   1.182 +                name=f'{t.__name__}[{_t.__name__}]',
   1.183 +                cast=lambda x: list(cast_iterator(get_type_describer(_t).cast, x)),
   1.184 +                like=list,
   1.185 +                is_complex=True
   1.186 +            )
   1.187 +
   1.188 +        elif t.__name__ == 'Tuple':
   1.189 +            if not _args:
   1.190 +                raise ValueError(f'Тип {t} не содержит в себе пояснений по составу хранящихся в нём типов')
   1.191 +
   1.192 +            if len(_args) == 1:
   1.193 +                _t = _args[0]
   1.194 +
   1.195 +                return TypeDescriber(
   1.196 +                    name=f'Tuple[{_t.__name__}]',
   1.197 +                    cast=lambda x: list(cast_iterator(get_type_describer(_t).cast, x)),
   1.198 +                    like=tuple,
   1.199 +                    is_complex=True
   1.200 +                )
   1.201 +
   1.202 +            else:
   1.203 +                _name = ', '.join(map(lambda x: x.__name__, _args))
   1.204 +                _cast_args = tuple(get_type_describer(i) for i in _args)
   1.205  
   1.206 -        for _t in type_obj.__args__:
   1.207 -            if _t.__name__ == 'NoneType':
   1.208 -                continue
   1.209 +                return TypeDescriber(
   1.210 +                    name=f'Tuple[{_name}]',
   1.211 +                    cast=lambda x: multi_item_tuple(_cast_args, x),
   1.212 +                    like=tuple,
   1.213 +                    is_complex=True
   1.214 +                )
   1.215 +
   1.216 +        elif t.__name__ == 'Dict':
   1.217 +            if len(_args) != 2:
   1.218 +                raise ValueError(f'Неожиданное количество значений типа в составном типе Dict: '
   1.219 +                                 f'{len(_args)} values=({_args})')
   1.220 +
   1.221 +            _name = ', '.join(map(lambda x: x.__name__, _args))
   1.222 +
   1.223 +            return TypeDescriber(
   1.224 +                name=f'Dict[{_name}]',
   1.225 +                cast=lambda x: dict_processor(_args, x),
   1.226 +                like=dict,
   1.227 +                is_complex=True
   1.228 +            )
   1.229 +
   1.230 +        else:
   1.231 +            raise ValueError(f'Неизвестный представитель типа "_GenericAlias": {t.__name__}')
   1.232  
   1.233 -            try:
   1.234 -                return _t(value)
   1.235 +    elif type(t).__name__ == '_UnionGenericAlias':
   1.236 +        if t.__name__ not in ('Union', 'Optional'):
   1.237 +            raise TypeError(f'Неизвестный подтип _UnionGenericAlias: {t.__name__}')
   1.238 +
   1.239 +        try:
   1.240 +            _args = t.__args__
   1.241 +
   1.242 +        except AttributeError:
   1.243 +            raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
   1.244 +                            f'типа "_UnionGenericAlias": {t}')
   1.245 +
   1.246 +        if len(_args) == 0:
   1.247 +            raise ValueError('Не указан ни один тип в конструкции Union')
   1.248  
   1.249 -            except (TypeError, ValueError):
   1.250 -                continue
   1.251 +        _cast_args = tuple(map(get_type_describer, _args))
   1.252 +        _args_name = ', '.join(map(lambda x: x.__name__, _args))
   1.253  
   1.254 -        raise ValueError('Не удалось привести значение к типу')
   1.255 +        return TypeDescriber(
   1.256 +            name=f'Union[{_args_name}]',
   1.257 +            cast=lambda x: union_processor(_cast_args, x),
   1.258 +            like=None
   1.259 +        )
   1.260 +
   1.261 +    else:
   1.262 +        return TypeDescriber(
   1.263 +            name=t.__name__,
   1.264 +            cast=t,
   1.265 +            like=t
   1.266 +        )
   1.267  
   1.268  
   1.269  class CPHSectionBase:
   1.270 @@ -113,17 +288,14 @@
   1.271                                           f'которое мы должны заполнить из параметра конфигурации: {fld}')
   1.272  
   1.273          if param_val is not None:
   1.274 +            type_desc = get_type_describer(fld.type)
   1.275              try:
   1.276 -                res = fld.type(param_val)
   1.277 +                res = type_desc(param_val)
   1.278  
   1.279              except (ValueError, TypeError) as e:
   1.280 -                try:
   1.281 -                    res = difficult_type_recognizer(fld.type, param_val)
   1.282 -
   1.283 -                except (ValueError, TypeError):
   1.284 -                    raise ConfigParseHelperError(f'При приведении параметра к '
   1.285 -                                                 f'заданному типу произошла ошибка: '
   1.286 -                                                 f'значение="{param_val}" ошибка="{e}"')
   1.287 +                raise ConfigParseHelperError(f'При приведении параметра к '
   1.288 +                                             f'заданному типу произошла ошибка: '
   1.289 +                                             f'значение="{param_val}" ошибка="{e}"')
   1.290  
   1.291          else:
   1.292              if fld.default is not MISSING:
     2.1 --- a/dataclass_utils.py	Thu Aug 18 23:57:48 2022 +0300
     2.2 +++ b/dataclass_utils.py	Fri Aug 19 00:07:55 2022 +0300
     2.3 @@ -263,15 +263,15 @@
     2.4      for fld in fields(dataclass_type):
     2.5          if _has(obj, fld.name):
     2.6              val = _get(obj, fld.name)
     2.7 -            typedesc = get_type_describer(fld.type)
     2.8 -            if val is not None and not isinstance(val, typedesc):
     2.9 +            type_desc = get_type_describer(fld.type)
    2.10 +            if val is not None and not isinstance(val, type_desc):
    2.11                  try:
    2.12 -                    val = typedesc(val)
    2.13 +                    val = type_desc(val)
    2.14  
    2.15                  except (ValueError, TypeError) as e:
    2.16                      raise ValueError(f'Аттрибут {fld.name} не может быть получен из значения "{val}"'
    2.17                                       f' с типом {type(val).__name__} поскольку не может быть преобразован в'
    2.18 -                                     f' тип {typedesc}, заданный в классе данных: {e}')
    2.19 +                                     f' тип {type_desc}, заданный в классе данных: {e}')
    2.20  
    2.21              params[fld.name] = val
    2.22