Skip to main content

A Python web framework for the modern web platform — HTML fragments, streaming, SSE, free-threading ready

Project description

⌁⌁ Chirp

PyPI version Python 3.14+ License: MIT Status: Alpha

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: ignore comments.
  • 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

📚 lbliii.github.io/chirp

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bengal_chirp-0.1.0.tar.gz (160.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bengal_chirp-0.1.0-py3-none-any.whl (123.0 kB view details)

Uploaded Python 3

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

Hashes for bengal_chirp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c7a7a5d810c54d1fc0062b5787ab73cf8f90fd310db37b933579e22246427d26
MD5 dac989f9c8062cabaa4bb55fc8df2d06
BLAKE2b-256 daf5d980384d8845050fafe14f0c091d77d70c48c2df017aed2ca8252c080f7b

See more details on using hashes here.

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

Hashes for bengal_chirp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7e2488b6d28532693680aa61d766266958376d3edd64e929fc46388de6583ca1
MD5 e1d43ecafea748b7230b62f51efb6a8b
BLAKE2b-256 b442766dc92b65b6bca0c8c5d815d0336d273aca7c9ae8832f74ef813cb54739

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page