Compare commits

..

No commits in common. "main" and "v0.1.3" have entirely different histories.
main ... v0.1.3

5 changed files with 9 additions and 37 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
# claude
.claude/
CLAUDE.md
# python
__pycache__/

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.4
aiowebhooks @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.3
# discord embeds / identity helpers need the extra:
aiowebhooks[discord] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.4
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
`aiowebhooks[discord]` adds `discord.py` (>=2.3, mainline — not discord.py-self), and
only for `DiscordWebhook`.
Drop the `@v0.1.4` suffix from the line above to install the latest unpinned.
Drop the `@v0.1.3` suffix from the line above to install the latest unpinned.
## Core sender
@ -72,10 +72,8 @@ result.proxy # canonical proxy string used (host:port:user:pass / host:port)
Status retries run through `commons.aretry` (exponential backoff + cap):
- **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.
- **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`.
- **5xx** — retried with exponential backoff, capped by `max_retries`.
- **4xx** (other than 429) — fails immediately (no retry), returned as `ok=False`.
@ -141,19 +139,6 @@ 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

View File

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

View File

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

View File

@ -145,16 +145,6 @@ class Webhook:
)
except _Retryable as exhausted:
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(
self, session: aiohttp.ClientSession, url: str, payload: Dict, counter: List[int]
@ -214,10 +204,7 @@ class Webhook:
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.
# note: aretry ALSO sleeps its backoff between retries, so an
# honored retry_after is additive (retry_after + backoff) — this
# only ever over-waits, never under-waits the server's hint
# generic webhook) still retries under aretry's backoff + cap
wait = self._retry_after(status, resp.headers, body)
if wait is not None:
log.warning("webhook 429 on %s; honoring retry_after %.3fs", url, wait)