API methods#

Операция представляет собой класс: его поля описывают запрос, а __http__ задаёт метод, путь и всё остальное, что операция объявляет. Роутер (SyncApi или AsyncApi) публикует операцию через op(...), и публичной сигнатурой SDK становится конструктор класса.

Операция как класс#

from dataclasses import dataclass

from pydantic import BaseModel

from eazy_sdk import UNSET, AsyncApi, Http, HttpOperation, Omittable, Path, Query, op

class User(BaseModel):
    id: int

@dataclass(frozen=True, slots=True, kw_only=True)
class GetUser(HttpOperation[User]):
    __http__ = Http.get("/users/{user_id}")

    user_id: Path[int]
    include: Query[Omittable[str]] = UNSET
    page: Query[int] = 1

class UsersApi(AsyncApi):
    get_user = op(GetUser)

HttpOperation[User] называет тип результата. Класс может быть dataclass, Pydantic-моделью или msgspec-структурой; требование одно: он frozen, потому что запрос является значением. Значения по умолчанию задаются обычными средствами Python, включая default_factory; поле, которое иногда не нужно отправлять вовсе, объявляется Omittable[T] = UNSET.

HTTP-параметры объявляйте keyword-only (kw_only=True): у них нет осмысленного порядка, а добавление поля не должно менять смысл позиционного вызова.

Декоратор как короткая форма#

@api.get(...) синтезирует ровно такой же frozen-класс. Он доступен как UsersApi.get_user.Operation. Отличия двух форм и выбор между ними описаны на странице Объявление операции.

Ответ операции#

Тип результата и есть успешный случай; success= нужен, только когда случаев несколько или у ответа особый статус. Ошибки объявляются мэппингом:

from dataclasses import dataclass

from pydantic import BaseModel

from eazy_sdk import AsyncApi, Http, HttpOperation, Path, op
from eazy_sdk.response import ApiError

class NotFound(BaseModel):
    detail: str

class UserNotFound(ApiError[NotFound]):
    pass

@dataclass(frozen=True, slots=True, kw_only=True)
class GetUser(HttpOperation[User]):
    __http__ = Http.get("/users/{user_id}", errors={404: UserNotFound})

    user_id: Path[int]

class UsersApi(AsyncApi):
    errors = ({429: RateLimited},)

    get_user = op(GetUser)

Ошибки операции складываются с errors роутера; inherit_errors=False отключает наследование. При совпадении статуса выигрывает случай, объявленный ближе к операции. Подробности приведены в Ошибки API.

Namespace api и Http#

Одни и те же глаголы в двух формах:

  • Http.get, Http.post, Http.put, Http.patch, Http.delete, Http.head, Http.options, Http.trace: для класса;

  • api.get, api.post, …: для декоратора.

Для другого валидного HTTP token есть общий вход: Http.request("PROPFIND", "/resources") и api.request("PROPFIND", "/resources"). Невалидный token отклоняется при создании спецификации.

Параметры Http.* и api.*#

Параметры перечислены в порядке _HttpOptions: от часто используемых к редким. path является единственным позиционным параметром.

Что возвращается

Параметр

Назначение

Default

path

URL path с {placeholder}

обязательный

success

Успешные случаи, когда их больше одного или статус не 200

тип результата

errors

Типизированные ошибки: {404: NotFound}

{}

Как выглядит запрос

Параметр

Назначение

Default

projection

BodyProjection задаёт закрытую структуру представления тела

нет

wire

Wire задаёт порядок, кодирование, шифрование и точность байтов

наследуется

Что сервис требует от запроса (всё наследуется по MRO роутера)

Параметр

Назначение

Default

security

Схема или политика авторизации; None снимает схему сервиса

наследуется

signing

Правила подписи; () снимает подпись сервиса

наследуется

crypto

Профиль шифрования payload; None отключает

наследуется

protections

Обязательные protection-требования

()

requires

Зависимости запроса (dependency(...))

()

inject

Готовые значения в место запроса (Inject(...))

()

