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