Skip to content

feat: AsyncColonyClient (httpx) as optional [async] extra#18

Merged
jackparnell merged 1 commit intomainfrom
feature/async-client
Apr 9, 2026
Merged

feat: AsyncColonyClient (httpx) as optional [async] extra#18
jackparnell merged 1 commit intomainfrom
feature/async-client

Conversation

@ColonistOne
Copy link
Copy Markdown
Collaborator

Summary

Adds AsyncColonyClient — a full async mirror of ColonyClient built on httpx.AsyncClient. Every method is a coroutine, supports async with, and shares the same JWT refresh / 401 retry / 429 backoff behaviour as the sync client.

Why

Downstream packages (langchain-colony, crewai-colony, the MCP server) currently fake async by wrapping the sync client in asyncio.to_thread. That serializes through a thread pool — asyncio.gather of many calls gets no real concurrency. With AsyncColonyClient, fan-out is genuinely parallel via httpx connection pooling.

Packaging

  • httpx is an optional dependency under the [async] extra. The sync client stays zero-dep.
  • from colony_sdk import AsyncColonyClient lazy-imports via __getattr__, so users who never touch async never load httpx.
  • Sync-only install: pip install colony-sdk (unchanged)
  • Async install: pip install "colony-sdk[async]"

Internals

  • Extracted _parse_error_body and _build_api_error helpers in client.py so sync and async error formatting stays in lockstep — single source of truth for the error message format.
  • mypy override for httpx (optional dep, not in the typecheck job's environment).

Tests

  • 60 new async tests using httpx.MockTransport (no network) covering every method, the auth flow, 401 refresh, 429 backoff (with Retry-After), network errors, registration, and lifecycle (async with / aclose).
  • Package coverage stays at 100% (406 / 406 statements).

Example

import asyncio
from colony_sdk import AsyncColonyClient

async def main():
    async with AsyncColonyClient("col_your_api_key") as client:
        me, posts, notifs = await asyncio.gather(
            client.get_me(),
            client.get_posts(colony="general", limit=10),
            client.get_notifications(unread_only=True),
        )

asyncio.run(main())

Test plan

  • pytest — 137 passed, 6 skipped, 100% coverage
  • ruff check and ruff format --check clean
  • mypy src/ — Success
  • Sync client still importable without httpx installed
  • CI green on Python 3.10 / 3.11 / 3.12 / 3.13
  • Codecov upload still succeeds

Adds a 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 as
the sync client.

Why: downstream packages (langchain-colony, crewai-colony, mcp server)
currently fake async by wrapping the sync client in `asyncio.to_thread`.
That serializes through a thread pool — `asyncio.gather` of many calls
gets no real concurrency. With AsyncColonyClient, fan-out is genuinely
parallel via httpx connection pooling.

Packaging:
- httpx is an optional dependency under the [async] extra. The sync
  client stays zero-dep. Importing `colony_sdk.ColonyClient` does not
  load httpx.
- `from colony_sdk import AsyncColonyClient` lazy-imports via module
  __getattr__, so users who never touch async never load httpx.

Internals:
- Extracted `_parse_error_body` and `_build_api_error` helpers in
  client.py so sync and async error formatting stays in lockstep.
- mypy override for httpx (optional dep, not in typecheck job).

Tests:
- 60 new async tests using httpx.MockTransport — every method, auth
  flow, 401 refresh, 429 backoff (with Retry-After), network errors,
  registration, lifecycle (`async with` / `aclose`).
- Package coverage stays at 100% (406 / 406 statements).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 9, 2026

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

@jackparnell jackparnell merged commit 82910b7 into main Apr 9, 2026
7 checks passed
ColonistOne added a commit that referenced this pull request Apr 9, 2026
Two changes that ship together so v1.5.0 can be the first release cut
via the new automation:

1. Release workflow at .github/workflows/release.yml — triggered on
   `v*` tag push. Stages:
     - test:           runs ruff, mypy, pytest before anything else
     - build:          builds wheel + sdist, refuses to proceed if
                       the tag version doesn't match pyproject.toml
     - publish:        uploads to PyPI via OIDC trusted publishing
                       (no API token stored anywhere — short-lived
                       token minted by PyPI from the GitHub Actions
                       OIDC identity at publish time)
     - github-release: extracts the matching CHANGELOG section and
                       creates a GitHub Release with the wheel + sdist
                       attached

2. Version bump 1.4.0 → 1.5.0 in pyproject.toml and __init__.py.

3. CHANGELOG: consolidated the 1.5.0 section into a clean, ordered
   summary covering everything that's landed since 1.4.0:
     - AsyncColonyClient (PR #18)
     - Typed error hierarchy (PR #19)
     - RetryConfig + 5xx default retry (PR #20)
     - py.typed + verify_webhook + Dependabot (PR #21)
     - Pagination iterators (PR #23)
     - Coverage + Codecov (PR #17)
     - This release automation

Coverage at 100% (514/514 statements). 215 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ColonistOne added a commit that referenced this pull request Apr 9, 2026
Two changes that ship together so v1.5.0 can be the first release cut
via the new automation:

1. Release workflow at .github/workflows/release.yml — triggered on
   `v*` tag push. Stages:
     - test:           runs ruff, mypy, pytest before anything else
     - build:          builds wheel + sdist, refuses to proceed if
                       the tag version doesn't match pyproject.toml
     - publish:        uploads to PyPI via OIDC trusted publishing
                       (no API token stored anywhere — short-lived
                       token minted by PyPI from the GitHub Actions
                       OIDC identity at publish time)
     - github-release: extracts the matching CHANGELOG section and
                       creates a GitHub Release with the wheel + sdist
                       attached

2. Version bump 1.4.0 → 1.5.0 in pyproject.toml and __init__.py.

3. CHANGELOG: consolidated the 1.5.0 section into a clean, ordered
   summary covering everything that's landed since 1.4.0:
     - AsyncColonyClient (PR #18)
     - Typed error hierarchy (PR #19)
     - RetryConfig + 5xx default retry (PR #20)
     - py.typed + verify_webhook + Dependabot (PR #21)
     - Pagination iterators (PR #23)
     - Coverage + Codecov (PR #17)
     - This release automation

Coverage at 100% (514/514 statements). 215 tests passing.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants