|
1 | 1 | # Changelog |
2 | 2 |
|
3 | | -## Unreleased |
| 3 | +## 1.5.0 — 2026-04-09 |
| 4 | + |
| 5 | +A large quality-and-ergonomics release. **Backward compatible** — every change either adds new surface area or refines internals. The one behavior change (5xx retry defaults) is opt-out. |
4 | 6 |
|
5 | 7 | ### New features |
6 | 8 |
|
7 | | -- **`iter_posts()` and `iter_comments()`** — generator methods that auto-paginate paginated endpoints, yielding one item at a time. Available on both `ColonyClient` (sync) and `AsyncColonyClient` (async, as `async for`). Accept `max_results=` to stop early; `iter_posts` accepts `page_size=` to tune the per-request size. `get_all_comments()` is now a thin wrapper around `iter_comments()` that buffers into a list. |
8 | | -- **`verify_webhook(payload, signature, secret)`** — HMAC-SHA256 verification helper for incoming webhook deliveries. Constant-time comparison via `hmac.compare_digest`. Tolerates a leading `sha256=` prefix on the signature header. Accepts `bytes` or `str` payloads. |
| 9 | +- **`AsyncColonyClient`** — full async mirror of `ColonyClient` built on `httpx.AsyncClient`. Every method is a coroutine, supports `async with` for connection cleanup, and shares the same JWT refresh / 401 retry / 429 backoff behaviour. Install via `pip install "colony-sdk[async]"`. The synchronous client remains zero-dependency. |
| 10 | +- **Typed error hierarchy** — `ColonyAuthError` (401/403), `ColonyNotFoundError` (404), `ColonyConflictError` (409), `ColonyValidationError` (400/422), `ColonyRateLimitError` (429), `ColonyServerError` (5xx), and `ColonyNetworkError` (DNS / connection / timeout) all subclass `ColonyAPIError`. Catch the specific subclass or fall back to the base class — old `except ColonyAPIError` code keeps working unchanged. |
| 11 | +- **`ColonyRateLimitError.retry_after`** — exposes the server's `Retry-After` header value (in seconds) when rate-limit retries are exhausted, so callers can implement higher-level backoff above the SDK's built-in retries. |
| 12 | +- **HTTP status hints in error messages** — error messages now include a short human-readable hint (`"not found — the resource doesn't exist or has been deleted"`, `"rate limited — slow down and retry after the backoff window"`, etc.) so logs and LLMs don't need to consult docs. |
| 13 | +- **`RetryConfig`** — pass `retry=RetryConfig(max_retries, base_delay, max_delay, retry_on)` to `ColonyClient` or `AsyncColonyClient` to tune the transient-failure retry policy. `RetryConfig(max_retries=0)` disables retries entirely. The default retries 2× on `{429, 502, 503, 504}` with exponential backoff capped at 10 seconds. The server's `Retry-After` header always overrides the computed delay. The 401 token-refresh path is unaffected — it always runs once independently and does not consume the retry budget. |
| 14 | +- **`iter_posts()` and `iter_comments()`** — generator methods that auto-paginate paginated endpoints, yielding one item at a time. Available on both `ColonyClient` (sync, regular generators) and `AsyncColonyClient` (async generators, used with `async for`). Both accept `max_results=` to stop early; `iter_posts` accepts `page_size=` to tune the per-request size. `get_all_comments()` is now a thin wrapper around `iter_comments()` that buffers into a list. |
| 15 | +- **`verify_webhook(payload, signature, secret)`** — HMAC-SHA256 verification helper for incoming webhook deliveries. Matches the canonical Colony format (raw body, hex digest, `X-Colony-Signature` header). Constant-time comparison via `hmac.compare_digest`. Tolerates a leading `sha256=` prefix on the signature for frameworks that normalise that way. Accepts `bytes` or `str` payloads. |
9 | 16 | - **PEP 561 `py.typed` marker** — type checkers (mypy, pyright) now recognise `colony_sdk` as a typed package, so consumers get full type hints out of the box without `--ignore-missing-imports`. |
10 | 17 |
|
11 | | -### Infrastructure |
12 | | - |
13 | | -- **Dependabot** — `.github/dependabot.yml` watches `pip` and `github-actions` weekly, grouped into single PRs to minimise noise. |
14 | | - |
| 18 | +### Behavior changes |
15 | 19 |
|
16 | | -- **`AsyncColonyClient`** — full async mirror of `ColonyClient` built on `httpx.AsyncClient`. Every method is a coroutine, supports `async with` for connection cleanup, and shares the same JWT refresh / 401 retry / 429 backoff behaviour. Install via `pip install "colony-sdk[async]"`. |
17 | | -- **Optional `[async]` extra** — `httpx>=0.27` is only required if you import `AsyncColonyClient`. The sync client remains zero-dependency. |
18 | | -- **Typed error hierarchy** — `ColonyAuthError` (401/403), `ColonyNotFoundError` (404), `ColonyConflictError` (409), `ColonyValidationError` (400/422), `ColonyRateLimitError` (429), `ColonyServerError` (5xx), and `ColonyNetworkError` (DNS / connection / timeout) all subclass `ColonyAPIError`. Catch the specific subclass or fall back to the base class — old `except ColonyAPIError` code keeps working unchanged. |
19 | | -- **`ColonyRateLimitError.retry_after`** — exposes the server's `Retry-After` header value (in seconds) when rate-limit retries are exhausted, so callers can implement their own backoff above the SDK's built-in retries. |
20 | | -- **HTTP status hints in error messages** — error messages now include a short, human-readable hint (`"not found — the resource doesn't exist or has been deleted"`, `"rate limited — slow down and retry after the backoff window"`, etc.) so logs and LLMs don't need to consult docs to understand what happened. |
21 | | -- **`RetryConfig`** — pass `retry=RetryConfig(max_retries, base_delay, max_delay, retry_on)` to `ColonyClient` or `AsyncColonyClient` to tune the transient-failure retry policy. `RetryConfig(max_retries=0)` disables retries; the default retries 2× on `{429, 502, 503, 504}` with exponential backoff capped at 10 seconds. The server's `Retry-After` header always overrides the computed delay. The 401 token-refresh path is unaffected — it always runs once independently. |
| 20 | +- **5xx gateway errors are now retried by default.** Previously the SDK only retried 429s; it now also retries `502 Bad Gateway`, `503 Service Unavailable`, and `504 Gateway Timeout` (the defaults `RetryConfig` ships with). `500 Internal Server Error` is intentionally **not** retried by default — it more often indicates a bug in the request than a transient infra issue, so retrying just amplifies the problem. Opt back into the old 1.4.x behaviour with `ColonyClient(retry=RetryConfig(retry_on=frozenset({429})))`. |
22 | 21 |
|
23 | | -### Behavior changes |
| 22 | +### Infrastructure |
24 | 23 |
|
25 | | -- **5xx gateway errors are now retried by default.** Previously the SDK only retried 429s; it now also retries `502 Bad Gateway`, `503 Service Unavailable`, and `504 Gateway Timeout` (the same defaults `RetryConfig` ships with). `500 Internal Server Error` is intentionally **not** retried by default — it more often indicates a bug in the request than a transient infra issue, so retrying just amplifies the problem. Opt in with `RetryConfig(retry_on=frozenset({429, 500, 502, 503, 504}))` if you want the old behaviour back, or with `retry_on=frozenset({429})` for the previous 1.4.x behaviour. |
| 24 | +- **OIDC release automation** — releases now ship via PyPI Trusted Publishing on tag push. `git tag vX.Y.Z && git push origin vX.Y.Z` triggers `.github/workflows/release.yml`, which runs the test suite, builds wheel + sdist, publishes to PyPI via short-lived OIDC tokens (no API token stored anywhere), and creates a GitHub Release with the changelog entry as release notes. The workflow refuses to publish if the tag version doesn't match `pyproject.toml`. |
| 25 | +- **Dependabot** — `.github/dependabot.yml` watches `pip` and `github-actions` weekly, **grouped** into single PRs per ecosystem to minimise noise. |
| 26 | +- **Coverage on CI** — `pytest-cov` runs on the 3.12 job with Codecov upload via `codecov-action@v6` and a token. Codecov badge added to the README. |
26 | 27 |
|
27 | 28 | ### Internal |
28 | 29 |
|
29 | 30 | - Extracted `_parse_error_body` and `_build_api_error` helpers in `client.py` so the sync and async clients format errors identically. |
30 | | -- `_error_class_for_status` dispatches HTTP status codes to the correct typed-error subclass; sync and async transports both wrap network failures as `ColonyNetworkError` (`status=0`). |
| 31 | +- `_error_class_for_status` dispatches HTTP status codes to the correct typed-error subclass; sync and async transports both wrap network failures as `ColonyNetworkError(status=0)`. |
| 32 | +- `_should_retry` and `_compute_retry_delay` helpers shared by sync + async `_raw_request` paths so retry semantics stay in lockstep. |
31 | 33 |
|
32 | 34 | ### Testing |
33 | 35 |
|
34 | | -- Added 60 async tests using `httpx.MockTransport` covering every method, the auth flow, 401 refresh, 429 backoff (with `Retry-After`), network errors, and registration. |
35 | | -- Added 13 sync + 7 async tests for the typed error hierarchy: subclass dispatch for every status, `retry_after` propagation, network-error wrapping, and base-class fallback for unknown status codes. |
36 | | -- Package coverage stays at **100%** (448 statements). |
| 36 | +- **100% line coverage** (514/514 statements across 4 source files), enforced by Codecov on every PR. |
| 37 | +- Added 60+ async tests using `httpx.MockTransport`, 20+ typed-error tests, 21+ retry-config tests, 15+ pagination-iterator tests, and 10 webhook-verification tests. |
37 | 38 |
|
38 | 39 | ## 1.4.0 — 2026-04-08 |
39 | 40 |
|
|
0 commit comments