COOKBOOK / 20 RECIPES
Copy. Paste. Run.
Twenty recipes covering the whole framework. Every snippet is self-contained and
self-asserting — tests/test_cookbook.py in the repo executes all of them on
every push, so this page cannot drift. Canonical source:
docs/COOKBOOK.md.
RECIPE 01
Hello + typed path params + query defaults
Dicts become JSON. {uid:int} coerces or 404s; query params fill from defaults when absent.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import Ikarem
from ikarem.testing import TestClient
app = Ikarem(enable_docs=False)
@app.get("/")
async def home(req):
return {"hello": "ikarem"}
@app.get("/users/{uid:int}")
async def get_user(req, uid: int, limit: int = 5):
return {"uid": uid, "limit": limit}
c = TestClient(app)
assert c.get("/").json() == {"hello": "ikarem"}
assert c.get("/users/3", query="limit=2").json() == {"uid": 3, "limit": 2}
assert c.get("/users/abc").status_code == 404
RECIPE 02
JSON validation with constraints
Schema coerces types; Field() adds bounds; extra="forbid" turns unknown keys into 400s instead of silently ignoring them.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import Field, Ikarem, Schema
from ikarem.testing import TestClient
class Item(Schema, extra="forbid"):
name: str = Field(..., min_length=1, max_length=80)
qty: int = Field(1, ge=1, le=99)
app = Ikarem(enable_docs=False)
@app.post("/items")
async def create(item: Item):
return {"name": item.name, "qty": item.qty}
c = TestClient(app)
assert c.post("/items", body={"name": "apple", "qty": "2"}).json() == {"name": "apple", "qty": 2}
assert c.post("/items", body={"qty": 1}).status_code == 400
assert c.post("/items", body={"name": "x", "hack": 1}).status_code == 400
assert c.post("/items", body={"name": "x", "qty": 500}).status_code == 400
RECIPE 03
HTML forms + file uploads
await req.form() handles urlencoded and multipart. Files arrive as UploadFile (filename, content_type, size); bodies over the cap 413.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import Ikarem
from ikarem.http import UploadFile
from ikarem.testing import TestClient
app = Ikarem(enable_docs=False)
@app.post("/note")
async def note(req):
form = await req.form()
f = form.get("photo")
if isinstance(f, UploadFile):
return {"title": form.get("title"), "filename": f.filename, "size": f.size}
return {"title": form.get("title"), "filename": None}
def _multipart(fields, files, boundary="BND"):
parts = []
for k, v in fields.items():
parts.append(f'--{boundary}\r\nContent-Disposition: form-data; name="{k}"\r\n\r\n{v}\r\n')
for k, (fn, body, ct) in files.items():
parts.append(
f'--{boundary}\r\nContent-Disposition: form-data; name="{k}"; filename="{fn}"\r\n'
f"Content-Type: {ct}\r\n\r\n{body}\r\n"
)
parts.append(f"--{boundary}--\r\n")
return "".join(parts).encode(), f"multipart/form-data; boundary={boundary}"
c = TestClient(app)
r = c.post("/note", body="title=hello", content_type="application/x-www-form-urlencoded")
assert r.json() == {"title": "hello", "filename": None}
body, ct = _multipart({"title": "pic"}, {"photo": ("a.png", "BYTES", "image/png")})
r = c.post("/note", body=body, content_type=ct)
assert r.json() == {"title": "pic", "filename": "a.png", "size": 5}
RECIPE 04
Sessions: login, me, logout
Signed-cookie sessions. req.session is a dict; mutating it re-signs the cookie. Secret comes from session_secret= (refused at startup when auth routes exist and the secret is still the default).
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import Ikarem, SessionMiddleware, Unauthorized
from ikarem.testing import TestClient
app = Ikarem(enable_docs=False, session_secret="cookbook-session-secret")
app.use(SessionMiddleware())
@app.post("/login")
async def login(req):
form = await req.form()
if form.get("user") == "amy" and form.get("pw") == "s3cret":
req.session["uid"] = "u1"
return {"ok": True}
raise Unauthorized("bad credentials")
@app.get("/me")
async def me(req):
uid = req.session.get("uid")
if not uid:
raise Unauthorized("anonymous")
return {"uid": uid}
@app.post("/logout")
async def logout(req):
req.session.clear()
return {"ok": True}
c = TestClient(app)
assert c.get("/me").status_code == 401
assert c.post("/login", body="user=amy&pw=s3cret", content_type="application/x-www-form-urlencoded").json() == {"ok": True}
assert c.get("/me").json() == {"uid": "u1"}
assert c.post("/logout").json() == {"ok": True}
assert c.get("/me").status_code == 401
RECIPE 05
JWT Bearer + roles
Stdlib HS256, no deps. require_roles() reads the roles claim from the app's auth_secret; 401 without a token, 403 with the wrong role.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import Depends, Ikarem, create_token, require_roles
from ikarem.testing import TestClient
SECRET = "cookbook-auth-secret"
app = Ikarem(enable_docs=False, auth_secret=SECRET)
@app.get("/admin")
async def admin(req, claims=Depends(require_roles("admin"))):
return {"sub": claims["sub"]}
c = TestClient(app)
assert c.get("/admin").status_code == 401
user = create_token("u2", SECRET, roles=["user"])
assert c.get("/admin", headers={"authorization": f"Bearer {user}"}).status_code == 403
root = create_token("u1", SECRET, roles=["admin"])
assert c.get("/admin", headers={"authorization": f"Bearer {root}"}).json() == {"sub": "u1"}
RECIPE 06
API keys + scopes
Service-to-service keys via APIKeyAuth (static dict or async lookup=), user scopes via require_scopes() (scope/scp JWT claims).
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import APIKeyAuth, Depends, Ikarem, create_token, require_scopes
from ikarem.testing import TestClient
SECRET = "cookbook-scope-secret"
app = Ikarem(enable_docs=False, auth_secret=SECRET)
keys = APIKeyAuth({"svc-key": {"name": "billing"}})
@app.get("/internal")
async def internal(req, info=Depends(keys)):
return {"svc": info["name"]}
@app.get("/files")
async def files(req, claims=Depends(require_scopes("read"))):
return {"n": 2}
c = TestClient(app)
assert c.get("/internal").status_code == 401
assert c.get("/internal", headers={"x-api-key": "svc-key"}).json() == {"svc": "billing"}
tok = create_token("u1", SECRET, scope="read write")
assert c.get("/files", headers={"authorization": f"Bearer {tok}"}).json() == {"n": 2}
narrow = create_token("u2", SECRET, scope="write")
assert c.get("/files", headers={"authorization": f"Bearer {narrow}"}).status_code == 403
RECIPE 07
Pagination
Slice + envelope. page/per_page from query with sane clamps; always return total so clients can render page counts.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import Ikarem
from ikarem.testing import TestClient
ROWS = [{"id": i} for i in range(1, 6)]
app = Ikarem(enable_docs=False)
@app.get("/rows")
async def rows(req, page: int = 1, per_page: int = 2):
page = max(1, page)
per_page = min(50, max(1, per_page))
start = (page - 1) * per_page
return {"total": len(ROWS), "page": page, "items": ROWS[start : start + per_page]}
c = TestClient(app)
p1 = c.get("/rows", query="page=1&per_page=2").json()
assert (p1["total"], p1["page"], len(p1["items"])) == (5, 1, 2)
p3 = c.get("/rows", query="page=3&per_page=2").json()
assert [r["id"] for r in p3["items"]] == [5]
RECIPE 08
CRUD resource in one call
app.resource() generates validated, paginated JSON CRUD from a Schema + table. No owner scoping here (see owner_field= in ikarem/resources.py for the session-scoped variant).
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import Ikarem, Schema
from ikarem.db import DatabasePlugin
from ikarem.testing import TestClient
class NoteIn(Schema):
text: str
app = Ikarem(enable_docs=False)
app.register(DatabasePlugin("sqlite:///:memory:"))
@app.on_startup
async def init():
await app.state_db.execute("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT)")
app.resource("/notes", NoteIn, table="notes")
c = TestClient(app)
assert c.post("/notes", body={"text": "buy milk"}).status_code == 201
r = c.post("/notes", body={"text": "second"})
assert r.status_code == 201
lst = c.get("/notes").json()
assert lst["total"] >= 2 and lst["items"]
rid = r.json()["id"]
assert c.get(f"/notes/{rid}").json()["text"] == "second"
assert c.put(f"/notes/{rid}", body={"text": "edited"}).json()["text"] == "edited"
assert c.delete(f"/notes/{rid}").json() == {"ok": True}
assert c.get(f"/notes/{rid}").status_code == 404
RECIPE 09
Background tasks (fire-and-forget)
Return the response now, run the side effect after send. Gone on restart with no retry — when the job must survive deploys, use recipe 10 instead.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import BackgroundTasks, Ikarem
from ikarem.testing import TestClient
sent = []
app = Ikarem(enable_docs=False)
@app.post("/welcome")
async def welcome(req, bg: BackgroundTasks):
body = await req.json()
bg.add(sent.append, body["email"])
return {"queued": True}
c = TestClient(app)
assert c.post("/welcome", body={"email": "amy@x.com"}).json() == {"queued": True}
assert sent == ["amy@x.com"]
RECIPE 10
Durable queue (survives restarts)
Portable leases, exponential-backoff retries, parked dead jobs. Workers drain via ikarem worker myapp:app; here run_one() drives a single job.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
import asyncio
from ikarem import Ikarem
from ikarem.db import DatabasePlugin
from ikarem.queue import QueuePlugin, task
seen = []
@task("cookbook-greet")
async def greet(name="world"):
seen.append(name)
app = Ikarem(enable_docs=False)
app.register(DatabasePlugin("sqlite:///:memory:"))
app.register(QueuePlugin())
async def main():
await app.startup()
try:
await app.state_queue.enqueue("cookbook-greet", {"name": "ada"})
assert await app.state_queue.depth() >= 1
assert await app.state_queue.run_one() is True
assert seen == ["ada"]
assert await app.state_queue.run_one() is False
finally:
await app.shutdown()
asyncio.run(main())
RECIPE 11
Cron + intervals (testable clock)
Jobs register with decorators; all timing flows through tick(now), so tests drive time instead of sleeping. Tie to lifespan with app.register(SchedulerPlugin()) in production.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
import asyncio
import time
from ikarem import Ikarem
app = Ikarem(enable_docs=False)
fired = []
@app.every(60)
async def heartbeat():
fired.append(1)
@app.cron("@daily")
async def daily():
fired.append("daily")
async def main():
sched = app._scheduler()
assert len(sched.jobs) == 2
await sched.tick(now=time.time() + 61)
assert fired == [1]
asyncio.run(main())
RECIPE 12
Middleware: timing + auth gate
(request, call_next) -> response. Return without calling call_next to short-circuit. Function middleware and classes both work.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import Ikarem, Unauthorized
from ikarem.testing import TestClient
app = Ikarem(enable_docs=False)
async def timing(req, call_next):
resp = await call_next(req)
resp.headers["x-took"] = "fast"
return resp
async def gate(req, call_next):
if req.headers.get("x-flag") != "yes":
raise Unauthorized("flag required")
return await call_next(req)
app.use(timing)
app.use(gate)
@app.get("/gated")
async def gated(req):
return {"ok": True}
c = TestClient(app)
assert c.get("/gated").status_code == 401
r = c.get("/gated", headers={"x-flag": "yes"})
assert r.json() == {"ok": True} and r.headers["x-took"] == "fast"
RECIPE 13
Errors: abort + custom handlers
abort(status, detail) fails tersely through the normal pipeline (so middleware headers still apply). @app.exception_handler maps exception types, most-specific match first.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import Ikarem, abort
from ikarem.testing import TestClient
app = Ikarem(enable_docs=False)
@app.exception_handler(ValueError)
async def _bad(req, exc):
from ikarem import JSONResponse
return JSONResponse({"detail": f"bad value: {exc}"}, status_code=422)
@app.get("/widget/{wid:int}")
async def widget(req, wid: int):
if wid == 0:
abort(404, "no such widget")
return {"wid": wid}
@app.get("/boom")
async def boom(req):
raise ValueError("wid")
c = TestClient(app)
assert c.get("/widget/0").status_code == 404
assert c.get("/boom").status_code == 422
assert c.get("/widget/7").json() == {"wid": 7}
RECIPE 14
CORS + security headers + trusted hosts
Preflights answer 204 without touching handlers; security headers apply to every response including errors; unlisted Host values 400 immediately.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import CORSMiddleware, Ikarem, SecurityHeadersMiddleware, TrustedHostMiddleware
from ikarem.testing import TestClient
app = Ikarem(enable_docs=False)
app.use(TrustedHostMiddleware(["example.com"]))
app.use(SecurityHeadersMiddleware())
app.use(CORSMiddleware(allow_origins=["https://app.example.com"]))
@app.get("/")
async def home(req):
return {"ok": True}
c = TestClient(app)
assert c.get("/", headers={"host": "evil.com"}).status_code == 400
r = c.get("/", headers={"host": "example.com"})
assert r.headers["x-frame-options"] == "DENY"
assert r.headers["access-control-allow-origin"] == "https://app.example.com"
pre = c.request("OPTIONS", "/", headers={"host": "example.com"})
assert pre.status_code == 204
RECIPE 15
Rate limiting
Fixed-window, per-IP, bounded buckets. 429s carry Retry-After plus X-RateLimit-* headers; successful responses carry the quota headers too.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import Ikarem, RateLimitMiddleware
from ikarem.testing import TestClient
app = Ikarem(enable_docs=False)
app.use(RateLimitMiddleware(per_minute=2))
@app.get("/")
async def home(req):
return {"ok": True}
c = TestClient(app)
assert c.get("/").status_code == 200
assert c.get("/").status_code == 200
r = c.get("/")
assert r.status_code == 429 and "retry-after" in r.headers
assert r.headers["x-ratelimit-limit"] == "2"
RECIPE 16
Timeouts, bulkheads, idempotent writes
Overload becomes clean 503s with Retry-After instead of wedges; retried POSTs with the same Idempotency-Key replay instead of double-charging.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
import asyncio
from ikarem import ConcurrencyLimitMiddleware, IdempotencyMiddleware, Ikarem, TimeoutMiddleware
from ikarem.testing import TestClient
app = Ikarem(enable_docs=False)
app.use(TimeoutMiddleware(timeout=0.05, retry_after=7))
app.use(ConcurrencyLimitMiddleware(limit=10))
app.use(IdempotencyMiddleware())
charges = []
@app.get("/slow")
async def slow(req):
await asyncio.sleep(5)
return {"never": True}
@app.get("/fast")
async def fast(req):
return {"ok": True}
@app.post("/pay")
async def pay(req):
charges.append(1)
return {"charged": len(charges)}
c = TestClient(app)
r = c.get("/slow")
assert r.status_code == 503 and r.headers["retry-after"] == "7"
assert c.get("/fast").status_code == 200
h = {"idempotency-key": "k-1"}
a = c.post("/pay", body={}, headers=h)
b = c.post("/pay", body={}, headers=h)
assert a.json() == b.json() == {"charged": 1} and len(charges) == 1
RECIPE 17
WebSocket rooms
In-process pub/sub: join on connect, broadcast to the rest, leave in a finally. Single process by design — an external broker slots in behind the same shape when you outgrow one node.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
import asyncio
from ikarem import Room
class FakeWS:
def __init__(self):
self.sent = []
async def send_text(self, text):
self.sent.append(text)
async def main():
room = Room()
a, b = FakeWS(), FakeWS()
await room.join(a)
await room.join(b)
assert len(room) == 2
n = await room.broadcast("hi", exclude=a)
assert n == 1 and b.sent == ["hi"] and a.sent == []
room.leave(a)
room.leave(b)
assert len(room) == 0
asyncio.run(main())
RECIPE 18
Static files + downloads
mount_static serves a directory with symlink-escape and prefix-collision guards (.. never leaves the root). FileResponse streams downloads with Content-Disposition intact through middleware.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
import tempfile
from pathlib import Path
from ikarem import Ikarem
from ikarem.testing import TestClient
pub = Path(tempfile.mkdtemp(prefix="cookbook-static-"))
(pub / "ok.txt").write_text("hello file")
app = Ikarem(enable_docs=False)
app.mount_static("/static", str(pub))
@app.get("/receipt")
async def receipt(req):
from ikarem import FileResponse
return FileResponse(str(pub / "ok.txt"), filename="receipt.txt")
c = TestClient(app)
r = c.get("/static/ok.txt")
assert r.status_code == 200 and r.text == "hello file"
assert c.get("/static/../secret.txt").status_code == 404
dl = c.get("/receipt")
assert dl.status_code == 200 and "attachment" in dl.headers.get("content-disposition", "")
RECIPE 19
OpenAPI + routes-as-MCP-tools
Every route derives its OpenAPI operation and MCP tool from the same compiled plan — query shapes, bodies, and auth boundaries can't drift.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
import asyncio
from ikarem import Depends, Ikarem, Schema, require_roles
from ikarem.openapi import build_openapi
SECRET = "cookbook-mcp-secret"
class Item(Schema):
name: str
app = Ikarem(enable_docs=False, auth_secret=SECRET)
@app.post("/items")
async def create_item(item: Item):
"""Create an item."""
return {"name": item.name}
@app.get("/admin")
async def admin(req, claims=Depends(require_roles("admin"))):
return {"sub": claims["sub"]}
spec = build_openapi(app)
assert spec["paths"]["/items"]["post"]["summary"] == "Create an item."
assert spec["paths"]["/admin"]["get"]["security"] == [{"bearerAuth": []}]
tools = {t["name"]: t for t in app.mcp_tools()}
assert set(tools) == {"create_item", "admin"}
out = asyncio.run(app.mcp_call("create_item", {"name": "apple"}))
assert out["isError"] is False
RECIPE 20
Testing + static audit
TestClient keeps cookies across requests (login flows just work); app.check() statically audits handlers — cycles, bare Depends(), duplicate routes — before traffic. 404s suggest close matches.
Runnable — copy into a file and run with python. Executed in CI by tests/test_cookbook.py.
from ikarem import Ikarem
from ikarem.testing import TestClient
app = Ikarem(enable_docs=False)
@app.get("/users")
async def users(req):
return {"n": 1}
report = app.check()
assert report["errors"] == []
assert any(r["path"] == "/users" for r in report["routes"])
c = TestClient(app)
miss = c.get("/user")
assert miss.status_code == 404 and "Did you mean" in miss.text