A Python web framework for HTML over the wire.
from chirp import App
app = App()
@app.route("/")
def index():
return "Hello, World!"
app.run()Chirp is a Python web framework built for the modern web platform: browser-native UI, HTML over the wire, streaming responses, and Server-Sent Events. Use plain Kida templates, your own CSS, or optional companion packages like chirp-ui. Return values drive content negotiation — no make_response(), no jsonify(). The type is the intent.
What's good about it:
- Browser-native UI —
<dialog>,popover, View Transitions, container queries. Most of what required a JS framework is now native HTML and CSS. - HTML over the wire — Serve full pages, template fragments, streaming HTML, and SSE. Built for htmx and the modern browser.
- Streaming HTML — Send the page shell immediately and fill in content as data becomes available. No loading spinners, no skeleton screens.
- Server-Sent Events — Push real-time updates over plain HTTP. No WebSocket protocol upgrade, no special infrastructure.
# pip
pip install bengal-chirp
# uv
uv add bengal-chirpRequires Python 3.14+.
Chirp works on its own with plain templates. chirp-ui is an optional companion UI layer, not part of the framework core.
chirp new myapp && cd myapp && python app.py| Function | Description |
|---|---|
chirp new <name> |
Scaffold an auth-ready project |
chirp new <name> --shell |
Scaffold with a persistent app shell (topbar + sidebar) |
chirp new <name> --sse |
Scaffold with SSE boilerplate (EventStream, sse_scope) |
chirp run <app> |
Start the dev server from an import string |
chirp check <app> |
Validate hypermedia contracts |
chirp check <app> --warnings-as-errors |
Fail CI on contract warnings |
chirp routes <app> |
Print the registered route table |
App() |
Create an application |
@app.route(path) |
Register a route handler |
Template(name, **ctx) |
Render a full template |
Template.inline(src, **ctx) |
Render from string (prototyping) |
Page(name, block, **ctx) |
Auto Fragment or Template based on request |
PageComposition(template, fragment_block, ...) |
Python-first composition with regions |
Fragment(name, block, **ctx) |
Render a named template block |
Stream(name, **ctx) |
Stream HTML progressively |
Suspense(name, **ctx) |
Shell first, OOB swaps for deferred data |
EventStream(gen) |
Server-Sent Events stream |
app.run() |
Start the development server |
| Feature | Description | Docs |
|---|---|---|
| Routing | Pattern matching, path params, method dispatch | Routing → |
| Filesystem routing | Route discovery from pages/ with layouts |
Filesystem → |
| Templates | Kida integration, rendering, filters | Templates → |
| Fragments | Render named template blocks independently | Fragments → |
| Forms | form_or_errors, form macros, validation |
Forms → |
| Streaming | Progressive HTML rendering via Kida | Streaming → |
| SSE | Server-Sent Events for real-time updates | SSE → |
| Middleware | CORS, sessions, static files, security headers, custom | Middleware → |
| Contracts | Compile-time validation of hypermedia surface | Reference → |
| Testing | Test client, assertions, isolation utilities | Testing → |
| Data | Database integration and form validation | Data → |
| Optional UI layer | chirp-ui companion components and styles |
chirp-ui → |
📚 Full documentation: lbliii.github.io/chirp
Chirp apps run on pounce, a production-grade ASGI server with enterprise features built-in:
- ✅ WebSocket compression — 60% bandwidth reduction
- ✅ HTTP/2 support — Multiplexed streams, server push
- ✅ Graceful shutdown — Finishes active requests on SIGTERM
- ✅ Zero-downtime reload —
kill -SIGUSR1for hot code updates - ✅ Built-in health endpoint —
/healthfor Kubernetes probes
- 📊 Prometheus metrics —
/metricsendpoint for monitoring - 🛡️ Per-IP rate limiting — Token bucket algorithm, configurable burst
- 📦 Request queueing — Load shedding during traffic spikes
- 🐛 Sentry integration — Automatic error tracking and reporting
- 🔄 Multi-worker mode — CPU-based auto-scaling
from chirp import App, AppConfig
# Production configuration
config = AppConfig(
debug=False, # ← Enables production mode
workers=4,
metrics_enabled=True,
rate_limit_enabled=True,
sentry_dsn="https://...",
)
app = App(config=config)
@app.route("/")
def index():
return "Hello, Production!"
app.run() # ← Automatically uses production server# Development (single worker, auto-reload)
chirp run myapp:app
# Production (multi-worker, all features)
chirp run myapp:app --production --workers 4 --metrics --rate-limitFROM python:3.14-slim
WORKDIR /app
COPY . .
RUN pip install bengal-chirp
CMD ["chirp", "run", "myapp:app", "--production", "--workers", "4"]Full deployment guide: Deployment docs
Return Values — Type-driven content negotiation
Route functions return values. The framework handles content negotiation based on the type:
return "Hello" # -> 200, text/html
return {"users": [...]} # -> 200, application/json
return Template("page.html", title="Home") # -> 200, rendered via Kida
return Page("search.html", "results", items=x) # -> Fragment or Template (auto)
return Fragment("page.html", "results", items=x) # -> 200, rendered block
return Stream("dashboard.html", **async_ctx) # -> 200, streamed HTML
return Suspense("dashboard.html", stats=...) # -> shell + OOB swaps
return EventStream(generator()) # -> SSE stream
return Response(body=b"...", status=201) # -> explicit control
return Redirect("/login") # -> 302No make_response(). No jsonify(). The type is the intent.
Fragments and htmx — Render template blocks independently
Kida can render a named block from a template independently, without rendering the whole page:
{# templates/search.html #}
{% extends "base.html" %}
{% block content %}
<input type="search" hx-get="/search" hx-target="#results" name="q">
{% block results_list %}
<div id="results">
{% for item in results %}
<div class="result">{{ item.title }}</div>
{% end %}
</div>
{% endblock %}
{% endblock %}@app.route("/search")
async def search(request: Request):
results = await db.search(request.query.get("q", ""))
if request.is_fragment:
return Fragment("search.html", "results_list", results=results)
return Template("search.html", results=results)Full page request renders everything. htmx request renders just the results_list block.
Same template, same data, different scope. No separate "partials" directory.
Streaming HTML — Progressive rendering
Kida renders template sections as they complete. The browser receives the shell immediately and content fills in progressively:
@app.route("/dashboard")
async def dashboard(request: Request):
return Stream("dashboard.html",
header=site_header(),
stats=await load_stats(),
activity=await load_activity(),
)Server-Sent Events — Real-time HTML updates
Push Kida-rendered HTML fragments to the browser in real-time:
@app.route("/notifications")
async def notifications(request: Request):
async def stream():
async for event in notification_bus.subscribe(request.user):
yield Fragment("components/notification.html", event=event)
return EventStream(stream())Combined with htmx's SSE support, this enables real-time UI updates with zero client-side JavaScript. The server renders HTML, the browser swaps it in.
Middleware — Composable request/response pipeline
No base class. No inheritance. A middleware is anything that matches the protocol:
async def timing(request: Request, next: Next) -> Response:
start = time.monotonic()
response = await next(request)
elapsed = time.monotonic() - start
return response.with_header("X-Time", f"{elapsed:.3f}")
app.add_middleware(timing)Built-in middleware: CORS, StaticFiles, HTMLInject, Sessions, SecurityHeaders.
Typed Contracts — Compile-time hypermedia validation
Chirp validates the server-client boundary at startup:
# Prints a contract report and exits non-zero on errors.
app.check()
# Optional strict mode: treat warnings as failures too.
app.check(warnings_as_errors=True)Every hx-get, hx-post, and action attribute in your templates is checked against the
registered route table. Every Fragment and SSE return type is checked against available
template blocks. SSE safety checks catch broken sse-connect / sse-swap structures and
unsafe inherited target scopes before runtime.
For strict CI:
chirp check myapp:app --warnings-as-errors- HTML over the wire. Serve full pages, template fragments, streaming HTML, and Server-Sent Events. Built for htmx and the modern browser.
- Kida built in. Same author, no seam. Fragment rendering, streaming templates, and filter registration are first-class features, not afterthoughts.
- Typed end-to-end. Frozen config, frozen request, chainable response. Zero
type: ignorecomments. - Free-threading native. Designed for Python 3.14t from the first line. Immutable data structures, ContextVar isolation.
- Contracts, not conventions.
app.check()validates the full hypermedia surface at startup. - UI is optional. Build with plain templates and your own design system, or add
chirp-uias a companion layer. - Minimal dependencies.
kida-templates+anyio+bengal-pounce. Everything else is optional.
| Section | Description |
|---|---|
| Get Started | Installation and quickstart |
| Core Concepts | App lifecycle, return values, configuration |
| Routing | Routes, filesystem routing, requests |
| Templates | Rendering, fragments, filters |
| Streaming | HTML streaming and Server-Sent Events |
| Middleware | Built-in and custom middleware |
| Data | Database integration and forms |
| Testing | Test client and assertions |
| Deployment | Production deployment with Pounce |
| Tutorials | Flask migration, htmx patterns |
| Examples | RAG demo, production stack, API |
| Reference | API documentation |
git clone https://github.com/lbliii/chirp.git
cd chirp
uv sync --group dev
pytestA structured reactive stack written in pure Python for 3.14t free-threading. Chirp is the framework; packages like chirp-ui sit on top as optional companions.
| ᓚᘏᗢ | Bengal | Static site generator | Docs |
| ∿∿ | Purr | Content runtime | — |
| ⌁⌁ | Chirp | Web framework ← You are here | Docs |
| ʘ | chirp-ui | Optional companion UI layer | — |
| =^..^= | Pounce | ASGI server | Docs |
| )彡 | Kida | Template engine | Docs |
| ฅᨐฅ | Patitas | Markdown parser | Docs |
| ⌾⌾⌾ | Rosettes | Syntax highlighter | Docs |
Python-native. Free-threading ready. No npm required.
MIT