Skip to main content

A minimal React-like Python UI framework on FastAPI (CSR runtime + component model).

Project description

Xania — Python UI Framework for SPAs

Build fast, interactive web apps entirely in Python. No JavaScript required.

Xania compiles Python code to standalone Single-Page Applications (SPAs) that run 100% in the browser.

Features

  • Write UI in Python – VDOM elements, components, state management
  • Compile to JavaScript – Your Python spec → index.html + app.js
  • Deploy anywhere – Static SPA with no server needed
  • Production-ready – Built on FastAPI with auth, rate limiting, CSRF protection
  • Zero client-side code – Framework handles all routing, state, and rendering

Quick Start

Requirements

  • Python 3.12+

1. Create a new project

pip install xania
xania init my_website
cd my_website

2. Edit your site

Edit app.py to add pages and content:

from xania import SpaApp, StaticPage, Div, H1, Button

app = SpaApp(name="MySite")

app.route(
    "/",
    StaticPage(
        title="Home",
        html='<h1>Welcome!</h1><p>Built with Xania</p>'
    )
)

3. Compile

python app.py
# Generates: index.html + static/app.js + static/spa_runtime.js

4. Run

xania serve .
# 🚀 Serving SPA from /path/to/my_website
# 📍 http://127.0.0.1:8000

Open your browser to http://127.0.0.1:8000

5. Deploy

Copy the generated files to any static host:

# Upload to Netlify, Vercel, GitHub Pages, S3, etc.
cp -r . /path/to/deployment/

How It Works

SPA Mode (Recommended)

  1. Define your app in Python – Use SpaApp with routes and pages
  2. Compile – Xania generates JavaScript that runs entirely in the browser
  3. Deploy – Copy index.html + static/ to any static host

No server needed for the SPA itself. Client-side routing via History API.

from xania import SpaApp, StaticPage

app = SpaApp(name="MySite")
app.route("/", StaticPage(title="Home", html="..."))
app.route("/about", StaticPage(title="About", html="..."))

# Then: python app.py  →  generates index.html + app.js

Server Mode (Advanced)

If you need a backend, integrate with FastAPI:

from fastapi import FastAPI
from xania import mount_spa

app = FastAPI()

# Add API routes
@app.get("/api/data")
def get_data():
    return {"items": [...]}

# Mount compiled SPA
mount_spa(app, ".")

Then: xania serve . or uvicorn app:app


Two Modes Explained

SPA Mode (Default)

  • HTML shell + JavaScript SPA
  • Client-side routing (History API)
  • No server needed for UI
  • Perfect for: static sites, blogs, documentation, dashboards
  • Deploy to: Netlify, Vercel, GitHub Pages, S3

Server Mode (Optional)

  • FastAPI backend + SPA frontend
  • Add custom APIs, auth, data
  • Still compiles to static files
  • Perfect for: full-stack apps with complex backends
  • Deploy to: Heroku, Railway, AWS, DigitalOcean

Core Concepts

VDOM Elements – Python classes representing HTML:

from xania import Div, H1, Button, Input

Div(
    H1("Welcome"),
    Input(type="text", placeholder="Enter name"),
    Button("Click me", onclick="App.dispatch('Counter', 'click')"),
    class_name="flex flex-col gap-4"
)

Components – Stateful UI units:

from xania import Component, Div, Span, Button

class Counter(Component):
    def initial_state(self):
        return {"count": 0}
    
    def render(self, state):
        return Div(
            Span(str(state.count)),
            Button("+", onclick="App.dispatch('Counter', 'inc')")
        )
    
    def on_inc(self, state, payload):
        state.count += 1

Pages – Routes in your SPA:

from xania import SpaApp, StaticPage, TemplatePage, JsExpr

app = SpaApp()

# Static page
app.route("/", StaticPage(
    title="Home",
    html="<h1>Welcome</h1>"
))

# Template page with placeholders
app.route("/user/{id}", TemplatePage(
    title="User",
    template="<h1>User {name}</h1>",
    placeholders={"name": "John"}
))

Project Structure

Core Modules:

  • renderer/ – VDOM elements, components, state management
  • runtime/ – SPA compiler (Python → JavaScript)
  • web/ – FastAPI integration, serving, auth
  • static/ – Browser runtime (spa_runtime.js)
  • cli.py – Command-line interface

See also: documentation/ for full tutorial SPA built with Xania


Examples

Counter with State

from xania import Component, Div, Button, Span

class Counter(Component):
    def initial_state(self):
        return {"count": 0}
    
    def render(self, state):
        return Div(
            Span(f"Count: {state.count}"),
            Button("+", onclick="App.dispatch('Counter', 'inc')"),
            Button("-", onclick="App.dispatch('Counter', 'dec')"),
        )
    
    def on_inc(self, state, payload):
        state.count += 1
    
    def on_dec(self, state, payload):
        state.count -= 1

Multi-Page SPA

from xania import SpaApp, StaticPage, TemplatePage

app = SpaApp(name="MyBlog")

# Home page
app.route("/", StaticPage(
    title="Home",
    html="<h1>Welcome to My Blog</h1>"
))

# About page
app.route("/about", StaticPage(
    title="About",
    html="<h1>About Me</h1><p>I build with Xania.</p>"
))

# Post page with placeholders
app.route("/post/{id}", TemplatePage(
    title="Post",
    template="<h1>{title}</h1><p>{content}</p>",
    placeholders={"title": "My First Post", "content": "..."}
))

Deployment

Option 1: Static Hosting (Simplest)

# Compile
python app.py

# Upload everything to your host
# - Netlify (drag & drop)
# - Vercel (git integration)
# - GitHub Pages
# - S3 + CloudFront

Option 2: FastAPI Server

# serve.py
from fastapi import FastAPI
from xania import mount_spa

app = FastAPI()

@app.get("/api/hello")
def hello():
    return {"message": "Hello from API!"}

mount_spa(app, ".")
# Local: uvicorn serve:app --reload
# Production: gunicorn serve:app (on Heroku, Railway, etc.)

Development

Clone and install:

git clone https://github.com/xania/framework.git
cd xania
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

Run examples:

xania serve documentation      # Full tutorial
xania dev                      # Dev server

Contributing

PRs welcome! See GitHub

  • DOM events calling App.dispatch('Counter', 'increment')

Tags / Elements (VDOM)

Xania’s “tags” are Python functions/classes that build a VDOM tree in renderer/elements.py.

  • Use built-in tag helpers: Div(...), Span(...), Button(...), H1(...), etc.
  • Use Element(tag, ...) for any HTML tag (even if there is no helper yet):
from renderer.elements import Element

node = Element("section", "Hello")

Children

  • Text children: strings (they are HTML-escaped by engine/serializer.py)
  • Element children: nested Element instances
  • Lists: spread them with *items when building children

Attributes (“props”)

Attributes are passed as keyword args:

  • class_nameclass
  • for_for
  • http_equivhttp-equiv
  • Any _ becomes - (e.g. data_component="Counter"data-component="Counter")
  • Booleans:
    • disabled=True renders disabled
    • disabled=False omits it

Example:

from renderer.elements import Button

Button(
    "Click",
    class_name="px-3 py-2 rounded",
    data_component="Counter",
    disabled=False,
)

Registering components

In app.py:

from renderer.registry import ComponentRegistry
from example.counter import Counter

ComponentRegistry.register("Counter", Counter(id="counter"))

The string "Counter" must match what the client dispatches:

onclick="App.dispatch('Counter','increment')"

Web API

GET /

Returns the HTML shell (mount points + <script src="/static/runtime.js">).

POST /event

Request (web/schemas.py):

{
  "component": "Counter",
  "action": "increment",
  "payload": {}
}

