rpckit
Define your realtime API once in Python. Get the server, the contract, and typed clients for Python and TypeScript — none of which can drift apart.
Agents, browser automation, live dashboards and voice need more than request/response over HTTP: server-pushed events, binary streams, one long-lived connection. So the JSON-RPC envelope gets hand-written, the dispatch table grows by hand, and the frontend client is maintained separately — until the two disagree in production.
rpckit makes the Python definition the single source of truth:
| You write | rpckit gives you |
|---|---|
| an async function on a channel | validated dispatch, injection, concurrency, shutdown |
| a payload model | an OpenRPC contract as a build-time artifact |
| an error class | typed exceptions in every generated client |
| an async iterator | server-pushed events and binary streams |
| nothing else | Python and TypeScript clients, regenerated in CI |
The core has no HTTP or WebSocket dependency — a FastAPI adapter ships with it, and any transport you already have can serve a rpckit service.
The idea
You define each operation once, on the server:
@tasks.method()
async def create(params: CreateTask, store: Inject[TaskStore]) -> Task:
return await store.create(params.title)
@tasks.event()
async def updated(store: Inject[TaskStore]) -> AsyncIterator[TaskUpdated]:
async for task in store.watch():
yield TaskUpdated(task=task)
One command turns that into an OpenRPC document and clients in both languages:
rpckit generate --config rpcgen.toml
And your frontend gets the whole API fully typed — no schema written twice, no client kept in sync by hand, no stringly-typed method names:
const task = await client.tasks.create({ title: "Ship 0.6" }); // Task
for await (const update of client.tasks.updated()) { // TaskUpdated
render(update.task);
}
Run --check in CI and a definition that outgrew its clients fails the build
instead of shipping.
Install
uv add pyrpckit
uv add "pyrpckit[fastapi]" # FastAPI adapter
uv add "pyrpckit[codegen]" # client generation
uv add "pyrpckit[otel]" # OpenTelemetry integration
Python 3.12 or newer. Pydantic is the only required dependency.
Quickstart
Channels group related operations and provide their namespace; a service mounts them on a socket. Their dispatch logic can also be called directly:
from rpckit import Inject, RpcChannel, RpcModel, RpcService, RpcSuccess
class CreateTask(RpcModel):
title: str
class Task(RpcModel):
id: int
title: str
class TaskStore:
def __init__(self) -> None:
self._tasks: list[Task] = []
async def create(self, title: str) -> Task:
task = Task(id=len(self._tasks) + 1, title=title)
self._tasks.append(task)
return task
tasks = RpcChannel("tasks")
@tasks.method()
async def create(params: CreateTask, store: Inject[TaskStore]) -> Task:
"""Create a task."""
return await store.create(params.title)
app = RpcService(version=1)
app.socket("/rpc", channels=(tasks,))
async def test_create() -> None:
server = tasks.create_server(context=TaskStore())
response = await server.handle(
{
"jsonrpc": "2.0",
"id": 1,
"method": "tasks.create",
"params": {"title": "Ship 0.6"},
}
)
assert isinstance(response, RpcSuccess)
assert response.result == Task(id=1, title="Ship 0.6")
tasks.create is the wire name, the docstring becomes the contract summary,
and Inject[TaskStore] is resolved on the server — it never appears in the
public schema.
Operations on a channel are implemented by the server unless you say otherwise:
@tasks.method(), @tasks.event(), @tasks.subscription(), and
@tasks.stream() cover the usual client-to-server API. Only when the server
also calls into the client do you name both sides, tasks.server and
tasks.client — see Client methods.
Documentation
Read the hardening stack and SDK migration guide for all 15 ticket PRs, changed defaults, and integration steps.
- Services and channels — methods, namespaces, parameter styles, sockets, protocol versions
- Dependency injection —
Inject[T], resolvers, scopes, Dishka - Connections and events — the
connecthook, rejecting handshakes, server-pushed events, limits - Typed errors — stable codes, typed details, generated exception classes
- Binary streams — upload, download, and bidirectional byte streams beside JSON-RPC
- Raw sockets — proxy native protocols such as CDP with generated socket clients
- Contract and clients —
rpcgen.toml, the CLI, the shape of generated clients - Transports — FastAPI, custom sockets, testing
Examples
examples/ holds standalone runnable scripts, and
examples/generated_clients contains real
generated Python and TypeScript output you can read before installing
anything.
Development
uv sync --all-groups
uv run ruff check .
uv run ruff format .
uv run pytest
Metadata
Release files for pyrpckit 0.11.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 |
|---|---|---|---|---|
| pyrpckit-0.11.0.tar.gz | 478.2 kB | Without provenance | Details for pyrpckit-0.11.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyrpckit-0.11.0-py3-none-any.whl | Python 3 | none | any | Details for pyrpckit-0.11.0-py3-none-any.whl |
Total release size: 664.1 kB
Release files / pyrpckit-0.11.0.tar.gz
| Download URL | pyrpckit-0.11.0.tar.gz |
|---|---|
| Size | 478.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9919f05c58c3748f092bfa480e1958baf42cd226d86f845762e0fa2a296a8d98
|
|
BLAKE2b-256 checksum How to use checksums |
c6d33abd2727b2c207989a18d8af6f09652fd73a6c118e0cd9b199bc285e8cfd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.2
|
Release files / pyrpckit-0.11.0-py3-none-any.whl
| Download URL | pyrpckit-0.11.0-py3-none-any.whl |
|---|---|
| Size | 185.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5406400e2e813f6f0075107861a2a60983eba258b4944bc8c88c3a7b51d28f29
|
|
BLAKE2b-256 checksum How to use checksums |
fd51f20543f976fe3d3a237fc0cbdd9b437ebf08fdf902eaa459d01bf0ec2886
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.2
|
Security for pyrpckit 0.11.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.