fix: never-raises net widened to unexpected exceptions; changelog/429 docs (v0.1.4)

M-2: _attempt caught only (aiohttp.ClientError, asyncio.TimeoutError); an unexpected
error escaping the attempt (closed injected session -> RuntimeError, malformed proxy url
-> ValueError) propagated out of send(), breaking the documented 'never raises on a send
failure' contract. add an outer catch-all in _send_loop converting any such exception to a
falsy WebhookResult(ok=False), logged at warning with exc_info.

aiowebhooks-F3: README 429 section + changelog were stale vs the v0.1.3 'retry any 429'
fix; added the no-parseable-wait-still-retries wording and v0.1.3/v0.1.4 changelog entries.

verified by execution: closed-session (RuntimeError) and bad-proxy (ValueError) controls
both fire and now return ok=False instead of raising.

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
2026-06-29 20:47:30 -04:00
parent 28bad7fc7f
commit ef20bc51f0
4 changed files with 32 additions and 7 deletions
+20 -5
View File
@@ -13,16 +13,16 @@ send to the core — inheriting rotation, proxy, retry, and result for free.
## Install
```
aiowebhooks @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.3
aiowebhooks @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.4
# discord embeds / identity helpers need the extra:
aiowebhooks[discord] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.3
aiowebhooks[discord] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.4
```
The base pulls `aiohttp` and `commons` (for the retry/backoff engine). Only
`aiowebhooks[discord]` adds `discord.py` (>=2.3, mainline — not discord.py-self), and
only for `DiscordWebhook`.
Drop the `@v0.1.3` suffix from the line above to install the latest unpinned.
Drop the `@v0.1.4` suffix from the line above to install the latest unpinned.
## Core sender
@@ -72,8 +72,10 @@ result.proxy # canonical proxy string used (host:port:user:pass / host:port)
Status retries run through `commons.aretry` (exponential backoff + cap):
- **429** — honors the `retry_after` from the body first (Discord sends seconds), then
the `Retry-After` header, sleeping that exact value; capped by `max_retries`.
- **429** — always retried, capped by `max_retries`. When a wait is parseable (body
`retry_after` first — Discord sends seconds — then the `Retry-After` header) it sleeps
that value before retrying; a 429 with no parseable wait (edge/Cloudflare/generic
webhook) still retries under aretry's backoff rather than failing one-shot.
- **5xx** — retried with exponential backoff, capped by `max_retries`.
- **4xx** (other than 429) — fails immediately (no retry), returned as `ok=False`.
@@ -139,6 +141,19 @@ Without the extra installed, importing `aiowebhooks` still works; constructing o
## Changelog
### v0.1.4
- **Never-raises net widened:** an unexpected exception that escapes a send attempt (a
closed injected session → `RuntimeError`, a malformed proxy URL → `ValueError`) now
converts to a falsy `WebhookResult(ok=False, ...)` instead of propagating out of
`send()`, restoring the documented contract for those edge triggers.
### v0.1.3
- **429 always retries:** every `429` is now retryable under aretry's backoff + cap, not
only those with a parseable `retry_after`. A 429 with no body `retry_after` and no
`Retry-After` header (edge/Cloudflare/generic webhook) previously failed one-shot.
### v0.1.2
- Removed a dead `clock` constructor param (it was stored but never used). Pinned