Response:

{
  "updates": [
    { "id": "counter", "html": "<div>...</div>" }
  ]
}

The client applies updates by doing document.getElementById(id).innerHTML = html.

Publish readiness (honest checklist)

This codebase is a solid architectural baseline, but it is not ready to publish as a production framework yet. Here’s what’s missing / risky:

  • Session/user isolation: ComponentRegistry stores singletons in-process.
    • In production you need per-user (cookie/session) component instances or a state store.
  • Concurrency & scaling:
    • Multiple workers/processes will not share component state.
    • Need a storage layer (Redis/DB) or stateless model (client-owned state + patches).
  • Security:
    • onclick="App.dispatch(...)" is safe here because HTML is generated server-side, but any user-provided text/attrs must be carefully validated.
    • Consider CSRF protection for /event.
    • Demo auth exists for stress-testing, but production needs strong secrets + HTTPS-only cookies + real user storage.
  • Performance:
    • Current updates replace innerHTML of the whole component.
    • Next step: diffing + patching (you already have a base architecture for it).
  • Testing:
    • No unit tests or integration tests yet.
  • Packaging polish:
    • Ensure pyproject.toml metadata is correct (name/urls/authors).
    • Add a changelog and decide versioning strategy.

Roadmap (recommended next steps)

  • Add per-session registry/state store (cookie -> component state)
  • Add diff/patch engine (update minimal DOM, not full innerHTML)
  • Add router + multipage CSR navigation
  • Add test suite + CI
  • Add docs site and examples gallery

Publish checklist (minimal)

  • Package identity: confirm pyproject.toml name/version/description are correct.
  • License: ensure LICENSE exists and matches pyproject.toml (MIT here).
  • Versioning: adopt SemVer (0.x while APIs are changing quickly).
  • README: keep Quick start + minimal example runnable.
  • Security: decide on CSRF/auth strategy for /event before public deploys.
  • State model: decide per-user state persistence (sessions/Redis) before scaling.
  • CI: add at least python -m py_compile + basic tests on every push.

Demo auth (stress testing only)

This repo includes a minimal cookie-session auth demo used by the SPA stress page:

  • Login endpoint: POST /api/auth/login (demo user: admin/admin)
  • Logout endpoint: POST /api/auth/logout
  • Current user: GET /api/auth/me
  • Protected stress endpoints: GET /api/private/big-json, POST /api/private/write-echo

Environment variables:

  • XANIA_ENV=dev|prod (default dev)
  • XANIA_SECRET_KEY=... (required when XANIA_ENV is not dev)
  • XANIA_COOKIE_SECURE=true|false (defaults to true outside dev)

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

xania-0.1.5.tar.gz (50.4 kB view details)

Uploaded Source

Built Distribution

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

xania-0.1.5-py3-none-any.whl (53.9 kB view details)

Uploaded Python 3

File details

Details for the file xania-0.1.5.tar.gz.

File metadata

  • Download URL: xania-0.1.5.tar.gz
  • Upload date:
  • Size: 50.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xania-0.1.5.tar.gz
Algorithm Hash digest
SHA256 acdefe28ecb1d4d879d167496983e329968bf9212567017c13f5bdebbd765bfa
MD5 93cad1ba5c64b63aa717e62c96a556fe
BLAKE2b-256 928fe8a499bbe2daf81857fea909755e08e0149643cf263628b73edf87ccf83e

See more details on using hashes here.

File details

Details for the file xania-0.1.5-py3-none-any.whl.

File metadata

  • Download URL: xania-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 53.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for xania-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 730c6bd197fb9a4b2d9f0bd52b31d79835bf78e5005af92b2a46f8680ede863c
MD5 1e5d306b0d00bd48c188872246dccd68
BLAKE2b-256 a177be02a63688551b17d8a9076f7ae618d9e0d4f7386819049aa27b02376b76

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