Skip to content

fix(connection): ретраить 429 и не падать на не-JSON теле ошибки - #188

Open
Fgeeha wants to merge 1 commit into
love-apples:mainfrom
Fgeeha:fix/retry-429
Open

fix(connection): ретраить 429 и не падать на не-JSON теле ошибки#188
Fgeeha wants to merge 1 commit into
love-apples:mainfrom
Fgeeha:fix/retry-429

Conversation

@Fgeeha

@Fgeeha Fgeeha commented Aug 13, 2026

Copy link
Copy Markdown

Проблема

MAX отвечает на превышение лимита запросов статусом 429 с заголовком
Content-Type: application/octet-stream. В BaseConnection.request() это
приводит к двум неприятностям сразу:

  1. 429 не входит в retry_on_statuses (по умолчанию 502, 503, 504), поэтому
    ответ считается окончательным и не повторяется — хотя это ровно тот случай,
    для которого механизм повторов и предназначен;
  2. тело разбирается строгим await response.json(), который на
    application/octet-stream поднимает aiohttp.ContentTypeError. Вызывающий
    код получает ошибку разбора ответа вместо MaxApiError с кодом 429 и не
    может отличить лимит запросов от сбоя в самой библиотеке.

На практике это заставляет прикладной код патчить BaseConnection.request,
чтобы ловить ContentTypeError и трактовать его как 429, — что явно не то,
чем должен заниматься пользователь SDK.

Решение

  • DEFAULT_RETRY_STATUSES = (429, 502, 503, 504) — 429 повторяется тем же
    backoff, что и серверные ошибки; поведение настраивается как раньше, через
    DefaultConnectionProperties(retry_on_statuses=...);
  • тело ошибочного ответа читается через _read_error_payload(): сначала
    response.json(content_type=None), при неудаче — {"error": <текст ответа>}.
    Успешный путь остаётся строгим;
  • сообщение _RetryableServerError для 429 — «Rate limit 429» вместо
    «Server error 429», лог повторов использует текст исключения.

Совместимость

Меняется значение по умолчанию: раньше 429 отдавался вызывающему коду сразу
(точнее, падал с ContentTypeError), теперь запрос повторяется до
max_retries раз. Кто хочет прежнее поведение — передаёт
retry_on_statuses=(502, 503, 504).

Тесты

tests/test_retry.py, класс TestRateLimitStatus — 3 теста:

  • 429 повторяется без дополнительной настройки соединения;
  • после исчерпания попыток поднимается MaxApiError с code == 429;
  • не-JSON тело ошибки отдаётся как {"error": <текст>}, а не роняет вызов.

Плюс обновлены два существующих ассерта на состав DEFAULT_RETRY_STATUSES.

Локально: pytest -q — 957 passed, 12 skipped; mypy maxapi — чисто;
ruff format --check — чисто.

MAX отвечает на превышение лимита запросов статусом 429 с заголовком
Content-Type: application/octet-stream. Статус не входил в retry_on_statuses,
а разбор тела шёл через строгий response.json(), поэтому вызов падал с
ContentTypeError вместо MaxApiError с кодом 429 — отличить лимит запросов от
сбоя разбора ответа было нельзя.

Теперь 429 входит в DEFAULT_RETRY_STATUSES и повторяется тем же backoff, что
и 5xx, а тело ошибочного ответа читается без опоры на Content-Type: не-JSON
тело отдаётся как {"error": <текст>}.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Добавляет устойчивую обработку временных HTTP-ошибок и ответов с не-JSON телом.

Changes:

  • Добавлен автоматический retry для HTTP 429.
  • Ошибочные ответы разбираются независимо от Content-Type.
  • Добавлены тесты обработки rate limit.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
maxapi/connection/base.py Обрабатывает retry и не-JSON ошибки.
maxapi/client/default.py Добавляет 429 в статусы retry.
tests/test_retry.py Покрывает новую обработку 429.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread maxapi/connection/base.py
response: Ответ с не-2xx статусом.

Returns:
Any: Разобранный JSON либо ``{"error": <текст ответа>}``.
Comment thread tests/test_retry.py
await base.request(method=HTTPMethod.GET, path="/test")

assert exc_info.value.code == 400
assert exc_info.value.raw == {"error": "rate limit exceeded"}
@Olegt0rr Olegt0rr added the bug Something isn't working label Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants