Skip to main content

Caspian Utils (casp)

HTML-first utilities for Caspian applications.

caspian-utils is the shared Python runtime package behind Caspian templates, components, layouts, RPC handlers, and supporting utilities. This repository is not a full application starter, so this README documents the package surface that exists here.

  • PyPI package: caspian-utils
  • Python package: casp
  • Python requirement: >=3.14

Installation

pip install caspian-utils

Core Model

Caspian is HTML-first.

  • write UI in .html
  • use native Jinja syntax for server-rendered values and control flow
  • define reusable components in Python
  • import components with @import comments inside HTML
  • render components with <x-component-name /> tags
  • place child routes in layouts with a real HTML <slot />
  • render pages and layouts from Python with helpers in casp.layout

There is no Caspian-only template syntax layer. Use standard Jinja {{ ... }} and {% ... %} directly.

HTML-First Components

Define a component in Python

Return the markup from html(...), which renders the string through the same Jinja environment Caspian uses for template files.

from casp.component_decorator import component, html


@component
def AlertBox(title: str, children: str = "", class_name: str = "") -> str:
    return html("""
    <section class="alert {{ class_name }}">
        <h2>{{ title }}</h2>
        <div>{{ children }}</div>
    </section>
    """, title=title, children=children, class_name=class_name)

Prefer html(...) over a Python f-string for component markup. An f-string consumes single braces, so {likes} intended for client-side reactivity has to be written {{likes}} and the two brace dialects start fighting. With html(...) they coexist:

