The Index Pattern
Separation of concerns without the complexity. Use
index.py for
server-side logic and
index.html for
reactive markup.
index.py
The Controller. Handles database queries, metadata definitions, and heavy lifting. It executes strictly on the server.
- Defines Page Metadata
- Fetches Data (Async Prisma)
- Smart Argument Binding
index.html
The View. Defines the layout and interactivity using PulsePoint. It receives data implicitly from the Python controller.
- Reactive UI (pp.state, pp.effect)
- Tailwind CSS Styling
- Zero-Build Component Imports
File-System Routing
Routes are defined automatically by your folder structure. A folder
becomes a public URL route if it contains either an
index.html
or an
index.py.
! Routing Hierarchy: Python Takes Precedence
If a folder contains both files, the server designates
index.py as the primary
entry point. The HTML file is ignored by the router and will only be
rendered if the Python script explicitly loads it.
1. The Logic Layer
src/app/index.py
The controller supports Async/Await and Smart Argument Binding.
Arguments are fully optional—simply define what you need (like request or specific query parameters) and the server injects them.
from typing import Optional from casp.page import render_page # --- Pattern 1: Minimal --- # No arguments needed? Just define page(). def page(): return render_page(__file__) # --- Pattern 2: Async & Request --- # Need headers/session? Ask for 'request'. async def page(request): print(request.headers.get("User-Agent")) return render_page(__file__) # --- Pattern 3: Typed Query Params --- # Arguments match URL query params (e.g., ?search=foo). async def page(search: Optional[str] = None): if search: # DB logic here... pass return render_page(__file__, {"query": search}) # --- Pattern 4: Mixed (Power User) --- # Combine request access with specific params. async def page( request, sort: str = "desc" ): user = request.session.get("user") return render_page(__file__)
2. The Logic Layer
src/app/index.py
The Python file exports a
page()
function. Use the Metadata class (imported from casp.layout) to define headers and inject data.
from casp.page import render_page from casp.layout import Metadata # 1. Define Metadata Metadata( title="Documentation | Caspian", description="Comprehensive documentation for Caspian.", ) # 2. The entry point function def page(): # Logic: Fetch data from DB todos = prisma.todo.find_many() # Return view with injected data return render_page(__file__, { "todos": [todo.to_dict() for todo in todos] })
2. The Reactive View
src/app/index.html
The HTML file contains your UI layout. Use <template pp-for> for lists and initialize state using the [[var|json]] syntax.
<!-- Import Icons --> <!-- @import { CheckCircle } from ../../lib/ppicons --> <div class="p-10 space-y-4"> <h1 class="text-2xl font-bold">My Todos</h1> <ul class="space-y-2"> <template pp-for="todo in todos"> <li key="{todo.id}" class="flex items-center gap-2"> <CheckCircle class="size-4 text-primary" /> <span>{todo.title}</span> </li> </template> </ul> </div> <script> // Initialize state with data injected from Python const [todos, setTodos] = pp.state([[todos|json]]); </script>