The Index Pattern
Every Caspian route is an
index.py module.
It keeps readable html(...)
markup together with route-owned metadata, first-render context, auth,
caching, redirects, validation, and @rpc() actions.
index.py
The route owner. Use it for metadata,
page(),
auth checks, redirects, route-level caching, or route-owned
@rpc()
actions.
-
Imports
htmlfromcasp.component_decorator -
Returns readable inline markup from
page() -
Receives path params as a single
paramsdict
Inline html(...) markup
The authored page template stays inside the Python route. Import child
components with normal Python imports and keep any
owned plain <script> inside the same single root.
-
Uses HTML-first
x-*component tags - Owns PulsePoint markup and local state
- One authored top-level root is the preferred shape
One route module, one clear owner
index.py
owns the visible page and its server workflow. Split substantial UI into
focused Python components, then keep the route itself as a short assembly.
File-System Routing
Folders under src/app
define URLs directly.
Route Logic Example
Keep metadata, first-render preparation, and readable markup together in
index.py.
Pass server-known values into html(...)
as keyword context.
from typing import Optional from casp.component_decorator import html from casp.layout import Metadata metadata = Metadata( title="Dashboard | Caspian", description="Overview page for the dashboard.", ) async def page(params: dict, search: Optional[str] = None, request=None): slug = params.get("slug") return html(r""" <main><h1>{{ slug }}</h1></main> """, slug=slug, search=search)
params
dict. Query params are injected by name, and
request
is injected by keyword when declared.
Inline Markup Example
Import child components at Python module scope. Keep the returned markup at one authored root and place its owned script inside that root.
from src.components.Button import Button return html(r""" <section class="space-y-4 p-8"> <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"> <span>todo.title</span> </li> </template> </ul> <x-button onclick="setFilter('open')"> Show Open </x-button> <script> const [todos] = pp.state({{ todos | tojson }}); const [filter, setFilter] = pp.state("all"); </script> </section> """, todos=todos)