Wirth-style minimal SSE web framework with topic-scoped pub/sub
Project description
py_sse
A small Python web framework for real-time hypermedia apps. HTTP/1.1, one OS thread per connection, brotli-compressed SSE, topic-scoped pub/sub. No async. About 1200 lines.
Pairs with html-tags and Datastar.
Install
pip install py-sse
Hello, live page
from py_sse import serve, live, Database, signals
from html_tags import h
db = Database("app.db", schema="CREATE TABLE IF NOT EXISTS msg (text TEXT)")
@live(topic="chat")
def home(req):
msgs = db.all("SELECT text FROM msg")
return [
h.h1("chat"),
h.ul(*[h.li(t) for (t,) in msgs]),
h.input({"data-bind:text": "",
"data-on:keydown": "evt.key === 'Enter' && "
"($text && (@post('/say'), $text=''))"}),
]
def say(req):
text = (signals(req).get("text") or "").strip()
if text:
db.execute("INSERT INTO msg (text) VALUES (?)", (text,))
db.changes.notify("chat")
return (200, [], b"")
ROUTES = [("GET", "/", home), ("POST", "/say", say)]
if __name__ == "__main__":
serve(ROUTES, port=8000, changes=db.changes, head=[
h.title("chat"),
h.script(type="module", src="https://cdn.jsdelivr.net/gh/"
"starfederation/datastar@v1.0.1/bundles/datastar.js"),
])
Open two browser tabs. Type in one, watch it appear in the other.
How it works
-
One URL per live page.
@live(topic="x")makes the handler serve three transport modes from the same route, dispatched byAccept:- Live SSE while viewers are under
soft_cap. - Polling between
soft_capandhard_cap(interval ramps exponentially). - Static above
hard_cap(page stops auto-updating). - Polling clients re-upgrade to live when capacity drops.
- Live SSE while viewers are under
-
CQRS. Reads come down the long-lived SSE stream. Writes are short-lived
@post()calls that mutate state and calldb.changes.notify("topic"). All open streams re-render. -
Topic patterns. Subjects are dotted:
"game.42.score". A waiter on"game.42.*"wakes on any ofgame.42.score,game.42.players, etc.@live(topic="game.{id}.*")expands{id}from URL parameters.changes.wait([p1, p2, ...])waits on multiple patterns at once. -
Fat morph. Every frame is the whole page. idiomorph diffs in place. Brotli's cross-frame state compresses repeated HTML at ~200:1.
-
Framework owns the envelope. Pass
head=[...]toserve()once. The<html><head><body>is byte-identical on every render, so idiomorph leaves it alone and scripts stay running across morphs.
API
serve(routes, *, host, port, changes, head, capacity, on_event,
before_hooks, ui_theme, max_connections, access_log)
live(handler, topic=..., hard_cap=...) # function or @live(topic="...")
Database(path, schema) # apsw passthrough + .changes
Changes() # topic-scoped pub/sub
.notify(subject) # publish
.wait(pattern, timeout=...) # pattern: str | iterable of str
Capacity(soft_cap, hard_cap, ...) # viewer capacity
html(body), redirect(loc), error(status, msg), no_content()
signals(req) # parse Datastar signals
set_cookie(req, name, value, **opts)
Handlers return html_tags Node, Safe, or an iterable of those.
Writes return a (status, headers, body) tuple.
on_event(event_dict) is an optional callback fired on
stream_open, stream_close, and page_render. No-op by default.
Status
0.8.0. Small, opinionated, not yet hardened for hostile traffic. Pair with a reverse proxy for TLS. Compression and SSE buffering are already correct on the application side.
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py_sse-0.10.0a2.tar.gz.
File metadata
- Download URL: py_sse-0.10.0a2.tar.gz
- Upload date:
- Size: 12.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux ARM","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8cad233347c792962a6e4e77adb48e2c03be3f7ada5182a4409e7635d2a1783f
|
|
| MD5 |
eaaa82510a50a9ddf053467ee718c3a4
|
|
| BLAKE2b-256 |
846d72f3d3f8ccc76cd15ac4a3eed5dc07df82d1c1ec51ddbc2adc0d8b4f89b5
|
File details
Details for the file py_sse-0.10.0a2-py3-none-any.whl.
File metadata
- Download URL: py_sse-0.10.0a2-py3-none-any.whl
- Upload date:
- Size: 15.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux ARM","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38c0b938f37ce4a7e9584ec47f2f5be295b32919dc0781c9682e6bc28084627d
|
|
| MD5 |
7dba76060188c18dae7497fb117e9e8a
|
|
| BLAKE2b-256 |
d2bf58d7573726f43896973ac6d16a62aef3cc40df720000930d917042561743
|