py.lib

Yohn Y. 2022-08-27 Parent:b8d559c989d6

41:483727ff89c4 Go to Latest

py.lib/type_utils/type_descriptor.py

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

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