Add native WebSocket support by vendoring httpx-ws

Merged
KL
3mo ago
+3243-7

Ports httpx-ws (MIT, by @frankie567) into httpx2 as a native httpx2.websockets package, preserving the full upstream commit history via a subtree-style merge.

What you get

  • httpx2.websocket("ws://…"), Client().websocket(…), AsyncClient().websocket(…)
  • WebSocketSession / AsyncWebSocketSession, ASGIWebSocketTransport for testing WebSocket endpoints against ASGI apps, and the exception hierarchy (HTTPXWSException, WebSocketDisconnect, WebSocketUpgradeError, …), all exported from httpx2.websockets
import httpx2
from httpx2.websockets import WebSocketDisconnect

with httpx2.websocket("ws://localhost:8000/ws") as ws:
    ws.send_text("Hello!")
    message = ws.receive_text()

Design

  • wsproto is an optional extra httpx2[ws], matching the brotli/http2/socks pattern. WebSocket features raise a clear "install httpx2[ws]" error when it is missing.
  • httpx2.websockets is a public subpackage, so the types live there rather than being re-exported at the top level. Because users import it explicitly, httpx2/__init__.py never imports it, and import httpx2 pulls in neither the subpackage nor wsproto - no lazy __getattr__ machinery needed to keep the extra optional. The top-level namespace grows by exactly one line (the websocket export); everything inside the package is private (_api, _exceptions, _ping, _transport).
  • The .websocket() methods mirror the existing .sse() shape and lazily import inside the method body, so they cost nothing at import time.
  • WebSocket defaults live in _config.py alongside DEFAULT_TIMEOUT_CONFIG and friends.
  • httpcore2 stays lazily imported inside the methods that need it, preserving httpx2's existing lazy-loading.
  • The vendored code is adapted to httpx2's namespaces and typing standards (passes ruff and mypy --strict).

History

The vendor commit merges httpx-ws's complete history (98 commits) under the WebSocket package, so upstream authorship and provenance are preserved.

Tests

Vendored httpx-ws tests live under tests/httpx2/websockets/ (136 passing across asyncio and trio), including a test that import httpx2 does not import wsproto. Dev deps added: starlette, websockets, flaky. scripts/check is green and coverage stays at 100%.

AI Disclaimer

This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.