microdot.sse — Server-Sent Events

Server-Sent Events (SSE) is a simple one-way push protocol: the client opens a normal HTTP request to Content-Type: text/event-stream, and the server keeps the connection open and writes formatted events as they happen. The browser exposes the stream as a JavaScript EventSource object. Compared to WebSockets, SSE is simpler when only the server pushes; the client always requests.

The typical camera use is publishing detections / sensor readings / status updates to a phone or dashboard at the rate they happen, without the dashboard having to poll.

class SSE

class microdot.sse.SSE

The handle the route receives. Passed to handlers as the second argument when with_sse() is in use.

async send(data, event: str | None = None, event_id: str | None = None, retry: int | None = None, comment: bool = False)

Push a single event to the client.

data

The event payload. Strings and bytes are sent as-is. Dicts and lists are serialized to JSON. Anything else is str()-converted.

event

Optional event name – the browser’s EventSource dispatches to a JavaScript listener of the same name when this is set.

event_id

Optional event id – the browser sends it back as Last-Event-ID if the connection drops and reconnects, which lets the server resume from where it left off.

retry

Reconnection delay in seconds; the browser uses this if the connection drops.

comment

If True, the payload is sent as an SSE comment line (ignored by the browser). Useful as a keep-alive heartbeat to stop NAT timeouts from closing an otherwise-idle stream.

Module-level decorators

microdot.sse.with_sse(f)

Decorator that turns a route into an SSE endpoint. The handler receives the request and an SSE object:

from microdot import Microdot
from microdot.sse import with_sse

app = Microdot()

@app.get('/events')
@with_sse
async def events(request, sse):
    import asyncio
    while True:
        await sse.send({'temp': read_sensor()}, event='reading')
        await asyncio.sleep(1)

The handler’s lifetime equals the stream’s lifetime – it runs for as long as the client stays connected. Cancellation (the client disconnects, the server shuts down) propagates as asyncio.CancelledError, which the framework swallows.

microdot.sse.sse_response(request, event_function, *args, **kwargs)

Low-level entry point: returns the response tuple that with_sse() returns under the hood. Useful when an SSE endpoint needs custom headers or status codes that the decorator does not allow:

from microdot.sse import sse_response

@app.get('/events')
async def events(request):
    if not request.g.current_user:
        return 401
    async def emit(req, sse):
        await sse.send('hello')
    return sse_response(request, emit)

event_function takes (request, sse, *args, **kwargs) and uses await sse.send(...) to push events.

The SSE response sets Content-Type: text/event-stream and keeps the connection open until event_function returns or the client disconnects. Use a while True loop with an await asyncio.sleep between sends to avoid hot-looping.