Редко нужное

Параметр

Назначение

Default

fallback

Случай «на всё остальное», ключ DEFAULT

нет

inherit_errors

Добавлять ли errors роутера к своим

True

idempotent

Явное разрешение на повтор для неидемпотентного метода

выводится из метода

raw_response

Вернуть NormalizedResponse вместо разбора случаев

False

Имена

Параметр

Назначение

Default

operation_id

Стабильная identity операции в диагностике и схемах

имя класса или метода

tags

Метаданные для генераторов и группировки

()

Если success= или errors= не объявлены#

success= не нужен, когда успешный ответ один: тип из HttpOperation[T] уже образует случай «200, семья по модели, модель T». Явный success= нужен для другого статуса ({201: Order}), для нескольких успешных форм или для представления с медиа-типом и условием. Если не объявлено ни HttpOperation[T], ни success=, операция отвергается при объявлении:

PlanError: operation class NoType declares neither HttpOperation[T] nor success=

errors= не обязателен вовсе. Без него у операции просто нет собственных типизированных ошибок: остаются унаследованные от роутера и от ClientConfig(errors=…). Ответ, не подошедший ни под один случай. Это не «тихий успех» и не None:

UnexpectedResponseError: GetUser received an undocumented response: 404 application/json

То есть недокументированный ответ всегда виден как ошибка контракта. Объявляйте errors= тогда, когда хотите отличать 404 от 409 типом исключения, а не когда хотите «чтобы не падало».

Роутер объявляет base_url, security, signing, crypto, wire, errors, unwrap, protocol, signed и allow обычными атрибутами класса; они наследуются по MRO, поэтому общий для нескольких роутеров сервис описывается обычным базовым классом (см. Мультисервисный SDK).

fallback, inherit_errors и raw_response#

fallback задаёт один случай для всего, что не совпало ни с успешным ответом, ни с объявленной ошибкой. Он существует отдельно от errors намеренно: по специфичности он слабее любого статуса и диапазона, и стой он рядом с ними в одном мэппинге, он совпал бы с тем же телом, что и случай с кодом. Поэтому DEFAULT в errors= отвергается при разборе объявления до первого запроса:

PlanError: errors= in 'getUser' cannot use DEFAULT; declare fallback= instead

Пишите fallback для сервиса, который отвечает одной и той же моделью проблемы на всё подряд: fallback=Problem превращает UnexpectedResponseError в обычную ApiError[Problem].

inherit_errors=False отбрасывает errors роутера для одной операции. Нужен там, где сервисный случай неверен именно здесь: например, роутер объявил {404: OrderNotFound}, а у этой операции 404 означает «ресурс ещё не создан» и должен разбираться другой моделью. Значение True по умолчанию ошибки складываются, и при совпадении статуса выигрывает случай, объявленный ближе к операции.

raw_response=True отключает выбор случая: вызов возвращает NormalizedResponse со статусом, заголовками и телом, и ни одна модель не строится. Это выход для ответов, форму которых SDK не описывает, например проксирование, скачивание файла или разбор в приложении. Это не способ «посмотреть статус»: для статуса и заголовков рядом с разобранной моделью есть with_response(...), которое проходит те же случаи и возвращает ResponseEnvelope.

Запрос как значение#

Связанная операция умеет строить, изменять и отправлять запрос. См. Запрос как значение:

request = users.get_user.request(user_id=42)
await users.get_user.send(users.get_user.evolve(request, page=2))

Подготовка без отправки#

Bound operation поддерживает ту же sync/async форму через .prepare(...):

prepared = await users.get_user.prepare(
    user_id=42,
    options=PrepareOptions(resolve_managed=False),
)

prepare выполняет обычные bind/default/project/encode/sign стадии и останавливается на общей границе перед handler send. Pure mode не вызывает providers и возвращает PreparationIncompleteError, если нужны managed values. Full mode (resolve_managed=True) использует test providers, но сеть всё равно не вызывается. PreparedCall редактирует secret headers, cookies, query и private body.