py.lib
py.lib/config_parse_helper.py
. Рефакторинг имени в dataclass_utils.py * Изменение по типу модуля dataclass_utils.py в модуле config_parse_helper.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: