A Python web framework for the modern web platform — HTML fragments, streaming, SSE, free-threading ready
Project description
⌁⌁ Chirp
A Python web framework for the modern web platform.
from chirp import App
app = App()
@app.route("/")
def index():
return "Hello, World!"
app.run()
Why Chirp?
Flask (2010) and FastAPI (2018) were designed for a different web. Flask assumes you render full HTML pages or bolt on extensions for everything. FastAPI assumes you serve JSON to a JavaScript frontend. Neither reflects where the web platform is in 2026:
- 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 — htmx proved that servers can send HTML fragments and the browser can swap them in — partial page updates with no custom JavaScript
- 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
Chirp is designed from scratch for this reality.
Installation
pip install bengal-chirp
Requires Python 3.14+
Quick Start
| Function | Description |
|---|---|
App() |
Create an application |
@app.route(path) |
Register a route handler |
Template(name, **ctx) |
Render a full template |
Fragment(name, block, **ctx) |
Render a named template block |
Stream(name, **ctx) |
Stream HTML progressively |
EventStream(gen) |
Server-Sent Events stream |
app.run() |
Start the development server |
Features
| Feature | Description | Docs |
|---|---|---|
| Routing | Pattern matching, path params, method dispatch | Routing → |
| Templates | Kida integration, rendering, filters | Templates → |
| Fragments | Render named template blocks independently | Fragments → |
| Streaming | Progressive HTML rendering via Kida | Streaming → |
| SSE | Server-Sent Events for real-time updates | SSE → |
| Middleware | CORS, sessions, static files, 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
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 Fragment("page.html", "results", items=x) # -> 200, rendered block
return Stream("dashboard.html", **async_ctx) # -> 200, streamed HTML
return EventStream(generator()) # -> SSE stream
return Response(body=b"...", status=201) # -> explicit control
return Redirect("/login") # -> 302
No 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.
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.
Key Ideas
- 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+anyio. Everything else is optional.
Documentation
| Section | Description |
|---|---|
| Get Started | Installation and quickstart |
| Core Concepts | App lifecycle, return values, configuration |
| Routing | Routes, requests, responses |
| 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 |
| Tutorials | Flask migration, htmx patterns |
| Reference | API documentation |
Development
git clone https://github.com/lbliii/chirp.git
cd chirp
uv sync --group dev
pytest
The Bengal Ecosystem
A 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.
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.1.0.tar.gz.
File metadata
- Download URL: bengal_chirp-0.1.0.tar.gz
- Upload date:
- Size: 160.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c7a7a5d810c54d1fc0062b5787ab73cf8f90fd310db37b933579e22246427d26
|
|
| MD5 |
dac989f9c8062cabaa4bb55fc8df2d06
|
|
| BLAKE2b-256 |
daf5d980384d8845050fafe14f0c091d77d70c48c2df017aed2ca8252c080f7b
|
File details
Details for the file bengal_chirp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: bengal_chirp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 123.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e2488b6d28532693680aa61d766266958376d3edd64e929fc46388de6583ca1
|
|
| MD5 |
e1d43ecafea748b7230b62f51efb6a8b
|
|
| BLAKE2b-256 |
b442766dc92b65b6bca0c8c5d815d0336d273aca7c9ae8832f74ef813cb54739
|