py.lib
35:ab4cf9f4f10a
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 __instancecheck__(self, instance):
38 return isinstance(instance, self.like)
41 return f'<TypeDescriber({self.__name__}, {self.like})>'
43 def __call__(self, val):
51 def cast_iterator(t: Union[type, TypeDescriber], lst: Iterable):
53 Обрабатывает последовательности единого типа.
62 except (TypeError, ValueError) as e:
63 raise ValueError(f'Не удалось привести значение к нужному типу: тип={t.__name__}; знач={i}')
66 def multi_item_tuple(tt, val):
68 Обрабатывает кортежи, состоящие из нескольких значений типов (кортежи элементов разных типов)
69 :param tt: Последовательность составляющих кортеж типов
70 :param val: итерируемый объект, хранящий значения в указанном порядке.
75 raise ValueError('При вызове процедуры конвертации котежей, не были указаны типы')
78 raise ValueError(f'Значение не содержит положенных {t_len} элементов: {len(val)} - {val}')
82 for i in range(t_len):
83 if isinstance(val[i], tt[i]):
87 res.append(tt[i](val[i]))
89 except (TypeError, ValueError) as e:
90 raise ValueError(f'Не удалось привести значение к нужному типу: тип={tt[i].__name__}; знач={val[i]}')
95 def union_processor(tt, val):
97 Пытается привести значение к одному из указанных типов
98 :param tt: список возможных типов
99 :param val: приводимое значение
108 raise ValueError('Не указан ни один тип в составном типе Union')
115 except (TypeError, ValueError) as e:
116 ex.append(f'{t}: {e}')
119 raise ValueError('Не удалось привести значение не к одному из типов:\n' + '\n'.join(ex))
125 def dict_processor(tt, val):
127 raise ValueError(f'Попытка воссоздать словарь со странным количеством аргументов типа: {tt}')
132 except (TypeError, ValueError) as e:
133 raise ValueError(f'Не удалось воссоздать словарь из представленного значения: {e}')
137 for k, v in _d.items():
139 _p.append((tt[0](k), tt[1](v)))
141 except (TypeError, ValueError) as e:
142 raise ValueError(f'Не удалось привести значения элемента словаря к требуемому типу: '
143 f'key="{k}" value="{v}"')
148 def get_type_describer(t) -> TypeDescriber:
149 if type(t).__name__ == '_GenericAlias':
153 except AttributeError:
154 raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
155 f'типа "_GenericAlias": {t}')
157 if t.__name__ == 'List':
162 raise ValueError(f'Тип {t} не содержит в себе типа своих элементов')
164 return TypeDescriber(
165 name=f'{t.__name__}[{_t.__name__}]',
166 cast=lambda x: list(cast_iterator(get_type_describer(_t).cast, x)),
171 elif t.__name__ == 'Tuple':
173 raise ValueError(f'Тип {t} не содержит в себе пояснений по составу хранящихся в нём типов')
178 return TypeDescriber(
179 name=f'Tuple[{_t.__name__}]',
180 cast=lambda x: list(cast_iterator(get_type_describer(_t).cast, x)),
186 _name = ', '.join(map(lambda x: x.__name__, _args))
187 _cast_args = tuple(get_type_describer(i) for i in _args)
189 return TypeDescriber(
190 name=f'Tuple[{_name}]',
191 cast=lambda x: multi_item_tuple(_cast_args, x),
196 elif t.__name__ == 'Dict':
198 raise ValueError(f'Неожиданное количество значений типа в составном типе Dict: '
199 f'{len(_args)} values=({_args})')
201 _name = ', '.join(map(lambda x: x.__name__, _args))
203 return TypeDescriber(
204 name=f'Dict[{_name}]',
205 cast=lambda x: dict_processor(_args, x),
211 raise ValueError(f'Неизвестный представитель типа "_GenericAlias": {t.__name__}')
213 elif type(t).__name__ == '_UnionGenericAlias':
214 if t.__name__ not in ('Union', 'Optional'):
215 raise TypeError(f'Неизвестный подтип _UnionGenericAlias: {t.__name__}')
220 except AttributeError:
221 raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
222 f'типа "_UnionGenericAlias": {t}')
225 raise ValueError('Не указан ни один тип в конструкции Union')
227 _cast_args = tuple(map(get_type_describer, _args))
228 _args_name = ', '.join(map(lambda x: x.__name__, _args))
230 return TypeDescriber(
231 name=f'Union[{_args_name}]',
232 cast=lambda x: union_processor(_cast_args, x),
237 return TypeDescriber(
244 def dataobj_extract(obj: Union[object, Dict[str, Any]], dataclass_type: type) -> object:
246 Извлекает объект данных из предоставленного объекта, путём получения из него
247 указанных в классе данных аттрибутов и поиска их в данном объекте.
252 if isinstance(obj, dict):
260 if not is_dataclass(dataclass_type):
261 raise ValueError(f'Не относится к классам данных: {dataclass_type.__name__}')
263 for fld in fields(dataclass_type):
264 if _has(obj, fld.name):
265 val = _get(obj, fld.name)
266 typedesc = get_type_describer(fld.type)
267 if val is not None and not isinstance(val, typedesc):
271 except (ValueError, TypeError) as e:
272 raise ValueError(f'Аттрибут {fld.name} не может быть получен из значения "{val}"'
273 f' с типом {type(val).__name__} поскольку не может быть преобразован в'
274 f' тип {typedesc}, заданный в классе данных: {e}')
276 params[fld.name] = val
279 res = dataclass_type(**params)
281 except (ValueError, TypeError) as e:
282 _params = ', '.join(map(lambda x: f'{x[0]}="{x[1]}"', params.items()))
283 raise ValueError(f'Не удалось получить объект'
284 f' класс {dataclass_type.__name__}'
285 f' из параметров: {_params}'
291 def json_type_sanitizer(val):
293 Преобразует значение ``val`` в пригодное для преобразования в json значение.
298 if is_dataclass(val):
299 return json_type_sanitizer(asdict(val))
301 elif val_t in (int, float, str, bool) or val is None:
304 elif val_t in (list, tuple):
305 return list(map(json_type_sanitizer, val))
308 return dict((key, json_type_sanitizer(d_val)) for key, d_val in val.items())