--- title: API methods description: Операция как frozen-класс, публикация на роутере, декоратор как короткая форма. sources: - eazy_sdk.api - eazy_sdk.preparation api_fingerprint: "sha256:50c4178c3bb810f58ba1336cf2d1acf52efb16876bc36f2f5e4111948aa06238" --- Операция представляет собой класс: его поля описывают запрос, а `__http__` задаёт метод, путь и всё остальное, что операция объявляет. Роутер (`SyncApi` или `AsyncApi`) публикует операцию через `op(...)`, и публичной сигнатурой SDK становится конструктор класса. ## Операция как класс ```python 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`. Отличия двух форм и выбор между ними описаны на странице {doc}`Объявление операции `. ## Ответ операции Тип результата и есть успешный случай; `success=` нужен, только когда случаев несколько или у ответа особый статус. Ошибки объявляются мэппингом: ```python 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` отключает наследование. При совпадении статуса выигрывает случай, объявленный ближе к операции. Подробности приведены в {doc}`Ошибки 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=`, операция отвергается при объявлении: ```text PlanError: operation class NoType declares neither HttpOperation[T] nor success= ``` `errors=` не обязателен вовсе. Без него у операции просто нет собственных типизированных ошибок: остаются унаследованные от роутера и от `ClientConfig(errors=…)`. Ответ, не подошедший ни под один случай. Это не «тихий успех» и не `None`: ```text UnexpectedResponseError: GetUser received an undocumented response: 404 application/json ``` То есть недокументированный ответ всегда виден как ошибка контракта. Объявляйте `errors=` тогда, когда хотите отличать `404` от `409` типом исключения, а не когда хотите «чтобы не падало». Роутер объявляет `base_url`, `security`, `signing`, `crypto`, `wire`, `errors`, `unwrap`, `protocol`, `signed` и `allow` обычными атрибутами класса; они наследуются по MRO, поэтому общий для нескольких роутеров сервис описывается обычным базовым классом (см. {doc}`Мультисервисный SDK `). ## `fallback`, `inherit_errors` и `raw_response` `fallback` задаёт один случай для всего, что не совпало ни с успешным ответом, ни с объявленной ошибкой. Он существует отдельно от `errors` намеренно: по специфичности он слабее любого статуса и диапазона, и стой он рядом с ними в одном мэппинге, он совпал бы с тем же телом, что и случай с кодом. Поэтому `DEFAULT` в `errors=` отвергается при разборе объявления до первого запроса: ```text 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`. ## Запрос как значение Связанная операция умеет строить, изменять и отправлять запрос. См. {doc}`Запрос как значение `: ```python 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(...)`: ```python 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.