py.lib

Yohn Y. 2022-08-27 Parent:b8d559c989d6

41:483727ff89c4 Go to Latest

py.lib/type_utils/type_descriptor.py

+ Возможность удалить cookie при формировании штатного ответа.

History
awgur@38 1 # coding: utf-8
awgur@38 2
awgur@38 3 class TypeDescriptorInterface:
awgur@38 4 """\
awgur@38 5 Определяем общий интерфейс для описателей типов
awgur@38 6 """
awgur@38 7 def __init__(self, name, type_class, is_nullable=False):
awgur@38 8 self.__name__ = name
awgur@38 9 self.t_class = type_class
awgur@38 10 self.is_nullable = is_nullable
awgur@38 11 self.event_description = ''
awgur@38 12 self.union_sort_order = 3
awgur@38 13
awgur@38 14 def raise_nullable(self, msg=''):
awgur@38 15 msg = msg if not msg else f': {msg}'
awgur@38 16
awgur@38 17 if not self.is_nullable:
awgur@38 18 raise ValueError(f'Передача значения "None" переменной, которая этого не ожидает{msg}')
awgur@38 19
awgur@38 20 def __call__(self, val):
awgur@38 21 """\
awgur@38 22 Реализация приведения типа
awgur@38 23 """
awgur@38 24 raise NotImplemented()
awgur@38 25
awgur@38 26 def __repr__(self):
awgur@38 27 if self.t_class is None:
awgur@38 28 return self.__name__
awgur@38 29
awgur@38 30 else:
awgur@38 31 return f'{self.__name__}({self.t_class.__name__})'
awgur@38 32
awgur@38 33 def check(self, val):
awgur@38 34 """\
awgur@38 35 Реализация проверки соответствия типу
awgur@38 36 """
awgur@38 37 raise NotImplemented()
awgur@38 38
awgur@38 39
awgur@38 40 class BaseTypeDescriptor(TypeDescriptorInterface):
awgur@38 41 """\
awgur@38 42 Базовый класс, объявляющий общий интерфейс классов адаптеров проверки и преобразования типов
awgur@38 43 """
awgur@38 44 def check(self, val):
awgur@38 45 event_description = ''
awgur@38 46 res = False
awgur@38 47 if val is None:
awgur@38 48 event_description = 'Передано значение "None"'
awgur@38 49 res = self.is_nullable
awgur@38 50
awgur@38 51 elif isinstance(self.t_class, TypeDescriptorInterface):
awgur@38 52 res = self.t_class.check(val)
awgur@38 53 event_description = self.t_class.event_description
awgur@38 54
awgur@38 55 else:
awgur@38 56 event_description = f'Требуется тип "{self.t_class.__name__}" а получаем "{type(val).__name__}"'
awgur@38 57 res = isinstance(val, self.t_class)
awgur@38 58
awgur@38 59 if not res:
awgur@38 60 self.event_description = event_description
awgur@38 61
awgur@38 62 return res
awgur@38 63
awgur@38 64 def check_fail(self, msg):
awgur@38 65 self.event_description = msg
awgur@38 66 return False
awgur@38 67
awgur@38 68
awgur@38 69 class ScalarTypeDescriptor(BaseTypeDescriptor):
awgur@38 70 """\
awgur@38 71 Реализация адаптера над простыми типами
awgur@38 72 """
awgur@39 73
awgur@38 74 def __init__(self, type_class, is_nullable=False):
awgur@38 75 super().__init__(type_class.__name__, type_class, is_nullable)
awgur@39 76 if type_class in (int, float, bool, complex):
awgur@38 77 self.union_sort_order = 0
awgur@38 78
awgur@38 79 elif type_class == str:
awgur@38 80 self.union_sort_order = 2
awgur@38 81
awgur@38 82 else:
awgur@38 83 self.union_sort_order = 1
awgur@38 84
awgur@38 85 def __call__(self, val):
awgur@38 86 if val is None:
awgur@38 87 return self.raise_nullable()
awgur@38 88
awgur@38 89 if self.check(val):
awgur@38 90 return val
awgur@38 91
awgur@39 92 if self.union_sort_order == 1:
awgur@39 93 if isinstance(val, (list, tuple)):
awgur@39 94 return self.t_class(*val)
awgur@39 95
awgur@39 96 elif isinstance(val, dict):
awgur@39 97 return self.t_class(**val)
awgur@39 98
awgur@38 99 return self.t_class(val)
awgur@38 100
awgur@38 101
awgur@38 102 class IterableTypeDescriptor(BaseTypeDescriptor):
awgur@38 103 """\
awgur@38 104 Реализация адаптера над последовательностями
awgur@38 105 """
awgur@38 106 def __init__(self, iterator_class, value_class, is_nullable=False):
awgur@38 107 super().__init__(
awgur@38 108 name=f'{iterator_class.__name__}[{value_class.__name__}]',
awgur@38 109 type_class=value_class,
awgur@38 110 is_nullable=is_nullable
awgur@38 111 )
awgur@38 112
awgur@38 113 self.iterator_class = iterator_class
awgur@38 114
awgur@38 115 def check(self, vals):
awgur@38 116 idx = 0
awgur@38 117 for val in vals:
awgur@38 118 if not super().check(val):
awgur@38 119 return self.check_fail(f'Элемент {idx}: {self.event_description}')
awgur@38 120
awgur@38 121 else:
awgur@38 122 idx += 1
awgur@38 123
awgur@38 124 return True
awgur@38 125
awgur@38 126 def __call__(self, vals):
awgur@38 127 if vals is None:
awgur@38 128 return self.raise_nullable()
awgur@38 129
awgur@38 130 res = []
awgur@38 131 for val in vals:
awgur@38 132 if val is None:
awgur@38 133 self.raise_nullable(f'Элемент "{len(res)}"')
awgur@38 134
awgur@38 135 try:
awgur@38 136 res.append(self.t_class(val))
awgur@38 137
awgur@38 138 except (TypeError, ValueError) as e:
awgur@38 139 raise ValueError(f'Не удалось преобразовать элемент "{len(res)}" со значением "{val}" '
awgur@38 140 f'в результирующий тип: {e}')
awgur@38 141
awgur@38 142 return self.iterator_class(res)
awgur@38 143
awgur@38 144
awgur@38 145 class TupleTypeDescriptor(BaseTypeDescriptor):
awgur@38 146 """\
awgur@38 147 Адаптер над кортежами
awgur@38 148 """
awgur@38 149 def __init__(self, type_classes, is_nullable=False):
awgur@38 150 if not isinstance(type_classes, (list, tuple)) or not type_classes:
awgur@38 151 raise TypeError(f'В конструктор прокси для обработки кортежей не передано типов кортежа')
awgur@38 152
awgur@38 153 names = ', '.join([i.__name__ for i in type_classes])
awgur@38 154
awgur@38 155 super().__init__(f'Tuple[{names}]', None, is_nullable)
awgur@38 156
awgur@38 157 self.type_classes = tuple(map(
awgur@38 158 lambda x: x if isinstance(x, TypeDescriptorInterface) else ScalarTypeDescriptor(x),
awgur@38 159 type_classes
awgur@38 160 ))
awgur@38 161
awgur@38 162 def check(self, vals):
awgur@38 163 if not isinstance(vals, tuple):
awgur@38 164 return self.check_fail(f'Переданная переменная не является кортежем, а относится к типу: '
awgur@38 165 f'{type(vals).__name__}')
awgur@38 166
awgur@38 167 if len(vals) != len(self.type_classes):
awgur@38 168 return self.check_fail(f'Не достаточно элементов в кортеже: '
awgur@38 169 f'имеется={len(vals)} нужно={len(self.type_classes)}')
awgur@38 170
awgur@38 171 for i in range(len(self.type_classes)):
awgur@38 172 if not self.type_classes[i].check(vals[i]):
awgur@38 173 return self.check_fail(f'Элемент {i}, значение "{vals[i]}": '
awgur@38 174 f'{self.type_classes[i].event_description}')
awgur@38 175
awgur@38 176 def __call__(self, vals):
awgur@38 177 if vals is None:
awgur@38 178 return self.raise_nullable()
awgur@38 179
awgur@38 180 if not isinstance(vals, tuple):
awgur@38 181 raise ValueError(f'Переданная переменная не является кортежем, а относится к типу: '
awgur@38 182 f'{type(vals).__name__}')
awgur@38 183
awgur@38 184 if len(vals) != len(self.type_classes):
awgur@38 185 raise ValueError(f'Не достаточно элементов в кортеже: '
awgur@38 186 f'имеется={len(vals)} нужно={len(self.type_classes)}')
awgur@38 187
awgur@38 188 res = []
awgur@38 189 for i in range(len(self.type_classes)):
awgur@38 190 try:
awgur@38 191 res.append(self.type_classes[i](vals[i]))
awgur@38 192
awgur@38 193 except (ValueError, TypeError) as e:
awgur@38 194 raise ValueError(f'Не удалось привести к результирующему виду '
awgur@38 195 f'элемент {i} со значением "{vals[i]}": {e}')
awgur@38 196
awgur@38 197 return tuple(res)
awgur@38 198
awgur@38 199
awgur@38 200 class UnionTypeDescriptor(BaseTypeDescriptor):
awgur@38 201 """\
awgur@38 202 Адаптер для объединения типов
awgur@38 203 """
awgur@38 204 def __init__(self, type_classes, is_nullable=False):
awgur@38 205 if not isinstance(type_classes, (list, tuple)) or not type_classes:
awgur@38 206 raise TypeError(f'В конструктор прокси для обработки объединений типов не передано типов')
awgur@38 207
awgur@38 208 names = ', '.join([i.__name__ for i in type_classes])
awgur@38 209
awgur@38 210 super().__init__(f'Union[{names}]', None, is_nullable)
awgur@38 211
awgur@38 212 self.type_classes = tuple(sorted(map(
awgur@38 213 lambda x: x if isinstance(x, BaseTypeDescriptor) else ScalarTypeDescriptor(x),
awgur@38 214 type_classes
awgur@38 215 ), key=lambda x: x.union_sort_order))
awgur@38 216
awgur@38 217 def check(self, val):
awgur@38 218 for t in self.type_classes:
awgur@38 219 if t.check(val):
awgur@38 220 return True
awgur@38 221
awgur@38 222 self.event_description = f'Значение "{val}" типа "{type(val).__name__}" не соответствует ' \
awgur@38 223 f'ни одному типу из моего набора'
awgur@38 224 return False
awgur@38 225
awgur@38 226 def __call__(self, val):
awgur@38 227 if val is None:
awgur@38 228 return self.raise_nullable()
awgur@38 229
awgur@38 230 if self.check(val):
awgur@38 231 return val
awgur@38 232
awgur@38 233 errs = []
awgur@38 234
awgur@38 235 for t in self.type_classes:
awgur@38 236 try:
awgur@38 237 return t(val)
awgur@38 238
awgur@38 239 except (TypeError, ValueError) as e:
awgur@38 240 errs.append(f'{repr(t)}: {e}')
awgur@38 241
awgur@38 242 raise ValueError(f'Не удалось преобразовать значение "{val}" типа "{type(val).__name__}" '
awgur@38 243 f'ни к одному из имеющихся типов: ' + '\n'.join(errs))
awgur@38 244
awgur@38 245
awgur@38 246 class DictTypeDescriptor(BaseTypeDescriptor):
awgur@38 247 """\
awgur@38 248 Адаптер словарей
awgur@38 249 """
awgur@38 250 def __init__(self, key_class, value_class, is_nullable=False):
awgur@38 251 super().__init__(f'Dict[{key_class.__name__}, {value_class.__name__}]', None, is_nullable)
awgur@38 252
awgur@38 253 if isinstance(key_class, BaseTypeDescriptor):
awgur@38 254 self.key_class = key_class
awgur@38 255
awgur@38 256 else:
awgur@38 257 self.key_class = ScalarTypeDescriptor(key_class)
awgur@38 258
awgur@38 259 if isinstance(value_class, BaseTypeDescriptor):
awgur@38 260 self.value_class = value_class
awgur@38 261
awgur@38 262 else:
awgur@38 263 self.value_class = ScalarTypeDescriptor(value_class)
awgur@38 264
awgur@38 265 def check(self, val):
awgur@38 266 try:
awgur@38 267 d = dict(val)
awgur@38 268
awgur@38 269 except (TypeError, ValueError) as e:
awgur@38 270 self.event_description = f'Не удалось преобразовать переданное значение в словарь: {e}'
awgur@38 271 return False
awgur@38 272
awgur@38 273 for k, v in d.items():
awgur@38 274 if not self.key_class.check(k):
awgur@38 275 return self.check_fail(f'В паре ["{k}": "{v}"] ключ не соответствует ожидаемому типу: '
awgur@38 276 f'{self.key_class.event_description}')
awgur@38 277
awgur@38 278 if not self.value_class.check(v):
awgur@38 279 return self.check_fail(f'В паре ["{k}": "{v}"] значение не соответствует ожидаемому типу: '
awgur@38 280 f'{self.value_class.event_description}')
awgur@38 281
awgur@38 282 return True
awgur@38 283
awgur@38 284 def __call__(self, val):
awgur@38 285 try:
awgur@38 286 d = dict(val)
awgur@38 287
awgur@38 288 except (TypeError, ValueError) as e:
awgur@38 289 raise ValueError(f'Не удалось преобразовать переданное значение в словарь: {e}')
awgur@38 290
awgur@38 291 p = []
awgur@38 292 for k, v in d.items():
awgur@38 293 p.append((self.key_class(k), self.value_class(v)))
awgur@38 294
awgur@38 295 return dict(p)
awgur@38 296
awgur@38 297
awgur@38 298 def get_type_describer(t) -> BaseTypeDescriptor:
awgur@38 299 if type(t).__name__ == '_GenericAlias':
awgur@38 300 try:
awgur@38 301 _args = t.__args__
awgur@38 302
awgur@38 303 except AttributeError:
awgur@38 304 raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
awgur@38 305 f'типа "_GenericAlias": {t}')
awgur@38 306
awgur@38 307 if t.__name__ == 'List':
awgur@38 308 try:
awgur@38 309 _t = _args[0]
awgur@38 310
awgur@38 311 except IndexError:
awgur@38 312 raise ValueError(f'Тип {t} не содержит в себе типа своих элементов')
awgur@38 313
awgur@38 314 return IterableTypeDescriptor(
awgur@38 315 iterator_class=list,
awgur@38 316 value_class=get_type_describer(_t)
awgur@38 317 )
awgur@38 318
awgur@38 319 elif t.__name__ == 'Tuple':
awgur@38 320 if not _args:
awgur@38 321 raise ValueError(f'Тип {t} не содержит в себе пояснений по составу хранящихся в нём типов')
awgur@38 322
awgur@38 323 if len(_args) == 1:
awgur@38 324 _t = _args[0]
awgur@38 325
awgur@38 326 return IterableTypeDescriptor(
awgur@38 327 iterator_class=tuple,
awgur@38 328 value_class=get_type_describer(_t)
awgur@38 329 )
awgur@38 330
awgur@38 331 else:
awgur@38 332 return TupleTypeDescriptor(
awgur@38 333 type_classes=_args
awgur@38 334 )
awgur@38 335
awgur@38 336 elif t.__name__ == 'Dict':
awgur@38 337 if len(_args) != 2:
awgur@38 338 raise ValueError(f'Неожиданное количество значений типа в составном типе Dict: '
awgur@38 339 f'{len(_args)} values=({_args})')
awgur@38 340
awgur@38 341 return DictTypeDescriptor(
awgur@38 342 key_class=get_type_describer(_args[0]),
awgur@38 343 value_class=get_type_describer(_args[1])
awgur@38 344 )
awgur@38 345
awgur@38 346 else:
awgur@38 347 raise ValueError(f'Неизвестный представитель типа "_GenericAlias": {t.__name__}')
awgur@38 348
awgur@38 349 elif type(t).__name__ == '_UnionGenericAlias':
awgur@38 350 if t.__name__ not in ('Union', 'Optional'):
awgur@38 351 raise TypeError(f'Неизвестный подтип _UnionGenericAlias: {t.__name__}')
awgur@38 352
awgur@38 353 nullable = False
awgur@38 354 if t.__name__ == 'Optional':
awgur@38 355 nullable = True
awgur@38 356
awgur@38 357 try:
awgur@38 358 _args = t.__args__
awgur@38 359
awgur@38 360 except AttributeError:
awgur@38 361 raise TypeError(f'Неизвестный тип хранения внутренних типов для представителя сложного '
awgur@38 362 f'типа "_UnionGenericAlias": {t}')
awgur@38 363
awgur@38 364 if len(_args) == 0:
awgur@38 365 raise ValueError('Не указан ни один тип в конструкции Union')
awgur@38 366
awgur@38 367 type_classes = tuple(map(lambda x: get_type_describer(x), [ i for i in _args if i is not None]))
awgur@38 368
awgur@38 369 return UnionTypeDescriptor(
awgur@38 370 type_classes=type_classes,
awgur@38 371 is_nullable=nullable
awgur@38 372 )
awgur@38 373
awgur@38 374 else:
awgur@38 375 return ScalarTypeDescriptor(type_class=t)