A Python web framework for the modern web platform.
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. 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 new myapp && cd myapp && python app.py| Function | Description |
|---|---|
chirp new <name> |
Scaffold a new project |
chirp run <app> |
Start the dev server from an import string |
chirp check <app> |
Validate hypermedia contracts |
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 |
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 → |
📚 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: docs/deployment/production.md
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:
issues = app.check()
for issue in issues:
print(f"{issue.severity}: {issue.message}")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. Broken references become compile-time errors, not runtime 404s.
- 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. - 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 — every layer written in pure Python for 3.14t free-threading.
| ᓚᘏᗢ | Bengal | Static site generator | Docs |
| ∿∿ | Purr | Content runtime | — |
| ⌁⌁ | Chirp | Web framework ← You are here | Docs |
| =^..^= | 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