wire_rpc
Typed asyncio JSON-RPC 2.0 with JSON/Pydantic/MessagePack codecs and TCP, HTTP, WebSocket, stdio, and in-process thread transports.
Quick start
The user guide covers handlers, routers, middleware, the client, transports, authentication, monitoring and testing.
A server registers handlers on an App. The type hint on params sets how
incoming params are validated, and the return hint sets how the result is encoded.
# server.py
import msgspec
from wire_rpc import App
from wire_rpc.transports.tcp import TcpServerTransport
class AddParams(msgspec.Struct):
a: int
b: int
app = App(TcpServerTransport(host="127.0.0.1", port=9000))
@app.method("add")
async def add(params: AddParams, ctx) -> int:
return params.a + params.b
if __name__ == "__main__":
app.run()
A client calls a method by name and names the type it expects back:
# client.py
import asyncio
from wire_rpc import Client
from wire_rpc.transports.tcp import TcpClientTransport
async def main():
async with Client(TcpClientTransport(host="127.0.0.1", port=9000)) as client:
print(await client.call("add", int, {"a": 2, "b": 3})) # 5
asyncio.run(main())
A server error reaches the client as WireRpcError, which carries the JSON-RPC
code and message. Handler exception details are never sent to the client.
Batches (a JSON array of requests, as in section 6 of the JSON-RPC 2.0 spec)
work on every transport. A batch shares one handler_timeout, holds at most
max_batch_size calls (100 by default), and its reply always fits
max_response_size.
Handlers take (ctx) or (params, ctx). ctx is whatever the
@app.on_startup hook returned, such as a database pool. Trusted request
metadata, including the authenticated principal, comes from current_request().
Group related methods with Router("prefix"), and wrap them with
@app.middleware or @router.middleware.
Transports
App serves one peer, and requests run one at a time. MulticastApp serves many
peers, each identified by a client_id, and can broadcast notifications to all of them.
| Transport | Use with | Notes |
|---|---|---|
TcpServerTransport / TcpClientTransport |
App / Client |
Length-prefixed frames, optional TLS. The server serves one connection; the app stops when it closes. |
TcpMulticastServerTransport |
MulticastApp |
Many TCP clients, bounded per-client queues. |
HttpServerTransport / HttpClientTransport |
App / Client |
One POST /rpc per call. Text codecs only. |
WsServerTransport / WsClientTransport |
App / Client |
One WebSocket connection on /ws. Text codecs only. |
MulticastWsServerTransport |
MulticastApp |
Many WebSocket clients, for browsers. |
StdIoTransport / StdIoServerTransport |
Client / App |
Talk to a child process over its stdin/stdout. |
ThreadTransport |
App + Client |
Two threads in one process, each with its own event loop. |
Every transport enforces a maximum message size. Authentication is set per transport with
auth=; see the authentication suite.
Between threads
ThreadTransport.pair() returns two connected ends. Give one to each thread;
each end binds to the event loop of the thread that connects it. Sending copies
the message, and an end that is full (max_queue_size unread messages) rejects
new sends instead of growing without limit. Either thread can start first: a
send waits up to connect_timeout (5 seconds by default) for the other end to
connect.
import asyncio
import threading
from wire_rpc import App, Client
from wire_rpc.transports import ThreadTransport
client_end, server_end = ThreadTransport.pair()
app = App(server_end)
@app.method("add")
async def add(params: dict, ctx) -> int:
return params["a"] + params["b"]
threading.Thread(target=app.run, daemon=True).start()
async def main():
async with Client(client_end) as client:
print(await client.call("add", int, {"a": 2, "b": 3})) # 5
asyncio.run(main())
Development and release checks
Requires Python 3.12 or newer, uv, and a running Docker daemon for the full suite.
Testcontainers starts isolated PostgreSQL 17 and Redis 7 services automatically.
Missing Docker or a failed container startup fails the tests; no backend is silently skipped.
To explicitly run only tests without Docker: uv run --locked pytest -q -W error -m "not integration".
uv sync --locked
uv run --locked pyrefly check
uv run --locked ruff check
uv run --locked pytest -q -W error
uv build --no-sources
uv run --locked python scripts/check_wheel.py
Pyrefly checks the package, tests, and release scripts against a Python 3.12
baseline, configured in [tool.pyrefly], and runs in CI.
Ruff enforces pyflakes rules and import order, configured in [tool.ruff];
uv run ruff check --fix sorts imports.
Tests are organized by attack surface. Concurrency tests use events and controlled ordering; socket tests use ephemeral ports. Test timeouts are deadlock guards. The CI matrix covers Python 3.12–3.14 on Linux, macOS, and Windows.
Releases follow semantic versioning; see the changelog for the policy and for breaking changes. Report vulnerabilities privately as described in SECURITY.md.
Read production contracts before deploying, including codec compatibility, identity propagation, overload behavior, cancellation semantics, resource budgets, and platform limitations.
Benchmarks give rough per-transport latency and throughput, and how to reproduce them.
The execution plan records the follow-up hardening work. The initial review records the first transport/codec regression pass.
The authentication suite documents authenticators,
credential validators, session backends, client credentials, TLS policy, and
adversarial backend tests. Optional extras are password, jwt, redis, and
postgres; core imports do not require them.
Metadata
Release files for wire-rpc 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| wire_rpc-0.2.0.tar.gz | 257.4 kB | Without provenance | Details for wire_rpc-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| wire_rpc-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for wire_rpc-0.2.0-py3-none-any.whl |
Total release size: 348.7 kB
Release files / wire_rpc-0.2.0.tar.gz
| Download URL | wire_rpc-0.2.0.tar.gz |
|---|---|
| Size | 257.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
413538c2996024d22d94855a5d436a3cb369cf71bc94f47c20c6133fe1478a8b
|
|
BLAKE2b-256 checksum How to use checksums |
cdb2bb0e98ff94a2bc5453fedf9621d09a2fac770a1e4993e41678b771144f77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.3
|
Release files / wire_rpc-0.2.0-py3-none-any.whl
| Download URL | wire_rpc-0.2.0-py3-none-any.whl |
|---|---|
| Size | 91.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
738c8ef4fce267d12ed54a665dfab25dbbf906e59b7882eb61a55950b4d73127
|
|
BLAKE2b-256 checksum How to use checksums |
859f55b0b7c8886856489962bf0bb86bf29f222ae6a83f16831998b493e22f09
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.3
|
Security for wire-rpc 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.