Разобрать ожидаемую ошибку API#

Ожидаемый отказ входит в контракт операции. Объявите модель тела, наследника ApiError и соответствие в errors=. Вызывающий сможет обработать каждый исход отдельным except, без ветвления по числовому статусу.

Ответ сайта

Правило операции

Что получает вызывающий

200 с заказом

HttpOperation[Order]

Order

404 с проблемой

errors={404: OrderNotFound}

OrderNotFound

429 с проблемой

errors={429: RateLimited}

RateLimited

→ Request · GET /orders/missing

← Response · 404 Not Found

{"code": "order_not_found", "message": "Unknown order missing"}
# examples/docs/response_errors.py
@dataclass(frozen=True, slots=True, kw_only=True)
class GetOrder(HttpOperation[Order]):
    __http__ = Http.get(
        "/orders/{order_id}",
        errors={404: OrderNotFound, 429: RateLimited},
    )

    order_id: Path[str]


class OrdersApi(SyncApi):
    get = op(GetOrder)

ApiError[ApiProblem] сохраняет типизированное тело в .error. Свойство .summary содержит безопасную сводку: идентификатор операции, статус, тип содержимого, номер попытки, метод и отредактированный адрес. Полный контекст ответа остаётся в .context.

Файл примера целиком
"""Map expected error responses to typed exceptions."""

from __future__ import annotations

from dataclasses import dataclass

from pydantic import BaseModel

from eazy_sdk import Http, HttpOperation, Path, SyncApi, op
from eazy_sdk.response import ApiError
from examples.docs.request_site import request_client


class ApiProblem(BaseModel):
    code: str
    message: str


class OrderNotFound(ApiError[ApiProblem]):
    pass


class RateLimited(ApiError[ApiProblem]):
    pass


@dataclass(frozen=True, slots=True)
class Order:
    id: str
    status: str
    total: int


# docs:example:start
# examples/docs/response_errors.py
@dataclass(frozen=True, slots=True, kw_only=True)
class GetOrder(HttpOperation[Order]):
    __http__ = Http.get(
        "/orders/{order_id}",
        errors={404: OrderNotFound, 429: RateLimited},
    )

    order_id: Path[str]


class OrdersApi(SyncApi):
    get = op(GetOrder)
# docs:example:end


def main() -> None:
    with request_client() as client:
        orders = OrdersApi(client)
        for order_id in ("missing", "busy"):
            try:
                orders.get(order_id=order_id)
            except OrderNotFound as error:
                print(f"{error.summary.status_code}: {error.error.message}")
            except RateLimited as error:
                print(f"{error.summary.status_code}: {error.error.code}")


if __name__ == "__main__":
    main()

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

404: Unknown order missing
429: rate_limited

Разместить общие ошибки#

Операция объявляет собственные ошибки. Роутер добавляет отказы всего сервиса через errors, а ClientConfig(errors=...) задаёт ответы конкретного хоста. При совпадении выигрывает объявление, которое находится ближе к операции.

Условие when= различает ответы с одинаковым статусом по телу, заголовку или типу содержимого. Предикаты из eazy_sdk.response.match проверяют сырые байты до разбора модели. Неизвестный ответ даёт UnexpectedResponseError, а неверное тело уже выбранного случая даёт MalformedResponseError.

Дальше#