Python web framework for HTMX, HTML fragments, streaming HTML, and Server-Sent Events
Project description
⌁⌁ Chirp
A Python web framework for HTMX, HTML fragments, streaming HTML, and Server-Sent Events.
from chirp import App
app = App()
@app.route("/")
def index():
return "Hello, World!"
app.run()
What is Chirp?
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. Routes return intent — Page, Fragment, OOB, EventStream, Suspense — and the framework handles content negotiation, layout composition, and htmx awareness automatically. One template with named blocks serves as a full page, a fragment endpoint, an SSE payload, and a Suspense deferred block. No make_response(). No jsonify(). The type is the intent.
@app.route("/search")
async def search(request: Request):
results = await db.search(request.query.get("q", ""))
return Page("search.html", "results", results=results)
# Full page for browsers. Fragment for htmx. Same template, same data.
- Browser-native UI —
<dialog>,popover, View Transitions, container queries. Let the browser be the framework. - HTML over the wire — Full pages, fragments, streaming HTML, and SSE. Built for htmx.
- Streaming HTML — Shell first, content fills in as data arrives. No loading spinners.
- Server-Sent Events — Real-time updates over plain HTTP. No WebSocket upgrade required.
- MCP tools — Register functions as tools callable by LLMs and MCP clients.
Read the Philosophy for the full picture.
Use Chirp For
- HTMX-driven web apps — Server-rendered UI with fragment swaps and progressive enhancement
- Server-rendered applications — Full pages plus partial updates from the same templates
- Streaming interfaces — Progressive HTML delivery and token-by-token responses
- Real-time dashboards — SSE-powered updates without WebSocket complexity
- Teams avoiding heavy frontend stacks — HTML, CSS, templates, and browser-native features
Installation
# pip
pip install bengal-chirp
# uv
uv add bengal-chirp
Requires 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.
Quick Start
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 |
hx_redirect(url) |
Redirect helper for htmx and full-page requests |
app.run() |
Start the development server |
Features
| Feature | Description | Docs |
|---|---|---|
| HTMX Patterns | Search, inline edit, infinite scroll, modal, and fragment workflows | htmx Patterns → |
| Comparison | When Chirp fits compared with Flask, FastAPI, and Django | When to Use Chirp → |
| Routing | Pattern matching, path params, method dispatch | Routing → |
| Filesystem routing | Route discovery from pages/ with layouts |
Filesystem → |
| Route directory contract | _meta.py, _context.py, _actions.py, sections, shell context, and route validation |
Route Directory → |
| Route introspection | Reserved files, inheritance rules, debug headers, and route explorer | Route Contract → |
| 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 | Validate htmx attrs, form actions, and route-bearing dialog args | 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
Benchmarks
Chirp now ships a synthetic benchmark suite for comparing Chirp, FastAPI, and Flask across JSON and CPU workloads, plus Chirp-specific fused sync and mixed JSON+SSE scenarios.
uv sync --extra benchmark
uv run poe benchmark
See benchmarks/README.md for how the benchmarks work, their caveats, and the available runners.
Production Deployment
Chirp apps run on Pounce, a production-grade ASGI server with HTTP/2, graceful shutdown, Prometheus metrics, rate limiting, and multi-worker scaling. See the deployment guide for details.
Usage
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 hx_redirect("/dashboard") # -> Location + HX-Redirect
return Response(body=b"...", status=201) # -> explicit control
return Redirect("/login") # -> 302
No make_response(). No jsonify(). The type is the intent.
For htmx-driven form posts or mutations that should trigger a full-page
navigation, prefer hx_redirect() so both plain browser and htmx requests
follow the redirect correctly.
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
Development
git clone https://github.com/lbliii/chirp.git
cd chirp
uv sync --group dev
pytest
The Bengal Ecosystem
A 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 |
| ⚡ | Zoomies | QUIC / HTTP/3 | — |
Python-native. Free-threading ready. No npm required.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bengal_chirp-0.3.2.tar.gz.
File metadata
- Download URL: bengal_chirp-0.3.2.tar.gz
- Upload date:
- Size: 392.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d5647fe43cf6f5d1c2b802e72b92a48184bfe4b96b72dfd5cc915ff106750f6
|
|
| MD5 |
f2d95efd3532a91198b147360c32ba30
|
|
| BLAKE2b-256 |
7e72f601df263bc6b2437d08fccbea1076b420e3a9b6f4546ca72d9bacf2bd85
|
Provenance
The following attestation bundles were made for bengal_chirp-0.3.2.tar.gz:
Publisher:
python-publish.yml on lbliii/chirp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bengal_chirp-0.3.2.tar.gz -
Subject digest:
5d5647fe43cf6f5d1c2b802e72b92a48184bfe4b96b72dfd5cc915ff106750f6 - Sigstore transparency entry: 1201082061
- Sigstore integration time:
-
Permalink:
lbliii/chirp@3b249a57482c5d5f4dd2e03a0970567d9a7a863b -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/lbliii
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@3b249a57482c5d5f4dd2e03a0970567d9a7a863b -
Trigger Event:
release
-
Statement type:
File details
Details for the file bengal_chirp-0.3.2-py3-none-any.whl.
File metadata
- Download URL: bengal_chirp-0.3.2-py3-none-any.whl
- Upload date:
- Size: 350.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c9c2c83f171fb167ec4a29177bfb85cf76fc7481ee5b9fb4eecf3a25546ba13
|
|
| MD5 |
3a61759f3dd78bda50353c50c2377063
|
|
| BLAKE2b-256 |
35d0c6e6dba09f3751782ed3d108453e005064b1bf7dbbd2a1cd8f1cbde2bea5
|
Provenance
The following attestation bundles were made for bengal_chirp-0.3.2-py3-none-any.whl:
Publisher:
python-publish.yml on lbliii/chirp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bengal_chirp-0.3.2-py3-none-any.whl -
Subject digest:
5c9c2c83f171fb167ec4a29177bfb85cf76fc7481ee5b9fb4eecf3a25546ba13 - Sigstore transparency entry: 1201082082
- Sigstore integration time:
-
Permalink:
lbliii/chirp@3b249a57482c5d5f4dd2e03a0970567d9a7a863b -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/lbliii
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@3b249a57482c5d5f4dd2e03a0970567d9a7a863b -
Trigger Event:
release
-
Statement type: