py.lib
37:ae0107755941
Go to Latest
py.lib/dataclass_utils.py
. Исправление ошибок и рефакторинг
3 from dataclasses import fields, is_dataclass, asdict
4 from typing import Union, Dict, Any, Iterable
7 def _dict_has(obj: Dict[str, Any], key: str) -> bool:
11 def _dict_get(obj: Dict[str, Any], key: str) -> Any:
15 def _obj_has(obj: object, key: str) -> bool:
16 return hasattr(obj, key)
19 def _obj_get(obj: object, key: str) -> Any:
20 return getattr(obj, key)
25 Реализует паттерн "адаптер" поверх типов, для упрощения приведения значений типов к объявленным формам
27 def __init__(self, name, cast, like, is_complex=False):
31 self.is_complex = is_complex
33 def check(self, instance):
38 return check_instance(instance, self.like)
41 return f'<TypeDescriber({self.__name__}, {self.like})>'
43 def __call__(self, val):
47 elif not self.is_complex and check_instance(val, self.like):
54 def check_instance(obj, types):
58 elif isinstance(types, (tuple, list)):
61 if check_instance(obj, t):
67 elif isinstance(types, TypeDescriber):
68 return types.check(obj)
71 return isinstance(obj, types)
74 def cast_iterator(t: Union[type, TypeDescriber], lst: Iterable):
76 Обрабатывает последовательности единого типа.
79 if check_instance(i, t):
86 except (TypeError, ValueError) as e:
87 raise ValueError(f'Не удалось привести значение к нужному типу: '
88 f'тип={t.__name__}; знач={i}')
91 def multi_item_tuple(tt, val):
93 Обрабатывает кортежи, состоящие из нескольких значений типов (кортежи элементов разных типов)
94 :param tt: Последовательность составляющих кортеж типов
95 :param val: итерируемый объект, хранящий значения в указанном порядке.
100 raise ValueError('При вызове процедуры конвертации котежей, не были указаны типы')
102 if len(val) != t_len:
103 raise ValueError(f'Значение не содержит положенных {t_len} элементов: {len(val)} - {val}')
107 for i in range(t_len):
108 if check_instance(val[i], tt[i]):
113 res.append(tt[i](val[i]))
115 except (TypeError, ValueError) as e:
116 raise ValueError(f'Не удалось привести значение к нужному типу: '
117 f'тип={tt[i].__name__}; знач={val[i]}')
122 def union_processor(tt, val):
124 Пытается привести значение к одному из указанных типов
125 :param tt: список возможных типов
126 :param val: приводимое значение
135 raise ValueError('Не указан ни один тип в составном типе Union')
137 sorted_types_begin = [ t for t in tt if t in (int, float, bool)]
138 sorted_types_body = [ t for t in tt if t not in (int, float, bool, str)]
139 sorted_types_end = [ t for t in tt if t == str]
141 for t in sorted_types_begin + sorted_types_body + sorted_types_end:
142 _t = get_type_describer(t)
147 except (TypeError, ValueError) as e:
148 ex.append(f'{t}: {e}')
151 raise ValueError('Не удалось привести значение не к одному из типов:\n' + '\n'.join(ex))
157 def dict_processor(tt, val):
159 raise ValueError(f'Попытка воссоздать словарь со странным количеством аргументов типа: {tt}')
164 except (TypeError, ValueError) as e:
165 raise ValueError(f'Не удалось воссоздать словарь из представленного значения: {e}')
169 tt_cast = [get_type_describer(t) for t in tt]
171 for k, v in _d.items():
173 _p.append((tt_cast[0](k), tt_cast[1](v)))
175 except (TypeError, ValueError) as e:
176 raise ValueError(f'Не удалось привести значения элемента словаря к требуемому типу: '
177 f'key="{k}" value="{v}"')
182 def get_type_describer(t) -> TypeDescriber:
183 if type(t).__name__ == '_GenericAlias':
187 except AttributeError:
188 raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
189 f'типа "_GenericAlias": {t}')
191 if t.__name__ == 'List':
196 raise ValueError(f'Тип {t} не содержит в себе типа своих элементов')
198 return TypeDescriber(
199 name=f'{t.__name__}[{_t.__name__}]',
200 cast=lambda x: list(cast_iterator(get_type_describer(_t).cast, x)),
205 elif t.__name__ == 'Tuple':
207 raise ValueError(f'Тип {t} не содержит в себе пояснений по составу хранящихся в нём типов')
212 return TypeDescriber(
213 name=f'Tuple[{_t.__name__}]',
214 cast=lambda x: list(cast_iterator(get_type_describer(_t).cast, x)),
220 _name = ', '.join(map(lambda x: x.__name__, _args))
221 _cast_args = tuple(get_type_describer(i) for i in _args)
223 return TypeDescriber(
224 name=f'Tuple[{_name}]',
225 cast=lambda x: multi_item_tuple(_cast_args, x),
230 elif t.__name__ == 'Dict':
232 raise ValueError(f'Неожиданное количество значений типа в составном типе Dict: '
233 f'{len(_args)} values=({_args})')
235 _name = ', '.join(map(lambda x: x.__name__, _args))
237 return TypeDescriber(
238 name=f'Dict[{_name}]',
239 cast=lambda x: dict_processor(_args, x),
245 raise ValueError(f'Неизвестный представитель типа "_GenericAlias": {t.__name__}')
247 elif type(t).__name__ == '_UnionGenericAlias':
248 if t.__name__ not in ('Union', 'Optional'):
249 raise TypeError(f'Неизвестный подтип _UnionGenericAlias: {t.__name__}')
254 except AttributeError:
255 raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
256 f'типа "_UnionGenericAlias": {t}')
259 raise ValueError('Не указан ни один тип в конструкции Union')
261 _cast_args = tuple(map(get_type_describer, [ i for i in _args if i is not None]))
262 _args_name = ', '.join(map(lambda x: x.__name__, _args))
264 return TypeDescriber(
265 name=f'Union[{_args_name}]',
266 cast=lambda x: union_processor(_cast_args, x),
271 return TypeDescriber(
278 def dataobj_extract(obj: Union[object, Dict[str, Any]], dataclass_type: type) -> object:
280 Извлекает объект данных из предоставленного объекта, путём получения из него
281 указанных в классе данных аттрибутов и поиска их в данном объекте.
286 if isinstance(obj, dict):
294 if not is_dataclass(dataclass_type):
295 raise ValueError(f'Не относится к классам данных: {dataclass_type.__name__}')
297 for fld in fields(dataclass_type):
298 if _has(obj, fld.name):
299 val = _get(obj, fld.name)
300 type_desc = get_type_describer(fld.type)
305 except (ValueError, TypeError) as e:
306 raise ValueError(f'Аттрибут {fld.name} не может быть получен из значения "{val}"'
307 f' с типом {type(val).__name__} поскольку не может быть преобразован в'
308 f' тип {type_desc}, заданный в классе данных: {e}')
310 params[fld.name] = val
313 res = dataclass_type(**params)
315 except (ValueError, TypeError) as e:
316 _params = ', '.join(map(lambda x: f'{x[0]}="{x[1]}"', params.items()))
317 raise ValueError(f'Не удалось получить объект'
318 f' класс {dataclass_type.__name__}'
319 f' из параметров: {_params}'
325 def json_type_sanitizer(val):
327 Преобразует значение ``val`` в пригодное для преобразования в json значение.
332 if is_dataclass(val):
333 return json_type_sanitizer(asdict(val))
335 elif val_t in (int, float, str, bool) or val is None:
338 elif val_t in (list, tuple):
339 return list(map(json_type_sanitizer, val))
342 return dict((key, json_type_sanitizer(d_val)) for key, d_val in val.items())