Python Web Utilities

Nitro

A collection of Python web-dev utils inspired by smart solutions across the ecosystem. Use them together or separately, with your favourite framework.

Layer 0

Templating

You just write Python functions. They return HTML. Powered by Datastar for interactivity. Inspired by FastHTML.

templating.py
from nitro.html.components import *  # noqa: F403
from nitro.html import template as templ
from your.components import Sidebar, Navbar

# Shared page template - define headers, footers, body and html kwargs and more
htmlkws = dict(lang="en")
page = page_template(htmlkw=htmlkws, lucide=True)


@templ
def template(content, title: str):
    return page(
        Fragment(
            Sidebar(),
            Main(
                Navbar(),
                Div(
                    Div(content, id="content"),
                    cls="p-4 md:p-6 xl:p-12",
                ),
            ),
        ),
        title=title,
        # wrap_in= HTMLResponse, or your framework's HTML response class
    )
my_page.py
from templates import template
# Than use it like this:
@template
def index():
    return Div(
        H1("Hello, World!"),
        P("This is a template."),
    )
Layer 1

HTML Generation

You just write Python functions. They return HTML. Powered by Rust for speed. Inspired by FastHTML.

Output

What the browser sees

Jane Doe

Software Engineer

Building things with Nitro. It's just Python. That's the whole pitch.

PythonFastAPINo JS
user_card.py
from rusty_tags import Div, H3, P
from nitro.html.components import Avatar, Badge

def UserCard(user):
    return Div(
        Div(
            Avatar(fallback=user.initials, size="lg"),
            Div(
                H3(user.name, cls="font-semibold"),
                P(user.role, cls="text-sm text-muted-foreground"),
            ),
            cls="flex items-center"
        ),
        P(user.bio, cls="mt-4"),
        Div(
            *[Badge(tag) for tag in user.tags],
            cls="flex gap-2 mt-4"
        ),
    )

# That's it. No JSX. No templates. Just Python.
Layer 2

Reactivity

You just add signals. Powered by Datastar. 10kb. Does what React does. No npm install.

counter.py
from rusty_tags import Div, Button, Span
from rusty_tags.datastar import Signals

def Counter():
    sigs = Signals(count=0)

    return Div(
        Button("-", on_click=sigs.count.sub(1)),
        Span(data_text=sigs.count, cls="text-4xl font-bold mx-8"),
        Button("+", on_click=sigs.count.add(1)),
        data_signals=sigs,
    )

# Datastar handles the reactivity.
# No virtual DOM. No hydration. No build step.

Live Counter

State lives in HTML. UI updates automatically.

Click the buttons. The count updates. No useState, no Redux, no tears.

Layer 3

Rich Entities

You just define your model. Business logic lives in methods. Persistence is automatic. Built on SQLModel.

Order #1234

Created 2 minutes ago

placed
CustomerAcme Corp
Total$1,234.00
Items3 products
order.py
from nitro.domain.entities.base_entity import Entity
from pydantic import Field

class Order(Entity, table=True):
    customer: str = Field(title="Customer")
    total: float = Field(default=0.0, title="Total")
    status: str = Field(default="draft", title="Status")

    def add_item(self, product, qty):
        self.total += product.price * qty
        self.save()  # Just works

    def place(self):
        if self.total == 0:
            raise ValueError("Empty order")
        self.status = "placed"
        self.save()

# order = Order(customer="Acme Corp")
# order.add_item(widget, 3)
# order.place()
#
# That's your business logic. In one place.
Layer 4

Event Routing

You just decorate handlers. Side effects stay decoupled. Built on Blinker with async support and priority ordering.

handlers.py
from nitro.events import event, on, emit

# Create or get a named event
order_placed = event('order-placed')

# Handlers are just decorated functions

@on(order_placed)
def send_confirmation(sender, **kwargs):
    EmailService.send(
        sender.customer_email,
        "Your order is confirmed!"
    )

@on(order_placed)
def update_inventory(sender, **kwargs):
    for item in sender.items:
        Product.get(item.product_id).reduce_stock(item.qty)

@on(order_placed)
def notify_warehouse(sender, **kwargs):
    WarehouseQueue.push(sender.id)

# Decoupled. Testable. No spaghetti.

Event Flow

What happens when an order is placed

order.place()

Fires 'order-placed' event

send_confirmation()
update_inventory()
notify_warehouse()
Bonus

Model Views

You just pass your Entity. Forms, tables, cards generated automatically. Field metadata controls everything.

Auto-Generated UI

One Entity, multiple views

class User(Entity)

Define once with Field metadata

ModelTable(User, data=users)
ModelCard(User, instance=user)
ModelForm(User)
views.py
from nitro.domain.entities.base_entity import Entity
from nitro.html.components.model_views import (
    ModelForm, ModelTable, ModelCard
)
from pydantic import Field

class User(Entity, table=True):
    name: str = Field(title="Name")
    email: str = Field(
        title="Email",
        json_schema_extra={'format': 'email'}
    )
    role: str = Field(
        default="member",
        json_schema_extra={'icon': 'user'}
    )

# Generate a complete form
form = ModelForm(User)

# Generate a data table with sorting
table = ModelTable(User, data=User.all())

# Generate a display card
card = ModelCard(User, instance=user)

# That's < 20 lines for a complete CRUD interface.

That's It

Four layers. Pure Python. No magic, just patterns that work.

Use it if it helps. Fork it if you want. File issues if it breaks.

pip install nitro-boost