py.lib
py.lib/type_utils/dataclass_utils.py
. Полный рефакторинг кода модулей dataclass_utils.py и config_parse_helper.py. Теперь логика предсказуема. + функция dataobj_extract не просто бездумно загоняет данные в класс данных, но имеет функционал проверки данных с возбуждением исключения при разнице (по умолчанию) и принудительного приведения типов.
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/type_utils/dataclass_utils.py Sat Aug 20 23:56:16 2022 +0300 1.3 @@ -0,0 +1,89 @@ 1.4 +# coding: utf-8 1.5 +"""\ 1.6 +Наполнение заданного класса данных из предоставленного словаря или объекта. 1.7 + 1.8 +НЕ РАБОТАЕТ БЕЗ ОСТАЛЬНОГО МОДУЛЯ type_utils 1.9 +""" 1.10 + 1.11 +from dataclasses import fields, is_dataclass, asdict 1.12 +from typing import Union, Dict, Any, Iterable 1.13 +from .type_descriptor import get_type_describer 1.14 + 1.15 + 1.16 +def _dict_has(obj: Dict[str, Any], key: str) -> bool: 1.17 + return key in obj 1.18 + 1.19 + 1.20 +def _dict_get(obj: Dict[str, Any], key: str) -> Any: 1.21 + return obj[key] 1.22 + 1.23 + 1.24 +def _obj_has(obj: object, key: str) -> bool: 1.25 + return hasattr(obj, key) 1.26 + 1.27 + 1.28 +def _obj_get(obj: object, key: str) -> Any: 1.29 + return getattr(obj, key) 1.30 + 1.31 + 1.32 +def dataobj_extract(obj: Union[object, Dict[str, Any]], dataclass_type: type, type_convert: bool = False) -> object: 1.33 + """\ 1.34 + Извлекает объект данных из предоставленного объекта, путём получения из него 1.35 + указанных в классе данных аттрибутов и поиска их в данном объекте. 1.36 + :param obj: Объект, из которого берутся данные для класса данных 1.37 + :param dataclass_type: Класс данных, наполняемый из ``obj`` 1.38 + :param type_convert: Признак конвертирования данных. Если задан, выполняет попытку сконвертировать имеющееся 1.39 + в параметре значение в тип, указанных в классе данных. Чудес не бывает, и процесс может 1.40 + ошибиться особенно с Union 1.41 + """ 1.42 + 1.43 + params = {} 1.44 + 1.45 + if isinstance(obj, dict): 1.46 + _has = _dict_has 1.47 + _get = _dict_get 1.48 + 1.49 + else: 1.50 + _has = _obj_has 1.51 + _get = _obj_get 1.52 + 1.53 + if not is_dataclass(dataclass_type): 1.54 + raise ValueError(f'Не относится к классам данных: {dataclass_type.__name__}') 1.55 + 1.56 + for fld in fields(dataclass_type): 1.57 + if _has(obj, fld.name): 1.58 + val = _get(obj, fld.name) 1.59 + type_desc = get_type_describer(fld.type) 1.60 + if val is not None: 1.61 + if not type_desc.check(val): 1.62 + if not type_convert: 1.63 + raise ValueError(f'Аттрибут {fld.name} не может быть получен из значения "{val}"' 1.64 + f' с типом {type(val).__name__} поскольку не может быть преобразован в' 1.65 + f' тип {type_desc}, заданный в классе данных: {type_desc.event_description}') 1.66 + 1.67 + try: 1.68 + val = type_desc(val) 1.69 + 1.70 + except (ValueError, TypeError) as e: 1.71 + raise ValueError(f'Аттрибут {fld.name} не может быть получен из значения "{val}"' 1.72 + f' с типом {type(val).__name__} поскольку не может быть преобразован в' 1.73 + f' тип {type_desc}, заданный в классе данных: {e}') 1.74 + 1.75 + elif not (type_desc.is_nullable or type_convert): 1.76 + raise ValueError(f'Передан "None" в поле "{fld.name}", хотя он не ожидался') 1.77 + 1.78 + params[fld.name] = val 1.79 + 1.80 + try: 1.81 + res = dataclass_type(**params) 1.82 + 1.83 + except (ValueError, TypeError) as e: 1.84 + _params = ', '.join(map(lambda x: f'{x[0]}="{x[1]}"', params.items())) 1.85 + raise ValueError(f'Не удалось получить объект' 1.86 + f' класс {dataclass_type.__name__}' 1.87 + f' из параметров: {_params}' 1.88 + f' ошибка: {e}') 1.89 + 1.90 + return res 1.91 + 1.92 +