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:
parent
28bad7fc7f
commit
ef20bc51f0
25
README.md
25
README.md
@ -13,16 +13,16 @@ 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.3
|
aiowebhooks @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.4
|
||||||
# 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.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
|
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
|
`aiowebhooks[discord]` adds `discord.py` (>=2.3, mainline — not discord.py-self), and
|
||||||
only for `DiscordWebhook`.
|
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
|
## 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):
|
Status retries run through `commons.aretry` (exponential backoff + cap):
|
||||||
|
|
||||||
- **429** — honors the `retry_after` from the body first (Discord sends seconds), then
|
- **429** — always retried, capped by `max_retries`. When a wait is parseable (body
|
||||||
the `Retry-After` header, sleeping that exact value; capped by `max_retries`.
|
`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`.
|
- **5xx** — retried with exponential backoff, capped by `max_retries`.
|
||||||
- **4xx** (other than 429) — fails immediately (no retry), returned as `ok=False`.
|
- **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
|
## 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
|
### v0.1.2
|
||||||
|
|
||||||
- Removed a dead `clock` constructor param (it was stored but never used). Pinned
|
- Removed a dead `clock` constructor param (it was stored but never used). Pinned
|
||||||
|
|||||||
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "aiowebhooks"
|
name = "aiowebhooks"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
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 = [
|
||||||
|
|||||||
@ -21,4 +21,4 @@ from .sender import Webhook
|
|||||||
|
|
||||||
__all__ = ["Webhook", "WebhookResult", "WebhookError", "NoUrlsError"]
|
__all__ = ["Webhook", "WebhookResult", "WebhookError", "NoUrlsError"]
|
||||||
|
|
||||||
__version__ = "0.1.3"
|
__version__ = "0.1.4"
|
||||||
|
|||||||
@ -145,6 +145,16 @@ class Webhook:
|
|||||||
)
|
)
|
||||||
except _Retryable as exhausted:
|
except _Retryable as exhausted:
|
||||||
return exhausted.result
|
return exhausted.result
|
||||||
|
except Exception as error:
|
||||||
|
# never-raises safety net: an unexpected error that escapes the attempt (a
|
||||||
|
# closed injected session -> RuntimeError, a malformed proxy url -> ValueError,
|
||||||
|
# anything not aiohttp.ClientError/TimeoutError) must come back as a failed
|
||||||
|
# result, not propagate out of send()
|
||||||
|
log.warning("webhook send failed unexpectedly on %s: %s", url, error, exc_info=True)
|
||||||
|
return WebhookResult(
|
||||||
|
ok=False, status=None, url=url, attempts=counter[0] or 1,
|
||||||
|
error=f"{type(error).__name__}: {error}",
|
||||||
|
)
|
||||||
|
|
||||||
async def _attempt(
|
async def _attempt(
|
||||||
self, session: aiohttp.ClientSession, url: str, payload: Dict, counter: List[int]
|
self, session: aiohttp.ClientSession, url: str, payload: Dict, counter: List[int]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user