Data applications¶
Render common Python objects with core Auto, then install the data extra for
tabular DataTable / DataEditor. Charts are Alpha — see
Charts and HTMX.
Auto (core — no extra)¶
from hedron import Auto, Hedron, Page, Stack
app = Hedron(title="Data", security="standard", session_secret="replace-me")
@app.page("/")
def home() -> Page:
return Page(
Stack(
Auto({"name": "Ada", "role": "admin"}),
Auto([1, 2, 3]),
),
title="Auto",
)
Auto picks an inspectable renderer; override or register renderers when you need
control (Auto API).
In-memory DataTable (requires hedron[data])¶
Try it (simulated)¶
Filter chips swap the declared table region. Docs simulation.
| ID | Name | Role |
|---|---|---|
| 1 | Ada | admin |
| 2 | Grace | member |
| 3 | Katherine | admin |
| 4 | Margaret | member |
Filter swaps the declared table region — same pattern as DataTable HTMX.
Minimal runnable app.py that reproduces this demo (real Hedron, not the docs simulator):
import os
from hedron import Hedron, Page, Stack, html, swap
app = Hedron(
title="People",
security="standard",
explorer="off",
session_secret=os.environ.get("HEDRON_SESSION_SECRET", "dev-only"),
)
table = app.region("people-table", description="People table")
ROWS = (
("1", "Ada", "admin"),
("2", "Grace", "member"),
("3", "Katherine", "admin"),
("4", "Margaret", "member"),
)
def table_panel(filter_role: str | None = None):
filtered = [r for r in ROWS if filter_role is None or r[2] == filter_role]
label = "All people" if filter_role is None else f"Role: {filter_role}"
return html.div(
html.strong(label),
html.table(
html.thead(html.tr(html.th("ID"), html.th("Name"), html.th("Role"))),
html.tbody(*[html.tr(html.td(a), html.td(b), html.td(c)) for a, b, c in filtered]),
),
id=table.id,
)
@app.page("/")
def home() -> Page:
return Page(
Stack(
table_panel(),
html.button(
"All",
type="button",
**{"hx-get": "/rows", "hx-target": table.selector, "hx-swap": "outerHTML"},
),
html.button(
"Admins",
type="button",
**{"hx-get": "/rows/admin", "hx-target": table.selector, "hx-swap": "outerHTML"},
),
html.button(
"Members",
type="button",
**{"hx-get": "/rows/member", "hx-target": table.selector, "hx-swap": "outerHTML"},
),
),
title="People",
)
@app.fragment("/rows", region=table)
def all_rows():
return swap(table_panel())
@app.fragment("/rows/admin", region=table)
def admin_rows():
return swap(table_panel("admin"))
@app.fragment("/rows/member", region=table)
def member_rows():
return swap(table_panel("member"))
from hedron import Hedron, Page
from hedron_data import Column, DataTable
app = Hedron(title="Table", security="standard", session_secret="replace-in-production")
@app.page("/")
def home() -> Page:
return Page(
DataTable(
rows=(
{"id": "1", "name": "Ada"},
{"id": "2", "name": "Grace"},
),
columns=(Column("id", label="ID"), Column("name", label="Name")),
),
title="People",
)
InMemoryDataSource (re-exported from hedron when hedron[data] is installed) wraps
row sequences behind the same DataQuery / DataPage protocol used by editors:
from hedron import InMemoryDataSource
from hedron_data import Column, DataEditor, DataQuery
source = InMemoryDataSource(
rows=[{"id": "1", "name": "Ada"}, {"id": "2", "name": "Grace"}],
key_field="id",
)
page = source.fetch(DataQuery(page=1, page_size=25))
SQLAlchemy / SQLModel (app-owned sessions)¶
Install the SQLAlchemy extra. Your app owns the engine, sessions, and transactions — Hedron does not invent an ORM lifecycle.
Minimal read-only table over a SQLAlchemy 2.x Select (paging uses SQL OFFSET/LIMIT;
sort / filters / search on DataQuery are not translated yet and raise
HED-DATA-0012 if set):
from collections.abc import Iterator
from contextlib import contextmanager
from fastapi import Depends
from sqlalchemy import create_engine, select
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, sessionmaker
from hedron import Hedron, Page
from hedron_data import Column, DataTable
from hedron_data.sqlalchemy_source import SQLAlchemyDataSource
engine = create_engine("sqlite:///./demo.db", future=True)
SessionLocal = sessionmaker(bind=engine, expire_on_commit=False, future=True)
class Base(DeclarativeBase):
pass
class Person(Base):
__tablename__ = "people"
id: Mapped[str] = mapped_column(primary_key=True)
name: Mapped[str]
@contextmanager
def session_scope() -> Iterator[Session]:
session = SessionLocal()
try:
yield session
finally:
session.close()
def get_people_source() -> SQLAlchemyDataSource[dict[str, str]]:
return SQLAlchemyDataSource(
session_factory=SessionLocal,
statement=select(Person),
row_key="id",
to_row=lambda row: {"id": row.id, "name": row.name},
)
app = Hedron(title="People DB", security="standard", session_secret="replace-in-production")
@app.page("/")
def home(source: SQLAlchemyDataSource[dict[str, str]] = Depends(get_people_source)) -> Page:
from hedron_data import DataQuery
page = source.fetch(DataQuery(page=1, page_size=25))
return Page(
DataTable(
page=page,
columns=(Column("id", label="ID"), Column("name", label="Name")),
),
title="People",
)
For editable grids, supply apply_changes on SQLAlchemyDataSource (or an
InMemoryDataSource / custom DataEditorSource) and wire CSRF-backed saves as in
Data and the reference app.
Charts¶
Install hedron[charts] (Alpha — pin and expect churn) and follow
Charts and HTMX.
See also¶
- Data API · Data sources
- What’s ready (Supported vs Alpha)