py.lib
py.lib/dataclass_utils.py
+ Возможность обработки параметров конфигурации перед добавлением в класс конфигурации . Переформатирование части кода по PEP
| awgur@31 | 1 # coding: utf-8 |
| awgur@31 | 2 |
| awgur@31 | 3 from dataclasses import fields, dataclass, is_dataclass |
| awgur@31 | 4 from typing import Union, Dict, Any |
| awgur@31 | 5 |
| awgur@31 | 6 |
| awgur@31 | 7 def _dict_has(obj: Dict[str, Any], key: str) -> bool: |
| awgur@31 | 8 return key in obj |
| awgur@31 | 9 |
| awgur@31 | 10 |
| awgur@31 | 11 def _dict_get(obj: Dict[str, Any], key: str) -> Any: |
| awgur@31 | 12 return obj[key] |
| awgur@31 | 13 |
| awgur@31 | 14 |
| awgur@31 | 15 def _obj_has(obj: object, key: str) -> bool: |
| awgur@31 | 16 return hasattr(obj, key) |
| awgur@31 | 17 |
| awgur@31 | 18 |
| awgur@31 | 19 def _obj_get(obj: object, key: str) -> Any: |
| awgur@31 | 20 return getattr(obj, key) |
| awgur@31 | 21 |
| awgur@31 | 22 |
| awgur@33 | 23 def difficult_type_recognizer(type_obj, value): |
| awgur@33 | 24 """\ |
| awgur@33 | 25 Магия борющаяся с костылями type hinting |
| awgur@33 | 26 """ |
| awgur@33 | 27 if type(type_obj).__name__ == '_GenericAlias': |
| awgur@33 | 28 if ( |
| awgur@33 | 29 hasattr(type_obj, '__args__') |
| awgur@33 | 30 and type(type_obj.__args__) == tuple |
| awgur@33 | 31 and type_obj.__args__ |
| awgur@33 | 32 ): |
| awgur@33 | 33 if type_obj.__name__ == 'List': |
| awgur@33 | 34 return list(map(type_obj.__args__[0], value)) |
| awgur@33 | 35 |
| awgur@33 | 36 elif type_obj.__name__ == 'Tuple': |
| awgur@33 | 37 return tuple(map(type_obj.__args__[0], value)) |
| awgur@33 | 38 |
| awgur@33 | 39 else: |
| awgur@33 | 40 raise ValueError('Неизвестный тип') |
| awgur@33 | 41 |
| awgur@33 | 42 else: |
| awgur@33 | 43 ValueError('Неизвестный тип') |
| awgur@33 | 44 |
| awgur@33 | 45 elif type(type_obj).__name__ == '_UnionGenericAlias': |
| awgur@33 | 46 if type_obj.__name__ not in ('Union', 'Optional'): |
| awgur@33 | 47 raise TypeError(f'Неизвестный подтип _UnionGenericAlias: {type_obj.__name__}') |
| awgur@33 | 48 |
| awgur@33 | 49 if not ( |
| awgur@33 | 50 hasattr(type_obj, '__args__') |
| awgur@33 | 51 and type(type_obj.__args__) == tuple |
| awgur@33 | 52 and type_obj.__args__ |
| awgur@33 | 53 ): |
| awgur@33 | 54 raise TypeError(f'Не ясно как работать с типом не вижу аргументов: {type_obj.__name__}') |
| awgur@33 | 55 |
| awgur@33 | 56 for _t in type_obj.__args__: |
| awgur@33 | 57 if _t.__name__ == 'NoneType': |
| awgur@33 | 58 continue |
| awgur@33 | 59 |
| awgur@33 | 60 try: |
| awgur@33 | 61 return _t(value) |
| awgur@33 | 62 |
| awgur@33 | 63 except (TypeError, ValueError): |
| awgur@33 | 64 continue |
| awgur@33 | 65 |
| awgur@33 | 66 raise ValueError('Не удалось привести значение к типу') |
| awgur@33 | 67 |
| awgur@33 | 68 |
| awgur@34 | 69 def dataobj_extract(obj: Union[object, Dict[str, Any]], dataclass_type: type) -> object: |
| awgur@31 | 70 """\ |
| awgur@31 | 71 Извлекает объект данных из предоставленного объекта, путём получения из него |
| awgur@31 | 72 указанных в классе данных аттрибутов и поиска их в данном объекте. |
| awgur@31 | 73 """ |
| awgur@31 | 74 |
| awgur@31 | 75 params = {} |
| awgur@31 | 76 |
| awgur@31 | 77 if isinstance(obj, dict): |
| awgur@31 | 78 _has = _dict_has |
| awgur@31 | 79 _get = _dict_get |
| awgur@31 | 80 |
| awgur@31 | 81 else: |
| awgur@31 | 82 _has = _obj_has |
| awgur@31 | 83 _get = _obj_get |
| awgur@31 | 84 |
| awgur@31 | 85 if not is_dataclass(dataclass_type): |
| awgur@31 | 86 raise ValueError(f'Не относится к классам данных: {dataclass_type.__name__}') |
| awgur@31 | 87 |
| awgur@31 | 88 for fld in fields(dataclass_type): |
| awgur@31 | 89 if _has(obj, fld.name): |
| awgur@31 | 90 val = _get(obj, fld.name) |
| awgur@31 | 91 if val is not None and not isinstance(val, fld.type): |
| awgur@31 | 92 try: |
| awgur@31 | 93 val = fld.type(val) |
| awgur@31 | 94 |
| awgur@31 | 95 except (ValueError, TypeError) as e: |
| awgur@33 | 96 try: |
| awgur@33 | 97 val = difficult_type_recognizer(fld.type, val) |
| awgur@33 | 98 |
| awgur@33 | 99 except (ValueError, TypeError): |
| awgur@33 | 100 raise ValueError(f'Аттрибут {fld.name} не может быть получен из значения "{val}"' |
| awgur@33 | 101 f' с типом {type(val).__name__} поскольку не может быть преобразован в' |
| awgur@33 | 102 f' тип {fld.type.__name__}, заданный в классе данных: {e}') |
| awgur@31 | 103 |
| awgur@31 | 104 params[fld.name] = val |
| awgur@31 | 105 |
| awgur@31 | 106 try: |
| awgur@31 | 107 res = dataclass_type(**params) |
| awgur@31 | 108 |
| awgur@31 | 109 except (ValueError, TypeError) as e: |
| awgur@31 | 110 _params = ', '.join(map(lambda x: f'{x[0]}="{x[1]}"', params.items())) |
| awgur@31 | 111 raise ValueError(f'Не удалось получить объект' |
| awgur@31 | 112 f' класс {dataclass_type.__name__}' |
| awgur@31 | 113 f' из параметров: {_params}' |
| awgur@31 | 114 f' ошибка: {e}') |
| awgur@31 | 115 |
| awgur@31 | 116 return res |