Skip to main content

oppionated python sse server wrapping granian

Project description

PyPI version

py-sse

minimal python sse server for datastar — built on rsgi/granian + apsw/sqlite

sse.py      →  datastar event formatters (patch_elements, patch_signals, …)
app.py      →  rsgi app: routing, signals, cookies, static, @app.stream
db.py       →  sqlite helpers + Changes (update_hook → asyncio)
mserver.py  →  background server for notebooks (serve_background/stop_background)
ngrok.py    →  public tunnel + .env loader
pip install py-sse
uv add py-sse      # preferred

hello

from py_sse import create_app, serve

app = create_app()

@app.get('/')
async def index(req): return '<h1>hello</h1>'

if __name__ == '__main__': serve(app)

live feed

Backend is the source of truth. Each write wakes every /feed stream; the handler re-renders. No polling, no optimistic updates, no client state.

from py_sse import *

db = create_db('chat.db')
migrate(db, "CREATE TABLE IF NOT EXISTS msgs (id INTEGER PRIMARY KEY, txt TEXT)")

def startup(loop):
    global changes
    changes = Changes(db, loop)

app = create_app(on_init=startup, on_del=lambda loop: changes.close())

@app.post('/say')
async def say(req):
    s = await signals(req)
    write(db, lambda c: c.execute("INSERT INTO msgs(txt) VALUES(?)", (s['text'],)))

@app.stream('/feed', on=lambda: changes)
def feed(req):
    rows = query(db, "SELECT txt FROM msgs ORDER BY id DESC LIMIT 50")
    return ''.join(f'<p>{t}</p>' for (t,) in rows)

handler returns

str          →  200 html
dict         →  200 json
None         →  204
(url, int)   →  redirect (3xx) or text body (4xx/5xx)
async gen    →  sse stream  (use @app.stream for the common case)

api

create_app(on_init=, on_del=)   →  app handle: .get .post .put .patch .delete .mount .before .stream
@app.stream(path, on=)          →  sse route; re-renders each time `on` ticks
signals(req)                    →  datastar signals (query on GET, json body otherwise)
body(req) / body_stream(req)    →  full body / chunked body
set_cookie(req, name, value)    →  queue a Set-Cookie
create_signer(secret)           →  .sign(value) / .unsign(signed) — hmac-sha256
static(app, prefix, dir)        →  zero-copy file serving, http range support

create_db(path)                 →  wal sqlite connection
migrate(conn, sql)              →  apply schema in one txn
query(conn, sql, …, limit=)     →  rows as tuples
write(conn, fn, …)              →  run fn(conn, …) in a txn, return its result
Changes(db, loop)               →  fan db writes out to streams; .wait() / .close()

patch_elements(html, …)         →  datastar-patch-elements event
patch_signals(signals, …)       →  datastar-patch-signals event
remove_signals(*names)          →  null those signals
execute_script(js, …)           →  datastar-execute-script event
redirect(url)                   →  navigate via patched script

serve(app)                      →  foreground granian server (blocks)
serve_background(app)           →  background thread → ServerState
stop_background(state)          →  clean shutdown, 3s join
dev_alive(port)                 →  is the port accepting connections
start_tunnel(port)              →  ngrok tunnel → TunnelState  (needs NGROK_AUTHTOKEN)
stop_tunnel(tunnel)             →  close it
load_env(path='.env')           →  read KEY=VALUE; missing file is a noop

datastar

patch elements & signals     →  the verb;  morph is the mechanism
fat morph                    →  send large dom chunks, up to <html>
signals                      →  user interaction only, used sparingly
cqrs                         →  long-lived read stream + short-lived writes
brotli on sse                →  ratios up to 200:1

MIT · built from marimo notebooks with marimo-dev

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_sse-0.12.2.tar.gz (13.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_sse-0.12.2-py3-none-any.whl (15.4 kB view details)

Uploaded Python 3

File details

Details for the file py_sse-0.12.2.tar.gz.

File metadata

  • Download URL: py_sse-0.12.2.tar.gz
  • Upload date:
  • Size: 13.0 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

Hashes for py_sse-0.12.2.tar.gz
Algorithm Hash digest
SHA256 e76418326ae242c50800c55cfa1f7b854e30911165f125d0bae3c7dcf62de78a
MD5 8206cf70079ac9e3ed0ac6d9e8e44699
BLAKE2b-256 768401fc121d3296810486ba13710bbf273d95264f53ed113638f8932a71b504

See more details on using hashes here.

File details

Details for the file py_sse-0.12.2-py3-none-any.whl.

File metadata

  • Download URL: py_sse-0.12.2-py3-none-any.whl
  • Upload date:
  • Size: 15.4 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

Hashes for py_sse-0.12.2-py3-none-any.whl
Algorithm Hash digest
SHA256 bd4495f865b692ea96c09fa4e213370534b850e8f1aa1b22b93663ce5cbb141a
MD5 a3b81a02677e5bfcbdc7f5f7b2dd4660
BLAKE2b-256 4717eae8e79f2a1a94dfc2696f7f93b5ca873e8f286fcbf1e85f754ae0c679e4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page