How to connect live clients¶
This guide shows you how to serve a Session or Hub over the connection adapters transports ships
today: WebSocket, Server-Sent Events, Jupyter comm, and anywidget custom messages.
Serve a session over WebSocket¶
Install the WebSocket dependencies:
pip install "transports[connections]" uvicorn
Create a Starlette app:
import asyncio
import transports
from pydantic import BaseModel
from starlette.applications import Starlette
from starlette.routing import WebSocketRoute
class Counter(BaseModel):
tick: int = 0
session = transports.Session()
counter = Counter()
session.host(counter)
server = transports.Server(session)
async def ticker():
while True:
await asyncio.sleep(1)
counter.tick += 1
async def startup():
asyncio.create_task(transports.autosync(server))
asyncio.create_task(ticker())
app = Starlette(
routes=[WebSocketRoute("/ws", transports.ws_endpoint(server))],
on_startup=[startup],
)
Run it:
uvicorn app:app --reload
Run one autosync task per Server or Hub. It drains host-side mutations and broadcasts the
resulting patches to all open connections.
Mirror the server in a browser¶
Initialize the wasm package, connect, and render whenever a message arrives.
import init, { Client, fromValue } from "1kbgz/transports";
await init();
const client = new Client();
const ws = client.connect("ws://127.0.0.1:8000/ws");
ws.addEventListener("message", () => {
const [id] = client.ids();
if (id === undefined) return;
render(fromValue(client.value(id)));
});
To send an edit, create the next core Value and send the encoded proposal frame:
import { toValue } from "1kbgz/transports";
const [id] = client.ids();
ws.send(client.edit(id, toValue({ tick: 10 })));
The local mirror updates when the server echoes the authoritative patch.
Mirror the server in Python¶
Client.connect() runs a receive loop until the WebSocket closes.
client = transports.Client()
await client.connect("ws://127.0.0.1:8000/ws")
For Python clients that also send edits, manage the WebSocket loop directly and send the frame
returned by client.edit(id, value).
Use MessagePack on a connection¶
Pass codec="msgpack" on the client. The client appends ?codec=msgpack; the server sends binary
frames to that connection and can still serve JSON clients at the same time.
client = transports.Client(codec="msgpack")
await client.connect("ws://127.0.0.1:8000/ws")
const client = new Client("msgpack");
const ws = client.connect("ws://127.0.0.1:8000/ws");
Stream receive-only updates over SSE¶
Use SSE for dashboards and other receive-only clients.
pip install "transports[sse]"
import asyncio
from starlette.applications import Starlette
from starlette.routing import Route
async def startup():
asyncio.create_task(transports.autosync(server))
app = Starlette(
routes=[Route("/sse", transports.sse_endpoint(server))],
on_startup=[startup],
)
Python client:
client = transports.Client()
await client.connect_sse("http://127.0.0.1:8000/sse")
Browser client:
const client = new Client();
const events = client.connectSSE("http://127.0.0.1:8000/sse");
SSE is JSON/text and server-to-client only. Use WebSocket when clients need to send edits.
Use a Jupyter comm¶
Install the comm dependency:
pip install "transports[jupyter]"
Wire a kernel comm to a Server or Hub:
from comm import create_comm
comm = create_comm(target_name="transports")
transports.serve_comm(server, comm)
# after mutating hosted models
transports.sync(server)
The comm carries JSON wire strings in data, so serve_comm rejects non-JSON codecs.
Use anywidget custom messages¶
serve_anywidget uses an anywidget-style send / on_msg object. The frontend sends
{"ready": true} before snapshots are delivered.
conn = transports.serve_anywidget(server, widget)
# after mutating hosted models
transports.sync(server)
Frontend messages use the same client protocol:
const client = new Client();
model.on("msg:custom", (content) => {
if (content.wire) client.recv(content.wire);
});
model.send({ ready: true });
Use model.send({ wire: client.edit(id, value) }) to send an edit from the frontend.
Serve a Hub¶
A Hub satisfies the same connection contract as Server, so the same adapters serve it:
transports.ws_endpoint(hub) for WebSocket, transports.sse_endpoint(hub) for SSE, and the same
serve_comm / serve_anywidget helpers for Jupyter (with autosync(hub) or sync(hub)).
hub = transports.Hub(key=lambda ws: ws.path_params["tenant"])
app = Starlette(routes=[WebSocketRoute("/ws/{tenant}", transports.ws_endpoint(hub))])