Skip to content
/ purr Public

≋ Purr — Content-reactive runtime for Python 3.14+ that turns static sites into dynamic apps

Notifications You must be signed in to change notification settings

lbliii/purr

Repository files navigation

purr

A content-reactive runtime for Python 3.14t.

import purr

purr.dev("my-site/")

Purr unifies the Bengal ecosystem into a single content-reactive runtime. Edit a Markdown file, and the browser updates the affected paragraph — not the page, not the site, just the content that changed. Add a dynamic route alongside your static content without changing frameworks. Deploy as static files or run as a live server. The boundary between static site and web application disappears.

Status: Pre-alpha — Phase 5 (incremental pipeline + observability) complete. Content changes propagate in O(change): incremental re-parse, selective block recompile, and targeted SSE broadcast. Full-stack observability unifies events from Pounce connections, content parsing, template compilation, and browser updates into a single queryable log. See ROADMAP.md for the full plan.


What is Purr?

Purr is the integration layer for the Bengal ecosystem. It connects Bengal's dependency graph to Chirp's SSE pipeline, maps Patitas AST changes to Kida template blocks, and unifies static content with dynamic routes. Edit a Markdown file and the browser updates the affected paragraph — not the page, not the site, just the content that changed.

What's good about it:

  • Content-reactive — Changes propagate through incremental re-parse, AST diff, selective block recompile, and into the browser via SSE. O(change), not O(document).
  • Static-to-dynamic continuum — Start with Markdown and templates. Add Chirp routes when you need search, APIs, or dashboards. Same templates, same server, same URL space.
  • Three modespurr dev for reactive local development. purr build for static export. purr serve for live production with dynamic routes.

Quick Start

# Development server (single worker, reactive pipeline active)
purr dev my-site/

# Static export (renders all routes to HTML files)
purr build my-site/

# Static export with asset fingerprinting and sitemap
purr build my-site/ --base-url https://example.com --fingerprint

# Production server (multi-worker via Pounce)
purr serve my-site/ --workers 4

Or programmatically:

import purr

purr.dev("my-site/")       # Reactive local development
purr.build("my-site/")     # Static export (all routes → HTML files)
purr.serve("my-site/")     # Live production server

How It Works

Content is a reactive data structure, not a build artifact. When you edit a Markdown file, the change propagates through a typed pipeline:

Edit Markdown file
    → Patitas incrementally re-parses only the affected blocks (O(change), not O(document))
    → ASTDiffer identifies which nodes changed (O(1) skip for unchanged subtrees)
    → DependencyGraph resolves affected pages and template blocks
    → Kida selectively recompiles only the changed template blocks (O(changed_blocks))
    → ReactiveMapper maps AST changes to specific block updates
    → Broadcaster pushes HTML fragments via SSE
    → Browser swaps the DOM (htmx, no JS framework)
    → Every step recorded as a typed event in the observability log

This isn't hot-reload. Hot-reload rebuilds the page. Purr traces a content change through the dependency graph to the exact DOM element that needs updating — and every step is observable.

┌─────────────────────────────────────────────────────────┐
│  Browser (htmx SSE subscription on /__purr/events)      │
└────────────────────────────┬────────────────────────────┘
                             │ HTTP + SSE
┌────────────────────────────▼────────────────────────────┐
│  Pounce (ASGI server, free-threading workers)           │
└────────────────────────────┬────────────────────────────┘
                             │ ASGI
┌────────────────────────────▼────────────────────────────┐
│  Purr App                                               │
│                                                         │
│  ContentRouter — Bengal pages as Chirp routes            │
│  RouteLoader  — user Python routes alongside content    │
│  SSE endpoint — /__purr/events                          │
│                                                         │
│  Reactive Pipeline (dev mode):                          │
│  FileWatcher → Incremental Parse → ASTDiffer → Mapper   │
│    → Block Recompile → SSE Broadcaster                  │
│                                                         │
│  Observability:                                         │
│  StackCollector ← Pounce events + pipeline events       │
│    → EventLog (queryable ring buffer)                   │
└─────────────────────────────────────────────────────────┘

Dynamic Routes

Add Python routes alongside your static content. Create a file in routes/ — the file path becomes the URL, and function names map to HTTP methods:

my-site/
├── content/          # Markdown pages (served by Bengal)
├── routes/
│   ├── search.py     # GET /search
│   └── api/
│       └── users.py  # GET /api/users
└── templates/
# routes/search.py
from chirp import Request, Response

async def get(request: Request) -> Response:
    query = request.query.get("q", "")
    results = site.search(query)
    return request.template("search.html", query=query, results=results)

No decorators. No base classes. No registration ceremony. If a function is named get, post, put, delete, or patch, it handles that HTTP method. If it's named handler, it handles GET.

Dynamic routes share the same templates and URL space as your content. They appear in navigation automatically via nav_title, and they access the Bengal site data through from purr import site.


Key Ideas

  • Content-reactive. Content is a typed data structure, not a build artifact. Changes propagate through incremental re-parse, AST diff, selective block recompile, and into the browser via SSE — surgically, in O(change), not O(document).
  • Static-to-dynamic continuum. Start with Markdown and templates. Add Chirp routes when you need search, APIs, or dashboards. Same templates, same server, same URL space. No migration, no rewrite.
  • Three modes. purr dev for reactive local development. purr build for static export to any CDN — renders all routes (content + dynamic), fingerprints assets, and generates a sitemap. purr serve for live production with dynamic routes and real-time updates.
  • Observable. Every stage of the pipeline — connection, parse, diff, recompile, broadcast — produces a typed event with nanosecond timestamps. Query the EventLog by event type, file path, or time range. Full-stack telemetry without logging.
  • Integration layer. Purr is thin by design — the hard problems are solved by Bengal (content pipeline), Chirp (framework), Kida (templates), Patitas (Markdown), Rosettes (highlighting), and Pounce (server). Purr wires them together.
  • Free-threading native. Built on Python 3.14t. Pounce serves with real thread parallelism. Kida compiles templates to Python AST. Patitas parses Markdown with O(n) state machines. No GIL, no fork, no compromise.

Requirements

  • Python >= 3.14

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 ← You are here
⌁⌁ Chirp Web framework 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

About

≋ Purr — Content-reactive runtime for Python 3.14+ that turns static sites into dynamic apps

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors