py.lib

Yohn Y. 2022-08-20 Parent:dataclass_utils.py@ae0107755941 Child:366c9fe26d76

38:4f4cc2fc9805 Go to Latest

py.lib/type_utils/dataclass_utils.py

. Полный рефакторинг кода модулей dataclass_utils.py и config_parse_helper.py. Теперь логика предсказуема. + функция dataobj_extract не просто бездумно загоняет данные в класс данных, но имеет функционал проверки данных с возбуждением исключения при разнице (по умолчанию) и принудительного приведения типов.

History
     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 +