Сделал небольшую “надстройку” над датаклассом - датакласс ValidatedDC.
Валидируются аннотации:
1. Стандартные типы python и пользовательские классы
2. Некоторый алиасы из typing: Any, List, Literal, Optional, Union (можно добавить и другие, но для json-api пока без надобности). Эти алиасы могут быть вложены друг в друга, то есть можно описывать сложные структуры данных.
3. При инициализации экземпляра, если его поле имеет в аннотации потомка ValidatedDC, то в такое поле можно подставить словарь (или список словарей, если поле List), и если он будет валидный, то значение поля станет экземпляром потомка ValidatedDC из аннотации.
Пример:
from dataclasses import dataclass from typing import List, Union from validated_dc import ValidatedDC # Some combinations of List and Union @dataclass class Foo(ValidatedDC): value: Union[int, List[int]] @dataclass class Bar(ValidatedDC): foo: Union[Foo, List[Foo]] # --- Valid input --- foo = {'value': 1} instance = Bar(foo=foo) assert instance.get_errors() is None assert instance == Bar(foo=Foo(value=1)) foo = {'value': [1, 2]} instance = Bar(foo=foo) assert instance.get_errors() is None assert instance == Bar(foo=Foo(value=[1, 2])) foo = [{'value': 1}, {'value': 2}] instance = Bar(foo=foo) assert instance.get_errors() is None assert instance == Bar(foo=[Foo(value=1), Foo(value=2)]) foo = [{'value': [1, 2]}, {'value': [3, 4]}] instance = Bar(foo=foo) assert instance.get_errors() is None assert instance == Bar(foo=[Foo(value=[1, 2]), Foo(value=[3, 4])]) # --- Invalid input --- foo = {'value': 'S'} instance = Bar(foo=foo) assert instance.get_errors() assert instance == Bar(foo={'value': 'S'}) # fix instance.foo['value'] = 1 assert instance.is_valid() assert instance.get_errors() is None assert instance == Bar(foo=Foo(value=1)) foo = [{'value': [1, 2]}, {'value': ['S', 4]}] instance = Bar(foo=foo) assert instance.get_errors() assert instance == Bar(foo=[{'value': [1, 2]}, {'value': ['S', 4]}]) # fix instance.foo[1]['value'][0] = 3 assert instance.is_valid() assert instance.get_errors() is None assert instance == Bar(foo=[Foo(value=[1, 2]), Foo(value=[3, 4])]) # --- get_errors() --- foo = {'value': 'S'} instance = Bar(foo=foo) print(instance.get_errors()) # { # 'foo': [ # # An unsuccessful attempt to use the dictionary to create a Foo instance # InstanceValidationError( # value_repr="{'value': 'S'}", # value_type=<class 'dict'>, # annotation=<class '__main__.Foo'>, # exception=None, # errors={ # 'value': [ # BasicValidationError( # because the str isn't an int # value_repr='S', value_type=<class 'str'>, # annotation=<class 'int'>, exception=None # ), # BasicValidationError( # and the str is not a list of int # value_repr='S', value_type=<class 'str'>, # annotation=typing.List[int], exception=None # ) # ] # } # ), # BasicValidationError( # the dict is not a list of Foo # value_repr="{'value': 'S'}", # value_type=<class 'dict'>, # annotation=typing.List[__main__.Foo], # exception=None # ) # ] # }
Подробнее тут:
https://github.com/EvgeniyBurdin/validated_dc