# megasniff ### Автоматическая валидация данных по схеме, сборка и разборка объекта в одном флаконе #### Как применять: ```python # 1. Объявляем схемы from __future__ import annotations import dataclasses import typing @dataclasses.dataclass class SomeSchema1: a: int b: float | str c: SomeSchema2 | str | None @dataclasses.dataclass class SomeSchema2: field1: dict field2: float field3: typing.Optional[SomeSchema1] # 2. Генерируем метод для валидации и сборки import megasniff infl = megasniff.SchemaInflatorGenerator() defl = megasniff.SchemaDeflatorGenerator() fn_in = infl.schema_to_inflator(SomeSchema1) fn_out = defl.schema_to_deflator(SomeSchema1) # 3. Проверяем что всё работает data = fn_in({'a': 1, 'b': 2, 'c': {'field1': {}, 'field2': '1.1', 'field3': None}}) # SomeSchema1(a=1, b=2.0, c={'field1': {}, 'field2': 1.1, 'field3': None}) fn_out(data) # {'a': 1, 'b': 2.0, 'c': {'field1': {}, 'field2': 1.1, 'field3': None}} ``` Особенности работы: - поддерживает циклические зависимости - проверяет `Union`-типы через ретрай на выбросе исключения - по умолчанию использует готовый щаблон для кодогенерации и исполняет его по запросу, требуется особое внимание к сохранности данного шаблона - проверяет типы списков, может приводить списки к множествам - не проверяет типы generic-словарей, кортежей (реализация ожидается) - пользовательские проверки типов должны быть реализованы через наследование и проверки в конструкторе - опциональный `strict-mode`: выключение приведения базовых типов - для inflation может генерировать кортежи верхнеуровневых объектов при наличии описания схемы (полезно при развертывании аргументов) - `TypedDict` поддерживается только для inflation из-за сложностей выбора варианта при сборке `Union`-полей - для deflation поддерживается включение режима `explicit_casts`, приводящего типы к тем, которые указаны в аннотациях (не распространяется на `Union`-типы, т.к. невозможно определить какой из них должен быть выбран) ---- ### Как установить: #### [uv](https://docs.astral.sh/uv/concepts/projects/dependencies/#dependency-sources): ```bash uv add megasniff --index sniff_index=https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple ``` #### [poetry](https://python-poetry.org/docs/repositories/#private-repository-example): 1. Добавить репозиторий в `pyproject.toml` ```bash poetry source add --priority=supplemental sniff_index https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple ``` 2. Поставить пакет ```bash poetry add --source sniff_index megasniff ``` ---- ### Strict-mode: #### Strict-mode off: ``` @dataclass class A: a: list[int] ``` ``` >>> {"a": [1, 1.1, "321"]} <<< A(a=[1, 1, 321]) >>> A(a=[1, 1.1, "321"]) <<< {"a": [1, 1.1, "321"]} # explicit_casts=False <<< {"a": [1, 1, 321]} # explicit_casts=True ``` #### Strict-mode on: ``` @dataclass class A: a: list[int] ``` ``` >>> {"a": [1, 1.1, "321"]} <<< FieldValidationException, т.к. 1.1 не является int >>> A(a=[1, 1.1, "321"]) <<< FieldValidationException, т.к. 1.1 не является int ``` ### Tuple unwrap ``` fn = infl.schema_to_inflator( (('a', int), TupleSchemaItem(Optional[list[int]], key_name='b', has_default=True, default=None))) ``` Создаёт `fn: (dict[str,Any]) -> tuple[int, Optional[list[int]]]: ...` (сигнатура остаётся `(dict[str,Any])->tuple`)