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