py.lib

Yohn Y. 2022-08-20 Child:b8d559c989d6

38:4f4cc2fc9805 Go to Latest

py.lib/type_utils/type_descriptor.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/type_descriptor.py	Sat Aug 20 23:56:16 2022 +0300
     1.3 @@ -0,0 +1,367 @@
     1.4 +# coding: utf-8
     1.5 +
     1.6 +class TypeDescriptorInterface:
     1.7 +    """\
     1.8 +    Определяем общий интерфейс для описателей типов
     1.9 +    """
    1.10 +    def __init__(self, name, type_class, is_nullable=False):
    1.11 +        self.__name__ = name
    1.12 +        self.t_class = type_class
    1.13 +        self.is_nullable = is_nullable
    1.14 +        self.event_description = ''
    1.15 +        self.union_sort_order = 3
    1.16 +
    1.17 +    def raise_nullable(self, msg=''):
    1.18 +        msg = msg if not msg else f': {msg}'
    1.19 +
    1.20 +        if not self.is_nullable:
    1.21 +            raise ValueError(f'Передача значения "None" переменной, которая этого не ожидает{msg}')
    1.22 +
    1.23 +    def __call__(self, val):
    1.24 +        """\
    1.25 +        Реализация приведения типа
    1.26 +        """
    1.27 +        raise NotImplemented()
    1.28 +
    1.29 +    def __repr__(self):
    1.30 +        if self.t_class is None:
    1.31 +            return self.__name__
    1.32 +
    1.33 +        else:
    1.34 +            return f'{self.__name__}({self.t_class.__name__})'
    1.35 +
    1.36 +    def check(self, val):
    1.37 +        """\
    1.38 +        Реализация проверки соответствия типу
    1.39 +        """
    1.40 +        raise NotImplemented()
    1.41 +
    1.42 +
    1.43 +class BaseTypeDescriptor(TypeDescriptorInterface):
    1.44 +    """\
    1.45 +    Базовый класс, объявляющий общий интерфейс классов адаптеров проверки и преобразования типов
    1.46 +    """
    1.47 +    def check(self, val):
    1.48 +        event_description = ''
    1.49 +        res = False
    1.50 +        if val is None:
    1.51 +            event_description = 'Передано значение "None"'
    1.52 +            res = self.is_nullable
    1.53 +
    1.54 +        elif isinstance(self.t_class, TypeDescriptorInterface):
    1.55 +            res = self.t_class.check(val)
    1.56 +            event_description = self.t_class.event_description
    1.57 +
    1.58 +        else:
    1.59 +            event_description = f'Требуется тип "{self.t_class.__name__}" а получаем "{type(val).__name__}"'
    1.60 +            res = isinstance(val, self.t_class)
    1.61 +
    1.62 +        if not res:
    1.63 +            self.event_description = event_description
    1.64 +
    1.65 +        return res
    1.66 +
    1.67 +    def check_fail(self, msg):
    1.68 +        self.event_description = msg
    1.69 +        return False
    1.70 +
    1.71 +
    1.72 +class ScalarTypeDescriptor(BaseTypeDescriptor):
    1.73 +    """\
    1.74 +    Реализация адаптера над простыми типами
    1.75 +    """
    1.76 +    def __init__(self, type_class, is_nullable=False):
    1.77 +        super().__init__(type_class.__name__, type_class, is_nullable)
    1.78 +        if type_class in (int, float, bool):
    1.79 +            self.union_sort_order = 0
    1.80 +
    1.81 +        elif type_class == str:
    1.82 +            self.union_sort_order = 2
    1.83 +
    1.84 +        else:
    1.85 +            self.union_sort_order = 1
    1.86 +
    1.87 +    def __call__(self, val):
    1.88 +        if val is None:
    1.89 +            return self.raise_nullable()
    1.90 +
    1.91 +        if self.check(val):
    1.92 +            return val
    1.93 +
    1.94 +        return self.t_class(val)
    1.95 +
    1.96 +
    1.97 +class IterableTypeDescriptor(BaseTypeDescriptor):
    1.98 +    """\
    1.99 +    Реализация адаптера над последовательностями
   1.100 +    """
   1.101 +    def __init__(self, iterator_class, value_class, is_nullable=False):
   1.102 +        super().__init__(
   1.103 +            name=f'{iterator_class.__name__}[{value_class.__name__}]',
   1.104 +            type_class=value_class,
   1.105 +            is_nullable=is_nullable
   1.106 +        )
   1.107 +
   1.108 +        self.iterator_class = iterator_class
   1.109 +
   1.110 +    def check(self, vals):
   1.111 +        idx = 0
   1.112 +        for val in vals:
   1.113 +            if not super().check(val):
   1.114 +                return self.check_fail(f'Элемент {idx}: {self.event_description}')
   1.115 +
   1.116 +            else:
   1.117 +                idx += 1
   1.118 +
   1.119 +        return True
   1.120 +
   1.121 +    def __call__(self, vals):
   1.122 +        if vals is None:
   1.123 +            return self.raise_nullable()
   1.124 +
   1.125 +        res = []
   1.126 +        for val in vals:
   1.127 +            if val is None:
   1.128 +                self.raise_nullable(f'Элемент "{len(res)}"')
   1.129 +
   1.130 +            try:
   1.131 +                res.append(self.t_class(val))
   1.132 +
   1.133 +            except (TypeError, ValueError) as e:
   1.134 +                raise ValueError(f'Не удалось преобразовать элемент "{len(res)}" со значением "{val}" '
   1.135 +                                 f'в результирующий тип: {e}')
   1.136 +
   1.137 +        return self.iterator_class(res)
   1.138 +
   1.139 +
   1.140 +class TupleTypeDescriptor(BaseTypeDescriptor):
   1.141 +    """\
   1.142 +    Адаптер над кортежами
   1.143 +    """
   1.144 +    def __init__(self, type_classes, is_nullable=False):
   1.145 +        if not isinstance(type_classes, (list, tuple)) or not type_classes:
   1.146 +            raise TypeError(f'В конструктор прокси для обработки кортежей не передано типов кортежа')
   1.147 +
   1.148 +        names = ', '.join([i.__name__ for i in type_classes])
   1.149 +
   1.150 +        super().__init__(f'Tuple[{names}]', None, is_nullable)
   1.151 +
   1.152 +        self.type_classes = tuple(map(
   1.153 +            lambda x: x if isinstance(x, TypeDescriptorInterface) else ScalarTypeDescriptor(x),
   1.154 +            type_classes
   1.155 +        ))
   1.156 +
   1.157 +    def check(self, vals):
   1.158 +        if not isinstance(vals, tuple):
   1.159 +            return self.check_fail(f'Переданная переменная не является кортежем, а относится к типу: '
   1.160 +                                   f'{type(vals).__name__}')
   1.161 +
   1.162 +        if len(vals) != len(self.type_classes):
   1.163 +            return self.check_fail(f'Не достаточно элементов в кортеже: '
   1.164 +                                   f'имеется={len(vals)} нужно={len(self.type_classes)}')
   1.165 +
   1.166 +        for i in range(len(self.type_classes)):
   1.167 +            if not self.type_classes[i].check(vals[i]):
   1.168 +                return self.check_fail(f'Элемент {i}, значение "{vals[i]}": '
   1.169 +                                       f'{self.type_classes[i].event_description}')
   1.170 +
   1.171 +    def __call__(self, vals):
   1.172 +        if vals is None:
   1.173 +            return self.raise_nullable()
   1.174 +
   1.175 +        if not isinstance(vals, tuple):
   1.176 +            raise ValueError(f'Переданная переменная не является кортежем, а относится к типу: '
   1.177 +                             f'{type(vals).__name__}')
   1.178 +
   1.179 +        if len(vals) != len(self.type_classes):
   1.180 +            raise ValueError(f'Не достаточно элементов в кортеже: '
   1.181 +                             f'имеется={len(vals)} нужно={len(self.type_classes)}')
   1.182 +
   1.183 +        res = []
   1.184 +        for i in range(len(self.type_classes)):
   1.185 +            try:
   1.186 +                res.append(self.type_classes[i](vals[i]))
   1.187 +
   1.188 +            except (ValueError, TypeError) as e:
   1.189 +                raise ValueError(f'Не удалось привести к результирующему виду '
   1.190 +                                 f'элемент {i} со значением "{vals[i]}": {e}')
   1.191 +
   1.192 +        return tuple(res)
   1.193 +
   1.194 +
   1.195 +class UnionTypeDescriptor(BaseTypeDescriptor):
   1.196 +    """\
   1.197 +    Адаптер для объединения типов
   1.198 +    """
   1.199 +    def __init__(self, type_classes, is_nullable=False):
   1.200 +        if not isinstance(type_classes, (list, tuple)) or not type_classes:
   1.201 +            raise TypeError(f'В конструктор прокси для обработки объединений типов не передано типов')
   1.202 +
   1.203 +        names = ', '.join([i.__name__ for i in type_classes])
   1.204 +
   1.205 +        super().__init__(f'Union[{names}]', None, is_nullable)
   1.206 +
   1.207 +        self.type_classes = tuple(sorted(map(
   1.208 +            lambda x: x if isinstance(x, BaseTypeDescriptor) else ScalarTypeDescriptor(x),
   1.209 +            type_classes
   1.210 +        ), key=lambda x: x.union_sort_order))
   1.211 +
   1.212 +    def check(self, val):
   1.213 +        for t in self.type_classes:
   1.214 +            if t.check(val):
   1.215 +                return True
   1.216 +
   1.217 +        self.event_description = f'Значение "{val}" типа "{type(val).__name__}" не соответствует ' \
   1.218 +                                 f'ни одному типу из моего набора'
   1.219 +        return False
   1.220 +
   1.221 +    def __call__(self, val):
   1.222 +        if val is None:
   1.223 +            return self.raise_nullable()
   1.224 +
   1.225 +        if self.check(val):
   1.226 +            return val
   1.227 +
   1.228 +        errs = []
   1.229 +
   1.230 +        for t in self.type_classes:
   1.231 +            try:
   1.232 +                return t(val)
   1.233 +
   1.234 +            except (TypeError, ValueError) as e:
   1.235 +                errs.append(f'{repr(t)}: {e}')
   1.236 +
   1.237 +        raise ValueError(f'Не удалось преобразовать значение "{val}" типа "{type(val).__name__}" '
   1.238 +                         f'ни к одному из имеющихся типов: ' + '\n'.join(errs))
   1.239 +
   1.240 +
   1.241 +class DictTypeDescriptor(BaseTypeDescriptor):
   1.242 +    """\
   1.243 +    Адаптер словарей
   1.244 +    """
   1.245 +    def __init__(self, key_class, value_class, is_nullable=False):
   1.246 +        super().__init__(f'Dict[{key_class.__name__}, {value_class.__name__}]', None, is_nullable)
   1.247 +
   1.248 +        if isinstance(key_class, BaseTypeDescriptor):
   1.249 +            self.key_class = key_class
   1.250 +
   1.251 +        else:
   1.252 +            self.key_class = ScalarTypeDescriptor(key_class)
   1.253 +
   1.254 +        if isinstance(value_class, BaseTypeDescriptor):
   1.255 +            self.value_class = value_class
   1.256 +
   1.257 +        else:
   1.258 +            self.value_class = ScalarTypeDescriptor(value_class)
   1.259 +
   1.260 +    def check(self, val):
   1.261 +        try:
   1.262 +            d = dict(val)
   1.263 +
   1.264 +        except (TypeError, ValueError) as e:
   1.265 +            self.event_description = f'Не удалось преобразовать переданное значение в словарь: {e}'
   1.266 +            return False
   1.267 +
   1.268 +        for k, v in d.items():
   1.269 +            if not self.key_class.check(k):
   1.270 +                return self.check_fail(f'В паре ["{k}": "{v}"] ключ не соответствует ожидаемому типу: ' 
   1.271 +                                       f'{self.key_class.event_description}')
   1.272 +
   1.273 +            if not self.value_class.check(v):
   1.274 +                return self.check_fail(f'В паре ["{k}": "{v}"] значение не соответствует ожидаемому типу: '
   1.275 +                                       f'{self.value_class.event_description}')
   1.276 +
   1.277 +        return True
   1.278 +
   1.279 +    def __call__(self, val):
   1.280 +        try:
   1.281 +            d = dict(val)
   1.282 +
   1.283 +        except (TypeError, ValueError) as e:
   1.284 +            raise ValueError(f'Не удалось преобразовать переданное значение в словарь: {e}')
   1.285 +
   1.286 +        p = []
   1.287 +        for k, v in d.items():
   1.288 +            p.append((self.key_class(k), self.value_class(v)))
   1.289 +
   1.290 +        return dict(p)
   1.291 +
   1.292 +
   1.293 +def get_type_describer(t) -> BaseTypeDescriptor:
   1.294 +    if type(t).__name__ == '_GenericAlias':
   1.295 +        try:
   1.296 +            _args = t.__args__
   1.297 +
   1.298 +        except AttributeError:
   1.299 +            raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
   1.300 +                            f'типа "_GenericAlias": {t}')
   1.301 +
   1.302 +        if t.__name__ == 'List':
   1.303 +            try:
   1.304 +                _t = _args[0]
   1.305 +
   1.306 +            except IndexError:
   1.307 +                raise ValueError(f'Тип {t} не содержит в себе типа своих элементов')
   1.308 +
   1.309 +            return IterableTypeDescriptor(
   1.310 +                iterator_class=list,
   1.311 +                value_class=get_type_describer(_t)
   1.312 +            )
   1.313 +
   1.314 +        elif t.__name__ == 'Tuple':
   1.315 +            if not _args:
   1.316 +                raise ValueError(f'Тип {t} не содержит в себе пояснений по составу хранящихся в нём типов')
   1.317 +
   1.318 +            if len(_args) == 1:
   1.319 +                _t = _args[0]
   1.320 +
   1.321 +                return IterableTypeDescriptor(
   1.322 +                    iterator_class=tuple,
   1.323 +                    value_class=get_type_describer(_t)
   1.324 +                )
   1.325 +
   1.326 +            else:
   1.327 +                return TupleTypeDescriptor(
   1.328 +                    type_classes=_args
   1.329 +                )
   1.330 +
   1.331 +        elif t.__name__ == 'Dict':
   1.332 +            if len(_args) != 2:
   1.333 +                raise ValueError(f'Неожиданное количество значений типа в составном типе Dict: '
   1.334 +                                 f'{len(_args)} values=({_args})')
   1.335 +
   1.336 +            return DictTypeDescriptor(
   1.337 +                key_class=get_type_describer(_args[0]),
   1.338 +                value_class=get_type_describer(_args[1])
   1.339 +            )
   1.340 +
   1.341 +        else:
   1.342 +            raise ValueError(f'Неизвестный представитель типа "_GenericAlias": {t.__name__}')
   1.343 +
   1.344 +    elif type(t).__name__ == '_UnionGenericAlias':
   1.345 +        if t.__name__ not in ('Union', 'Optional'):
   1.346 +            raise TypeError(f'Неизвестный подтип _UnionGenericAlias: {t.__name__}')
   1.347 +
   1.348 +        nullable = False
   1.349 +        if t.__name__ == 'Optional':
   1.350 +            nullable = True
   1.351 +
   1.352 +        try:
   1.353 +            _args = t.__args__
   1.354 +
   1.355 +        except AttributeError:
   1.356 +            raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
   1.357 +                            f'типа "_UnionGenericAlias": {t}')
   1.358 +
   1.359 +        if len(_args) == 0:
   1.360 +            raise ValueError('Не указан ни один тип в конструкции Union')
   1.361 +
   1.362 +        type_classes = tuple(map(lambda x: get_type_describer(x), [ i for i in _args if i is not None]))
   1.363 +
   1.364 +        return UnionTypeDescriptor(
   1.365 +            type_classes=type_classes,
   1.366 +            is_nullable=nullable
   1.367 +        )
   1.368 +
   1.369 +    else:
   1.370 +        return ScalarTypeDescriptor(type_class=t)