Преобразовать публичные поля в тело запроса#

Используйте BodyProjection, когда вызывающий передаёт плоские предметные значения, а внешний API ждёт вложенный служебный документ. Проекция выполняется для каждой попытки и возвращает структуру, которую затем кодирует выбранное представление тела.

→ Request · POST /register

{
  "account": {"login": "ada", "email": "ada@mail.example"},
  "client": "python"
}

← Response · 201 Created

{"id": "account-42", "login": "ada"}
# examples/docs/request_projection.py
@dataclass(frozen=True, slots=True, kw_only=True)
class RegisterUser(HttpOperation[Account]):
    __http__ = Http.post(
        "/register",
        success={201: Account},
        projection=BodyProjection(
            target=RegistrationWire,
            using=lambda source: {
                "account": {"login": source.login, "email": source.email},
                "client": "python",
            },
            encoding=Body.json(),
            name="registration-v1",
        ),
    )

    login: str
    email: str


class RegistrationApi(SyncApi):
    register = op(RegisterUser)

target описывает закрытую структуру на проводе. using получает значение операции и строит документ. encoding=Body.json() выбирает JSON, а name даёт проекции устойчивое имя для диагностики и схемы.

Файл примера целиком
"""Project flat public inputs into a nested service-owned JSON document."""

from __future__ import annotations

from dataclasses import dataclass
from typing import TypedDict

from eazy_sdk import BodyProjection, Http, HttpOperation, SyncApi, op
from eazy_sdk.request import Body
from examples.docs.request_site import request_client


class AccountWire(TypedDict):
    login: str
    email: str


class RegistrationWire(TypedDict):
    account: AccountWire
    client: str


@dataclass(frozen=True, slots=True)
class Account:
    id: str
    login: str


# docs:projection:start
# examples/docs/request_projection.py
@dataclass(frozen=True, slots=True, kw_only=True)
class RegisterUser(HttpOperation[Account]):
    __http__ = Http.post(
        "/register",
        success={201: Account},
        projection=BodyProjection(
            target=RegistrationWire,
            using=lambda source: {
                "account": {"login": source.login, "email": source.email},
                "client": "python",
            },
            encoding=Body.json(),
            name="registration-v1",
        ),
    )

    login: str
    email: str


class RegistrationApi(SyncApi):
    register = op(RegisterUser)
# docs:projection:end


def main() -> None:
    with request_client() as client:
        account = RegistrationApi(client).register(
            login="ada",
            email="ada@mail.example",
        )

    print(f"registered: {account.login} as {account.id}")


if __name__ == "__main__":
    main()

Запуск: uv run python examples/docs/request_projection.py.

registered: ada as account-42

Разделить уровни представления#

Уровень

Что он меняет

Где настраивается

имя поля

Python-имя в имя на проводе

маркер или адаптер модели

документ

публичные поля в другую структуру

BodyProjection

порядок и строка запроса

стабильный порядок, кодирование пробелов

Wire, FieldOrder, QueryCodec

JSON-байты

пробелы, ASCII, порядок ключей

JsonPolicy

библиотека моделей

чтение и запись моделей

Serialization на корне или роутере

Не помещайте в проекцию авторизацию, капчу, CSRF и другие значения сессии. Их добавляют Identity, зависимости и политики защиты после построения предметного документа.

Дальше#