127 lines
4.3 KiB
Markdown
127 lines
4.3 KiB
Markdown
# 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`) |