fix: remove dead clock param; pin commons v0.2.1 (v0.1.2)
the clock= constructor param was stored (self._clock) but never read — the 429 retry_after wait uses asyncio.sleep directly. it was dead code, and the CLAUDE.md wrongly claimed it made 429 timing test-controllable. remove the param + the unused time import, and correct the doc (tests patch commons.retry's sleep + sender.asyncio .sleep, not a clock seam). bump the commons pin to v0.2.1 (retry attempts floor). verified: clock param gone, constructs fine, 18/18 fix harness intact. Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
parent
40f8cc5b5f
commit
aec0a5cc2b
53
README.md
53
README.md
@ -13,13 +13,14 @@ 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.0
|
aiowebhooks @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.1
|
||||||
# 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.0
|
aiowebhooks[discord] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiowebhooks.git@v0.1.1
|
||||||
```
|
```
|
||||||
|
|
||||||
The base pulls `aiohttp`. Only `aiowebhooks[discord]` adds `discord.py` (>=2.3,
|
The base pulls `aiohttp` and `commons` (for the retry/backoff engine). Only
|
||||||
mainline — not discord.py-self), and only for `DiscordWebhook`.
|
`aiowebhooks[discord]` adds `discord.py` (>=2.3, mainline — not discord.py-self), and
|
||||||
|
only for `DiscordWebhook`.
|
||||||
|
|
||||||
## Core sender
|
## Core sender
|
||||||
|
|
||||||
@ -65,12 +66,15 @@ result.proxy # canonical proxy string used (host:port:user:pass / host:port)
|
|||||||
|
|
||||||
## Retry & rate limits
|
## Retry & rate limits
|
||||||
|
|
||||||
- **429** — waits the `retry_after` from the body first (Discord sends seconds), then
|
Status retries run through `commons.aretry` (exponential backoff + cap):
|
||||||
the `Retry-After` header, then retries; capped by `max_retries`.
|
|
||||||
- **5xx** — retried, capped by `max_retries`.
|
- **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`.
|
- **4xx** (other than 429) — fails immediately (no retry), returned as `ok=False`.
|
||||||
|
|
||||||
Exceeding a cap returns a failed result rather than looping.
|
Exceeding a cap returns a failed result rather than looping — and the result carries the
|
||||||
|
**real** last status/body (not a synthetic placeholder).
|
||||||
|
|
||||||
## Proxy rotation (optional, duck-typed)
|
## Proxy rotation (optional, duck-typed)
|
||||||
|
|
||||||
@ -91,9 +95,10 @@ result = await wh.send(payload) # sends through pm.get(); on a timeout/connect
|
|||||||
```
|
```
|
||||||
|
|
||||||
On a timeout/connection error the current proxy is burned and the next is tried, up to
|
On a timeout/connection error the current proxy is burned and the next is tried, up to
|
||||||
`max_proxy_retries`. A provider `ProxiesExhaustedError` (or hitting the cap) returns a
|
`max_proxy_retries`. Hitting the cap, or **any exception from the provider's
|
||||||
failed result — never an infinite loop. With no provider, a timeout just fails after
|
`get()`/`burn()`** (the provider is duck-typed and never imported, so its exception
|
||||||
normal retry.
|
types can't be caught by class), returns a failed result — never an infinite loop, never
|
||||||
|
an escape. With no provider, a timeout just fails after normal retry.
|
||||||
|
|
||||||
## Discord (`aiowebhooks[discord]`)
|
## Discord (`aiowebhooks[discord]`)
|
||||||
|
|
||||||
@ -124,11 +129,27 @@ Without the extra installed, importing `aiowebhooks` still works; constructing o
|
|||||||
|
|
||||||
- Every send returns a `WebhookResult`; the core never raises on a send failure and
|
- Every send returns a `WebhookResult`; the core never raises on a send failure and
|
||||||
never prints. Callers check `result.ok`.
|
never prints. Callers check `result.ok`.
|
||||||
- v0.1.0 is JSON-only: **files/attachments, `tts`, and `allowed_mentions` are out**
|
- JSON-only: **files/attachments, `tts`, and `allowed_mentions` are out** (deliberate
|
||||||
(deliberate scope cut, addable later). The Discord surface is content + embeds +
|
scope cut, addable later). The Discord surface is content + embeds + identity.
|
||||||
identity.
|
- Rotation is round-robin only; try-next-on-failure across URLs is a later feature.
|
||||||
- v0.1.0 rotation is round-robin only; try-next-on-failure across URLs is a later
|
|
||||||
feature.
|
## Changelog
|
||||||
|
|
||||||
|
### v0.1.2
|
||||||
|
|
||||||
|
- Removed a dead `clock` constructor param (it was stored but never used). Pinned
|
||||||
|
`commons` to v0.2.1.
|
||||||
|
|
||||||
|
### v0.1.1
|
||||||
|
|
||||||
|
- **Never-raises contract hardened:** an error from a duck-typed proxy provider's
|
||||||
|
`get()`/`burn()` (e.g. `aioproxies.burn()` raising `ValueError` for an unknown proxy)
|
||||||
|
used to escape `send()`. Now any provider exception is caught and converted to a
|
||||||
|
failed result.
|
||||||
|
- **Retry via `commons.aretry`:** 429/5xx retry moved onto the shared backoff engine —
|
||||||
|
5xx now backs off (was a tight no-backoff loop), and exhausted retries return the
|
||||||
|
**real** last status/body instead of a synthetic placeholder. Adds a `commons`
|
||||||
|
dependency.
|
||||||
|
|
||||||
## Versioning
|
## Versioning
|
||||||
|
|
||||||
|
|||||||
@ -4,12 +4,12 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "aiowebhooks"
|
name = "aiowebhooks"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
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 = [
|
||||||
"aiohttp>=3.9",
|
"aiohttp>=3.9",
|
||||||
"commons @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.2.0",
|
"commons @ git+ssh://git@git.rethinkstudios.io/rethink-public/commons.git@v0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|||||||
@ -21,4 +21,4 @@ from .sender import Webhook
|
|||||||
|
|
||||||
__all__ = ["Webhook", "WebhookResult", "WebhookError", "NoUrlsError"]
|
__all__ = ["Webhook", "WebhookResult", "WebhookError", "NoUrlsError"]
|
||||||
|
|
||||||
__version__ = "0.1.1"
|
__version__ = "0.1.2"
|
||||||
|
|||||||
@ -8,7 +8,6 @@ the actual POST here so it inherits rotation / proxy / retry / result.
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
from typing import Dict, List, Optional, Union
|
from typing import Dict, List, Optional, Union
|
||||||
from urllib.parse import unquote, urlsplit
|
from urllib.parse import unquote, urlsplit
|
||||||
|
|
||||||
@ -72,7 +71,6 @@ class Webhook:
|
|||||||
timeout: float = 15,
|
timeout: float = 15,
|
||||||
max_retries: int = 3,
|
max_retries: int = 3,
|
||||||
max_proxy_retries: int = 3,
|
max_proxy_retries: int = 3,
|
||||||
clock=time.monotonic,
|
|
||||||
):
|
):
|
||||||
self._urls = [urls] if isinstance(urls, str) else list(urls)
|
self._urls = [urls] if isinstance(urls, str) else list(urls)
|
||||||
if not self._urls:
|
if not self._urls:
|
||||||
@ -82,7 +80,6 @@ class Webhook:
|
|||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.max_retries = max_retries
|
self.max_retries = max_retries
|
||||||
self.max_proxy_retries = max_proxy_retries
|
self.max_proxy_retries = max_proxy_retries
|
||||||
self._clock = clock
|
|
||||||
self._index = 0
|
self._index = 0
|
||||||
|
|
||||||
def _next_url(self) -> str:
|
def _next_url(self) -> str:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user