Overview
If you know Flask, you already know 80% of Chirp. This guide maps Flask concepts to their Chirp equivalents.
App Setup
from flask import Flask
app = Flask(__name__)
app.config["SECRET_KEY"] = "secret"
from chirp import App, AppConfig
config = AppConfig(secret_key="secret")
app = App(config=config)
Chirp uses a frozen dataclass instead of a dict. Noapp.config["SCRET_KEY"]typos.
Routes
@app.route("/users/<int:id>")
def user(id):
return render_template("user.html", user=get_user(id))
@app.route("/users/{id:int}")
def user(id: int):
return Template("user.html", user=get_user(id))
Differences:
- Path parameters use
{name:type}instead of<type:name> - Return
Template(...)instead of callingrender_template() - Type annotations on handler parameters
Request Access
from flask import request
@app.route("/search")
def search():
q = request.args.get("q", "")
return render_template("search.html", q=q)
from chirp import Request
@app.route("/search")
def search(request: Request):
q = request.query.get("q", "")
return Template("search.html", q=q)
Differences:
requestis a parameter, not a global importrequest.queryinstead ofrequest.args- The request is frozen (immutable)
JSON Responses
from flask import jsonify
@app.route("/api/users")
def api_users():
return jsonify({"users": get_all_users()})
@app.route("/api/users")
def api_users():
return {"users": get_all_users()}
Nojsonify()needed. Return a dict and Chirp serializes it as JSON.
Error Handling
@app.errorhandler(404)
def not_found(error):
return render_template("404.html"), 404
@app.error(404)
def not_found(request: Request):
return Template("404.html", path=request.path)
Chirp's error handlers use the same return-value system. No tuple return for status codes.
Template Filters
@app.template_filter()
def currency(value):
return f"${value:,.2f}"
@app.template_filter()
def currency(value: float) -> str:
return f"${value:,.2f}"
Identical decorator pattern. Chirp adds type annotations.
Middleware
# Flask uses WSGI middleware or before/after_request
@app.before_request
def before():
g.start = time.monotonic()
@app.after_request
def after(response):
elapsed = time.monotonic() - g.start
response.headers["X-Time"] = f"{elapsed:.3f}"
return response
async def timing(request: Request, next: Next) -> Response:
start = time.monotonic()
response = await next(request)
return response.with_header("X-Time", f"{time.monotonic() - start:.3f}")
app.add_middleware(timing)
Chirp uses a single middleware function instead of separate before/after hooks.
Sessions and Auth
from flask import session, redirect
from flask_login import login_user, login_required
@app.route("/login", methods=["POST"])
def do_login():
session["user_id"] = user.id
login_user(user)
return redirect("/dashboard")
@app.route("/dashboard")
@login_required
def dashboard():
return render_template("dashboard.html")
from chirp import login, logout, login_required, get_user, is_safe_url, Redirect, Template
@app.route("/login", methods=["POST"])
async def do_login(request: Request):
form = await request.form()
user = await verify_credentials(form["username"], form["password"])
if user:
login(user) # regenerates session automatically
next_url = request.query.get("next", "/dashboard")
if not is_safe_url(next_url):
next_url = "/dashboard"
return Redirect(next_url)
return Template("login.html", error="Invalid credentials")
@app.route("/dashboard")
@login_required
def dashboard():
user = get_user()
return Template("dashboard.html", user=user)
Chirp has built-inlogin() / logout() helpers and @login_required — no Flask-Login equivalent needed. Both login() and logout() regenerate the session to prevent session fixation attacks. Use is_safe_url() to validate ?next= redirects (prevents open redirects). Requires SessionMiddleware + AuthMiddleware. See Built-in Middleware for setup.
What Chirp Adds
Beyond Flask equivalents, Chirp offers:
- Fragment rendering --
Fragment("page.html", "block_name")renders a named block - Streaming HTML --
Stream("page.html")for progressive rendering - Server-Sent Events --
EventStream(generator())for real-time updates - Typed contracts --
app.check()validates htmx references at startup - Free-threading -- Designed for Python 3.14t from day one
Quick Reference
| Flask | Chirp |
|---|---|
Flask(__name__) |
App() |
app.config["KEY"] |
AppConfig(key=...) |
render_template() |
Template(...) |
jsonify() |
Return a dict |
request.args |
request.query |
request(global) |
request(parameter) |
redirect() |
Redirect(...) |
session["key"] |
get_session()["key"] |
login_user(user) |
login(user)(regenerates session) |
@login_required |
@login_required |
| N/A | is_safe_url(url) |
@app.errorhandler |
@app.error |
<int:id> |
{id:int} |
| N/A | Fragment(...) |
| N/A | Stream(...) |
| N/A | EventStream(...) |
Next Steps
- htmx Patterns -- Common htmx + Chirp workflows
- Quickstart -- Build your first app
- Fragments -- The key differentiator