py.lib
2022-08-20
Child:b8d559c989d6
py.lib/type_utils/type_descriptor.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/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)