fix: retry any 429, not just those with a parseable retry_after (v0.1.3)

treat every status==429 as retryable: sleep only when retry_after parses, but raise
_Retryable either way so aretry's backoff + max_retries cap engages. previously a 429
with no body retry_after and no Retry-After header (edge/Cloudflare/generic webhook)
returned a terminal ok=False with no retry, contradicting the documented retry-on-429.

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
disqualifier 2026-06-29 17:09:30 -04:00
parent cd93e4f44f
commit f3d2561bf9
4 changed files with 14 additions and 8 deletions

View File

@ -13,9 +13,9 @@ send to the core — inheriting rotation, proxy, retry, and result for free.
## Install ## Install
``` ```
aiowebhooks @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.2 aiowebhooks @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.3
# discord embeds / identity helpers need the extra: # discord embeds / identity helpers need the extra:
aiowebhooks[discord] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.2 aiowebhooks[discord] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.3
``` ```
The base pulls `aiohttp` and `commons` (for the retry/backoff engine). Only The base pulls `aiohttp` and `commons` (for the retry/backoff engine). Only

View File

@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "aiowebhooks" name = "aiowebhooks"
version = "0.1.2" version = "0.1.3"
description = "async webhook sender (aiohttp) with round-robin urls, retry, and proxy rotation; optional discord.py embeds" description = "async webhook sender (aiohttp) with round-robin urls, retry, and proxy rotation; optional discord.py embeds"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [

View File

@ -21,4 +21,4 @@ from .sender import Webhook
__all__ = ["Webhook", "WebhookResult", "WebhookError", "NoUrlsError"] __all__ = ["Webhook", "WebhookResult", "WebhookError", "NoUrlsError"]
__version__ = "0.1.2" __version__ = "0.1.3"

View File

@ -201,10 +201,16 @@ class Webhook:
error=f"http {status}", response=body, proxy=last_proxy, error=f"http {status}", response=body, proxy=last_proxy,
) )
if status == 429:
# every 429 is retryable; honor an explicit retry_after by
# sleeping it, but a 429 with no parseable wait (edge/Cloudflare/
# generic webhook) still retries under aretry's backoff + cap
wait = self._retry_after(status, resp.headers, body) wait = self._retry_after(status, resp.headers, body)
if wait is not None: if wait is not None:
log.warning("webhook 429 on %s; honoring retry_after %.3fs", url, wait) log.warning("webhook 429 on %s; honoring retry_after %.3fs", url, wait)
await asyncio.sleep(wait) await asyncio.sleep(wait)
else:
log.warning("webhook 429 on %s; no retry_after, backing off", url)
raise _Retryable(result) raise _Retryable(result)
if status >= 500: if status >= 500:
raise _Retryable(result) raise _Retryable(result)