title: Move chattolib transport to the connectrpc Python package status: completed type: task priority: high created_at: 2026-07-09T10:03:55Z
Replace hand-rolled Connect-JSON-over-httpx with the official connectrpc Python library (chatto-bot uses this too). Generate connect-python service stubs for every Chatto service from the vendored .proto files, rewrite ChattoClient so its methods delegate to those stubs, drop _transport.py, and keep the Pythonic top-level API (client.post_message(...)) intact.
Migrated the chattolib request/response transport from hand-rolled Connect-JSON-over-httpx to the official connectrpc Python package plus generated ConnectRPC service stubs (same transport chatto-bot uses).
scripts/generate_pb.sh to fetch every Chatto proto (api/v1, admin/v1, auth/v1, discovery/v1, realtime/v1) and to invoke protoc-gen-connect-python alongside the standard --python_out. Now produces 39 *_pb2.py message modules and 25 *_connect.py service stub modules under src/chattolib/_pb/._transport.py rewritten: exposes build_service_clients(base_url) (returns a typed ServiceClients bundle with one client per Chatto service, all sharing the google.protobuf binary codec) plus translate_connect_error (ConnectError → ChattoAuthError/ChattoConnectError) and pb_to_dict (bridges protobuf responses to the dataclass .parse() methods).ChattoClient fully rewritten: each method builds a protobuf request, calls the appropriate stub via a common _rpc helper that adds auth headers and translates errors, then normalises the response into the existing dataclass types. Added a public .services property so callers can reach the raw ConnectRPC clients directly.client.call(service, method, request) string-based escape hatch (replaced by .services).pyproject.toml: added connectrpc>=0.11 and protobuf>=5.28 as hard runtime deps; httpx stays for the /auth/login endpoint; websockets still under [realtime].unittest.mock.AsyncMock — 48 tests, all passing, no HTTP mocking.ruff + mypy --strict clean.Verified live against Chatto HQ (0.4.2) with the RoboChatto account: login → me() → list_rooms() → get_motd() → list_roles() → has_notifications() all succeed end-to-end. Realtime WebSocket unchanged and still working.
CLAUDE.md architecture section refreshed to describe the new transport and _pb layout.