Skip to main content

UI components as typed Python Objects

Project description

Pysty

Pysty is a Python‑first UI component system for building modern web interfaces using FastAPI, Pydantic, Dominate, TailwindCSS, and optional HTMX — without writing JavaScript.

Pysty components are:

  • Strongly typed (Pydantic models)
  • Server‑rendered HTML
  • HTMX‑ready by default
  • Explicit and composable

This document shows how to use Pysty, render components, and build a documentation site using FastAPI.


Installation

pip install pysty

or

uv add pysty

Core Idea

Pysty components are Python objects that:

  1. Accept configuration via typed fields
  2. Compile into HTML using render()
  3. Can optionally include HTMX attributes

No client‑side framework is required.


Minimal Example

import pysty as ps

card = ps.DefaultCard(
    title="Hello",
    content="This is a Pysty card",
)

html = card.render()

render() returns a safe HTML string that can be returned directly from FastAPI.


Using Pysty with FastAPI

Below is a complete example showing how to build a small documentation site using Pysty.

File: fastapi_demo.py

"""Example FastAPI application Pysty components.

"""

from __future__ import annotations

import random
from fastapi import FastAPI
from fastapi.responses import HTMLResponse

import pysty as ps

app = FastAPI()


def page(content: str) -> str:
    return f"""
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Pysty Demo</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <script src="https://cdn.tailwindcss.com"></script>
        <script src="https://unpkg.com/htmx.org@1.9.10"></script>
    </head>
    <body class="bg-gray-50 text-gray-900">
        <div class="max-w-6xl mx-auto px-6 py-16">
            {content}
        </div>
    </body>
    </html>
    """


def doc_section(title: str, description: str, preview: str, code: str) -> str:
    return f"""
    <section class="mb-24">
        <h2 class="text-3xl font-bold mb-2">{title}</h2>
        <p class="text-gray-600 mb-8 max-w-3xl">{description}</p>
        <div class="bg-white border border-gray-200 rounded-xl p-10 mb-8">
            {preview}
        </div>
        <pre class="bg-slate-900 text-slate-100 text-sm rounded-xl p-6 overflow-x-auto">{code}</pre>
    </section>
    """


@app.get("/", response_class=HTMLResponse)
def home() -> HTMLResponse:
    content = (
        """
        <h1 class="text-4xl font-bold text-center mb-3">Pysty Components</h1>
        <p class="text-gray-600 text-center mb-20 max-w-2xl mx-auto">
            A Python-first UI component system built around explicit configuration,
            strong typing, and composable server-driven interaction.
        </p>
        """
        + cards_section()
        + button_section()
    )
    return HTMLResponse(page(content))


# --------------------
# Cards Section
# --------------------

def cards_section() -> str:
    static_card = ps.DefaultCard(
        title="Static Card",
        content="A simple card with a title and description.",
    ).render()

    interactive_card = ps.DefaultCard(
        title="Interactive Card",
        content="Click this card to refresh.",
        hx_get="/card/refresh",
        hx_trigger="click",
        hx_swap="outerHTML",
    ).render()

    preview = f"""
    <div class="flex flex-wrap gap-6">
        {static_card}
        {interactive_card}
    </div>
    """

    code = """ps.DefaultCard(
    title="Interactive Card",
    content="Click this card to refresh.",
    hx_get="/card/refresh",
    hx_trigger="click",
    hx_swap="outerHTML",
)
"""

    return doc_section(
        "Cards",
        "Cards can be static or interactive without changing their API.",
        preview,
        code,
    )


@app.get("/card/refresh", response_class=HTMLResponse)
def refresh_card() -> str:
    return ps.DefaultCard(
        title="Updated!",
        content=f"Random value: {random.randint(1, 100)}",
        hx_get="/card/refresh",
        hx_trigger="click",
        hx_swap="outerHTML",
    ).render()


# --------------------
# Buttons Section
# --------------------

def button_section() -> str:
    preview = f"""
    <div class="flex flex-wrap gap-3 mb-6">
        {ps.DefaultButton(label="Default").render()}
        {ps.DefaultButton(label="Success", variant="success").render()}
        {ps.DefaultButton(label="Warning", variant="warning").render()}
        {ps.DefaultButton(label="Danger", variant="danger").render()}
        {ps.DefaultButton(label="Ghost", variant="ghost").render()}
    </div>
    <div class="flex flex-wrap gap-3">
        {ps.DefaultButton(
            label="Gradient",
            variant="bg-gradient-to-r from-purple-500 to-pink-500 text-white",
        ).render()}
        {ps.DefaultButton(
            label="Outline",
            variant="bg-transparent border-2 border-blue-600 text-blue-600",
        ).render()}
    </div>
    """

    code = """ps.DefaultButton(label="Success", variant="success")

ps.DefaultButton(
    label="Gradient",
    variant="bg-gradient-to-r from-purple-500 to-pink-500 text-white",
)
"""

    return doc_section(
        "Buttons",
        "Buttons support semantic presets or fully custom Tailwind classes.",
        preview,
        code,
    )

HTMX Support (Optional)

Pysty does not require HTMX, but supports it natively.

You can pass any hx_* attribute directly to a component:

ps.DefaultCard(
    title="Live",
    content="Click to refresh",
    hx_get="/refresh",
    hx_trigger="click",
    hx_swap="outerHTML",
)

Pysty treats HTMX attributes as first‑class configuration, not string hacks.


Design Philosophy

  • Python is the source of truth
  • No client‑side state framework
  • Explicit configuration over magic
  • Tailwind classes stay visible
  • Components are data, not templates

Roadmap (Intentional)

  • More base components (Table, Navbar, Modal)
  • Theme abstraction layer
  • JSON / schema export for tooling
  • Optional state helpers

Summary

If you know FastAPI + Python, you already know Pysty.

No JSX. No build step. No framework lock‑in.

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

pysty-0.1.1.tar.gz (229.3 kB view details)

Uploaded Source

Built Distribution

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

pysty-0.1.1-py3-none-any.whl (12.4 kB view details)

Uploaded Python 3

File details

Details for the file pysty-0.1.1.tar.gz.

File metadata

  • Download URL: pysty-0.1.1.tar.gz
  • Upload date:
  • Size: 229.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.17

File hashes

Hashes for pysty-0.1.1.tar.gz
Algorithm Hash digest
SHA256 d77e694cd31b2113c070339f72f39f8ea289ec6b26805e10cb8b142e278cc6c9
MD5 6ffa75eeddedb7100499eb54477ff42a
BLAKE2b-256 18073a551466fe15b00abcccb123c4e58575327c1c414e5d5847708a1b1fe452

See more details on using hashes here.

File details

Details for the file pysty-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: pysty-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 12.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.17

File hashes

Hashes for pysty-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 acfbe6b1f5a4440fa92f2c1c01dd1130753523d7e25a0891bf17dbad607b1340
MD5 74f2a6323ba7126df07ba053ef422b11
BLAKE2b-256 3fc936f66062152d7c1bbeb439a9ece2a29e50ef00e2ed920de59a2a7286b0ce

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