-
Notifications
You must be signed in to change notification settings - Fork 1
Routing
The entry point of any HTeaLeaf app is the server object. HTeaLeaf supports three variants:
| Variant | Notes |
|---|---|
ASGI |
Async, works with Uvicorn / Hypercorn |
WSGI |
Sync, works with Gunicorn / uWSGI |
CGI |
Legacy |
from htealeaf import HteaLeaf, adapters
app = HteaLeaf(adapters.ASGI)
Run with any ASGI server, e.g. Uvicorn:
uvicorn myapp:appfrom htealeaf import HteaLeaf, adapters
app = HteaLeaf(adapters.WSGI)
if __name__ == "__main__":
from wsgiref.simple_server import make_server
with make_server("", 8000, app) as server:
print("Serving on http://127.0.0.1:8000")
try:
server.serve_forever()
except KeyboardInterrupt:
print("\rBye")Regardless of variant, the server object manages:
- Route registry
- Session management
- Server-specific functionality (WSGI entrypoint, CGI handlers, etc.)
HTeaLeaf offers two equivalent ways to register routes.
@app.route("/hello")
def hello():
return "Hello World!"def hello():
return "Hello World!"
app.add_path("/hello", hello)Both approaches are equivalent. The decorator style is more concise for most cases; add_path is useful when routes are registered dynamically or assembled from a separate module.
Each route handler must return a valid HTTP response. Accepted types:
| Type | Description |
|---|---|
str |
Plain text or HTML string |
dict / list
|
Serialised as JSON |
bytes |
Raw byte response |
| HTML element | Object built with htealeaf.elements
|
| 2-tuple |
(status, body) — e.g. return 401, "Unauthorized"
|
| 3-tuple | Full HTTP response (status, headers, body)
|
⚠️ In the tuple forms the status comes first.return "Unauthorized", 401would send"Unauthorized"as the status line and401as the body.
For redirects there is a ready-made helper:
from htealeaf.server.utils import redirect
@app.route("/old")
def old():
return redirect("/new")Route handlers can be async def — the server awaits them natively (sync handlers are run in an executor so they don't block the loop):
@app.route("/")
async def home(session, req: Request):
...use_state() and @js work the same in both forms — the render context is propagated to the executor thread that runs sync handlers.
Dynamic segments are declared with curly braces {} and injected as function arguments:
@app.route("/hello/{name}")
def greet(name):
return f"Hello, {name}!"A request to /hello/John returns Hello, John!.
Add a req: Request argument to receive the full request object:
from htealeaf.server.http import Request
@app.route("/echo")
def echo(req: Request):
return {
"method": req.method,
"path": req.path,
"body": req.text(),
"json": req.json(),
}Other useful attributes: req.args (query-string dict), req.headers, req.form() (parsed form body, None if not a form), req.is_ssl.
Add a session argument to access the user's session:
from htealeaf.server import SessionData
@app.route("/profile")
def profile(session: SessionData):
if session.has("userName"):
return f"Welcome, {session['userName']}"
return "Please log in"HTeaLeaf injects arguments by parameter name (annotations are optional and ignored for matching): declare req, session, and/or cookies (a dict of the request's cookies) and only what you name gets passed.
The server exposes an event hook system — the same mechanism SuperStore uses to wire itself in. Register a callback with registry_hook:
from htealeaf.server.server import ServerEvent
def log_request(req):
print(req.method, req.path)
app.registry_hook(ServerEvent.on_request, log_request)Available events: on_request (receives the Request), on_response (status, body, headers — before rendering), on_render (the root Component about to be rendered), path_registered, and new_session.
Path parameters, req, and session can be combined freely:
@app.route("/user/{id}/settings")
def user_settings(id, session: SessionData):
if not session.has("userName"):
return 401, "Unauthorized"
return f"Settings for user {id}"🍃 HTeaLeaf
State
Client-side