Syntax Meaning
{{ value }} server render (Python to HTML), autoescaped
{{ value | json }} serialize a server value into a <script>, returns Markup
{# comment #} Jinja comment, stripped from output
{ value } left untouched for client-side reactivity

Autoescaping is on, so {{ value }} escapes user text automatically; trusted HTML needs Markup(...) or | safe. A children value is marked safe for you, so nested component markup renders without | safe.

For large markup or long scripts, keep the Python file thin and put the markup in a sibling .html file with render_html:

from casp.component_decorator import component, render_html


@component
def AlertBox(title: str, children: str = "") -> str:
    return render_html(__file__, {"title": title, "children": children})

render_html(__file__, {...}) and render_html(__file__, title="Saved") are both accepted. Either form must render exactly one top-level element, with any <script> nested inside it.

Import and use it in HTML

<!-- @import { AlertBox } from "../components/ui" -->

<main class="space-y-4">
  <x-alert-box title="Saved">
    <p>Your settings were updated.</p>
  </x-alert-box>
</main>

Alias imports when needed

<!-- @import { AlertBox as Notice } from "../components/ui" -->

<div>
  <x-notice title="Heads up" />
</div>

Import and tag rules:

  • AlertBox becomes <x-alert-box>
  • aliases also convert to kebab-case, so Notice becomes <x-notice>
  • grouped imports use <!-- @import { A, B as C } from "..." -->
  • single imports use <!-- @import ComponentName from "..." -->
  • paths are resolved relative to the current template or component directory

Example: <!-- @import { AlertBox } from "../components/ui" --> resolves AlertBox from ../components/ui/AlertBox.py.

Rendering Pages

Use casp.layout to render HTML templates relative to a Python file.

from casp.layout import render_page


def get_dashboard() -> str:
    return render_page(__file__, {
        "pageTitle": "Dashboard",
        "stats": ["Projects", "Tasks", "Alerts"],
    })
<!-- app/dashboard/index.html -->
<!-- @import { AlertBox } from "../components/ui" -->

<main class="space-y-6">
  <h1>{{ pageTitle }}</h1>

  <x-alert-box title="Welcome back" class="rounded border p-4">
    <p>Rendered through an imported Python component.</p>
  </x-alert-box>

  <ul>
    {% for label in stats %}
      <li>{{ label }}</li>
    {% endfor %}
  </ul>
</main>

casp.layout also exposes render_layout(), render(), load_template(), compile_template(), and layout discovery helpers for nested layout flows.

Nested Layouts

Layouts are authored as HTML and use a real <slot /> element as the child-route outlet.

<!-- app/layout.html -->
<html>
  <head>
    <title>{{ metadata.title }}</title>
  </head>
  <body>
    <slot />
  </body>
</html>

During nested layout rendering, Caspian parses the layout HTML and replaces real <slot> elements with the current child page or nested layout. Escaped documentation text such as &lt;slot /&gt; is not treated as a layout outlet.

If a layout needs shared props or metadata, add a sibling layout.py:

from casp.layout import Metadata

metadata = Metadata(title="Dashboard")


def layout():
    return {
        "shell_class": "dashboard-shell",
    }

Those props are available in layout.html as {{ layout.shell_class }}. The installed layout runtime supports sync or async layout() results, but layout work should stay focused on shared subtree props or metadata.

Template Syntax

Caspian templates use native Jinja:

  • {{ value }} for interpolation
  • {% if condition %}...{% endif %} for conditionals
  • {% for item in items %}...{% endfor %} for loops
  • filters such as {{ children | safe }}

There is no additional Caspian template language on top of Jinja.

Template Constraints

Some compiler rules are important when writing templates:

  • every page, layout, and component must render exactly one top-level HTML element
  • unknown <x-...> tags raise an error unless the component has been imported
  • async components are supported by the component pipeline
  • authored PulsePoint scripts should be plain <script> tags inside the single root; the runtime can rewrite them for browser execution

These constraints exist because Caspian injects pp-component metadata into the rendered root element.

RPC Helpers

The package also includes the server-side RPC decorator and related request/serialization utilities.

from casp.rpc import rpc


@rpc(require_auth=True, limits="30/minute")
async def save_profile(name: str):
    return {"ok": True, "name": name}

casp.rpc includes:

  • RPC registration and route-scoped function lookup
  • auth-aware decorators
  • rate limiting
  • serialization for common Python objects
  • FastAPI-oriented request and response helpers

Request gates

Every RPC call is checked before the decorated function runs. A call that fails a gate never reaches application code:

Gate Failure
Origin against the allow-list (skipped when the header is absent) 403 Invalid origin
Content-Type is application/json or multipart/form-data when a body is present 415 Invalid content type
X-CSRF-Token compared against the session token 403 Missing CSRF token / Invalid CSRF token
require_auth=True 401 Authentication required
roles=[...] 403 Permission denied
per-route rate limit 429

The allow-list is the request's own base URL, plus APP_BASE_URL, plus CORS_ALLOWED_ORIGINS, plus the forwarded origin when TRUST_FORWARDED_HEADERS is on. Outside production, http://localhost:<port> and http://127.0.0.1:<port> origins are also accepted.

Payload keys are filtered against the function signature, so a parameter is settable by the client only when it is declared. Declaring **kwargs opts the function into the entire payload.

Environment

casp.rpc and casp.runtime_security read these on use, so loading .env at any point before the first request is sufficient — import order does not affect them.

Variable Default Effect
APP_ENV unset Resolved fail-closed. Only dev, development, local, staging, test, or testing select development behavior; anything else, including unset or misspelled, is treated as production.
AUTH_SECRET none Session secret. Required in production; a missing or placeholder value raises.
CORS_ALLOWED_ORIGINS unset Comma-separated additions to the origin allow-list.
APP_BASE_URL unset Public origin, for deployments behind a proxy.
TRUST_FORWARDED_HEADERS off Honour X-Forwarded-* / Forwarded for the origin check and rate-limit bucket. Enable only behind a proxy you control.
CONTENT_SECURITY_POLICY built-in policy Replaces the default CSP wholesale.
RATE_LIMIT_DEFAULT 200/minute Default limit passed to slowapi.
RATE_LIMIT_RPC 60/minute Applied to @rpc() without explicit limits.
RATE_LIMIT_AUTH 60/minute Applied to @rpc(require_auth=True) without explicit limits.
RATE_LIMIT_MAX_BUCKETS 10000 Cap on tracked rate-limit buckets.
RATE_LIMIT_CLEANUP_INTERVAL 60 Seconds between bucket sweeps.
CASPIAN_ROOT auto-detected Explicit project root override for config and file-index lookup.

APP_ENV is the widest switch in the package: it gates the session secret check, the HSTS header, how much error detail reaches the client, and the localhost origin bypass above. Applications typically extend it to their own cookie and transport settings. Leaving it unset in development is what produces a working-looking app whose every RPC call returns 403 Invalid origin.

OAuth providers read GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI, GITHUB_CLIENT_ID, and GITHUB_CLIENT_SECRET.

Auth Policy

casp.auth provides the framework runtime for sessions, route checks, decorators, CSRF helpers, and OAuth providers. Application auth policy should live in an app-owned src/lib/auth/auth_config.py file, where the app builds AuthSettings and applies them during startup with configure_auth(...).

Keep route privacy, redirects, and RBAC policy in that app config file instead of changing casp.auth.

Main Modules

Module Purpose
casp.layout Load, compile, and render pages and nested layouts with native Jinja and parser-based <slot /> replacement
casp.html_native BeautifulSoup-backed fragment parsing helpers used by layout and component transforms
casp.component_decorator @component, html() for single-file components, render_html() for sibling templates, and component loading
casp.components_compiler Parse @import directives and transform <x-...> component tags
casp.html_attrs Attribute rendering, prop alias normalization, and Tailwind class merge helpers
casp.scripts_type Rewrite authored PulsePoint scripts for browser runtime execution
casp.rpc RPC decorator, registration, serialization, request gates (origin, content type, CSRF, auth, roles, rate limit), and request handling helpers
casp.streaming Server-Sent Events helpers including SSE
casp.auth Auth settings, session helpers, decorators, OAuth providers, and route checks
casp.runtime_security is_production_environment() fail-closed APP_ENV resolution, safe public-file serving, security headers, and production secret checks
casp.cache_handler Page cache helpers
casp.state_manager Request-scoped and session-backed state helpers
casp.validate Validation and sanitization helpers for strings, IDs, files, dates, and numbers
casp.caspian_config Config loading and file index helpers
casp.loading Route and loading.html file discovery
casp.string_helpers Case conversion between component names and <x-...> tags

Dependencies

This package declares no install_requires, so pip install caspian-utils installs casp alone and pins nothing. The following packages are expected to be present in the host application's environment, where a Caspian project installs and pins them:

Package Required by
fastapi casp.rpc, casp.auth, casp.runtime_security
jinja2 and markupsafe casp.layout, casp.component_decorator, casp.html_attrs
beautifulsoup4 casp.html_native, casp.components_compiler
slowapi casp.rpc
python-multipart multipart RPC payloads and file uploads, through FastAPI
httpx2 casp.auth OAuth provider calls
cuid2 and python-ulid casp.validate

Importing casp in an environment missing any of these raises ImportError at import time.

Repository

TheSteelNinjaCode/caspian_utils

License

MIT

Author

Jefferson Abraham

Release files for caspian-utils 0.4.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for caspian-utils 0.4.4
File Size Uploaded
caspian_utils-0.4.4.tar.gz 69.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for caspian-utils 0.4.4
File Interpreter ABI Platform
caspian_utils-0.4.4-py3-none-any.whl Python 3 none any Details

Total release size:139.6 kB

Release files / caspian_utils-0.4.4.tar.gz

Download URL caspian_utils-0.4.4.tar.gz
Size 69.6 kB
Tags Source
SHA-256 checksum
How to use checksums
b34f2de425385a6de06a4a17506ab72e9a91c2dd8ae7918413f8cc3971c8205e
BLAKE2b-256 checksum
How to use checksums
d1718112cd2a27f9e142fb27bef761d2966c9ebe55ef219314c1091dae838925
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release files / caspian_utils-0.4.4-py3-none-any.whl

Download URL caspian_utils-0.4.4-py3-none-any.whl
Size 70.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
85bf421dd3dafc66ef22ce3ad453e356b5e80b8634d5c9936f0606b199fdb627
BLAKE2b-256 checksum
How to use checksums
dadd3b8238f73d688cea68cc1e5de45f96e3c11bd7d05277a4de67827433523d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release history Release notifications | RSS feed

0.5.1

2 release files

0.5.0

2 release files

0.4.26

2 release files

0.4.25

2 release files

0.4.24

2 release files

0.4.23

2 release files

0.4.22

2 release files

0.4.13

2 release files

0.4.12

2 release files

0.4.11

2 release files

0.4.10

2 release files

0.4.9

2 release files

0.4.8

2 release files

0.4.7

2 release files

0.4.6

2 release files

0.4.5

2 release files

This release

0.4.4 This release

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.17

2 release files

0.3.16

2 release files

0.3.15

2 release files

0.3.14

2 release files

0.3.13

2 release files

0.3.12

2 release files

0.3.11

2 release files

0.3.10

2 release files

0.3.9

2 release files

0.3.8

2 release files

0.3.7

2 release files

0.3.6

2 release files

0.3.5

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.17

2 release files

0.2.16

2 release files

0.2.15

2 release files

0.2.14

2 release files

0.2.13

2 release files

0.2.12

2 release files

0.2.9

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.0

2 release files

0.0.35

2 release files

0.0.34

2 release files

0.0.32

2 release files

0.0.31

2 release files

0.0.30

2 release files

0.0.29

2 release files

0.0.28

2 release files

0.0.27

2 release files

0.0.26

2 release files

0.0.25

2 release files

0.0.24

2 release files

0.0.23

2 release files

0.0.22

2 release files

0.0.21

2 release files

0.0.20

2 release files

0.0.19

2 release files

0.0.18

2 release files

0.0.17

2 release files

0.0.16

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page