Dashboards y apps de datos
Pantallas llenas de números, tablas y listas: el tipo de herramienta interna, panel de administración o app multimedia que son muchos programas de escritorio. Los seis primeros son Bloques, pantallas completas escritas con la biblioteca de componentes que puedes copiar y adaptar; la página de cada bloque tiene su código completo y notas sobre qué cambiar. Cada captura sigue el tema de este sitio: cambia entre claro y oscuro arriba de la página para verlas en los dos.
Dashboard
Sección titulada «Dashboard»Un panel de administración: navegación lateral, tarjetas de KPI con tendencias, un gráfico de ingresos, fuentes de tráfico y una tabla de pedidos recientes.


- Las tarjetas de KPI son
ui.statcon undeltay una pequeña línea detrend. Los gráficos también son componentes listos:ui.bar_chartpara los ingresos (su rango es un controlui.segmented) yui.donutpara las fuentes de tráfico, con una barraui.progresspara el objetivo del mes. - La barra lateral cambia de página cambiando un atributo del estado: mira Una app con varias vistas.
uv run --with pyzed-ui python examples/blocks/dashboard.pypython3 -m venv .venvsource .venv/bin/activatepython -m pip install pyzed-uipython examples/blocks/dashboard.pypy -m venv .venv.venv\Scripts\Activate.ps1python -m pip install pyzed-uipython examples\blocks\dashboard.pypython3 examples/blocks/dashboard.pypy examples\blocks\dashboard.pyEl código: bloque Dashboard.
Tabla de datos
Sección titulada «Tabla de datos»Una tabla de miembros de un equipo con búsqueda, filtros por estado, columnas ordenables, selección de filas, páginas y una acción de exportar.


uv run --with pyzed-ui python examples/blocks/data_table.pypython3 -m venv .venvsource .venv/bin/activatepython -m pip install pyzed-uipython examples/blocks/data_table.pypy -m venv .venv.venv\Scripts\Activate.ps1python -m pip install pyzed-uipython examples\blocks\data_table.pypython3 examples/blocks/data_table.pypy examples\blocks\data_table.pyEl código: bloque Data table. Para aprender las partes una a una (buscar, ordenar, seleccionar, mostrar los detalles), sigue Listas y tablas con datos, que construye esta pequeña biblioteca de libros:

Tablero kanban
Sección titulada «Tablero kanban»Un tablero de proyecto con columnas, tarjetas con etiquetas, responsables, progreso y botones para mover las tarjetas.


uv run --with pyzed-ui python examples/blocks/kanban.pypython3 -m venv .venvsource .venv/bin/activatepython -m pip install pyzed-uipython examples/blocks/kanban.pypy -m venv .venv.venv\Scripts\Activate.ps1python -m pip install pyzed-uipython examples\blocks\kanban.pypython3 examples/blocks/kanban.pypy examples\blocks\kanban.pyEl código: bloque Kanban.
Chat de equipo
Sección titulada «Chat de equipo»Una mensajería con canales, mensajes directos, un hilo de mensajes y una caja para escribir.


uv run --with pyzed-ui python examples/blocks/chat.pypython3 -m venv .venvsource .venv/bin/activatepython -m pip install pyzed-uipython examples/blocks/chat.pypy -m venv .venv.venv\Scripts\Activate.ps1python -m pip install pyzed-uipython examples\blocks\chat.pypython3 examples/blocks/chat.pypy examples\blocks\chat.pyEl código: bloque Chat. Para un chat que recibe respuestas en streaming de un modelo de IA, mira el Chat con IA.
Reproductor musical
Sección titulada «Reproductor musical»Una barra lateral con la biblioteca, una página de álbum con su lista de canciones y una barra de reproducción cuyo progreso avanza mientras suena una canción.


uv run --with pyzed-ui python examples/blocks/music_player.pypython3 -m venv .venvsource .venv/bin/activatepython -m pip install pyzed-uipython examples/blocks/music_player.pypy -m venv .venv.venv\Scripts\Activate.ps1python -m pip install pyzed-uipython examples\blocks\music_player.pypython3 examples/blocks/music_player.pypy examples\blocks\music_player.pyEl código: bloque Music player.
Layout de IDE
Sección titulada «Layout de IDE»La disposición de un editor de código: un árbol de proyecto, pestañas con migas de pan, una terminal, un panel de estructura y una barra de estado.


uv run --with pyzed-ui python examples/blocks/file_explorer.pypython3 -m venv .venvsource .venv/bin/activatepython -m pip install pyzed-uipython examples/blocks/file_explorer.pypy -m venv .venv.venv\Scripts\Activate.ps1python -m pip install pyzed-uipython examples\blocks\file_explorer.pypython3 examples/blocks/file_explorer.pypy examples\blocks\file_explorer.pyEl código: bloque File explorer. Un editor que funciona con la misma idea es el Editor de proyectos.
Contactos sobre SQLite
Sección titulada «Contactos sobre SQLite»Una app de contactos sobre SQLAlchemy 2.0 con 20.000 filas en una tabla virtual.
La búsqueda y el orden corren como consultas SQL en un hilo de trabajo (Command.perform), así que la ventana
nunca espera a la base de datos.

cd examples/integrations/sqlalchemy_contactsuv run main.pycd examples/integrations/sqlalchemy_contactspython3 -m venv .venvsource .venv/bin/activatepython -m pip install pyzed-ui "sqlalchemy>=2.0"python main.pycd examples\integrations\sqlalchemy_contactspy -m venv .venv.venv\Scripts\Activate.ps1python -m pip install pyzed-ui "sqlalchemy>=2.0"python main.pycd examples/integrations/sqlalchemy_contactspython main.pyCódigo completo: db.py y main.py
"""The database: SQLAlchemy 2.0 models on SQLite, plus the queries the window runs."""
from __future__ import annotations
import randomfrom datetime import datetimefrom pathlib import Path
from sqlalchemy import String, create_engine, func, or_, selectfrom sqlalchemy.exc import IntegrityErrorfrom sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
DATABASE = Path(__file__).with_name("contacts.db")engine = create_engine(f"sqlite:///{DATABASE}")
class Base(DeclarativeBase): pass
class Contact(Base): __tablename__ = "contacts"
id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(80), index=True) email: Mapped[str] = mapped_column(String(120), unique=True) company: Mapped[str] = mapped_column(String(80), default="") city: Mapped[str] = mapped_column(String(60), default="") created: Mapped[datetime] = mapped_column(default=datetime.now)
# Sorting by a column title of the table is an ORDER BY on this column.COLUMNS = {"Name": Contact.name, "Email": Contact.email, "Company": Contact.company, "City": Contact.city}
def setup(rows: int = 20_000) -> None: """Creates the table and fills it with made-up contacts the first time.""" Base.metadata.create_all(engine) with Session(engine) as session: if session.scalar(select(func.count()).select_from(Contact)): return rng = random.Random(7) first = ["Ada", "Alan", "Grace", "Linus", "Guido", "Margaret", "Ken", "Barbara", "Dennis", "Radia", "Tim", "Frances", "Bjarne", "Anita", "John", "Hedy", "Yukihiro", "Sophie", "Brendan", "Katherine"] last = ["Lovelace", "Turing", "Hopper", "Torvalds", "van Rossum", "Hamilton", "Thompson", "Liskov", "Ritchie", "Perlman", "Berners-Lee", "Allen", "Stroustrup", "Borg", "McCarthy", "Lamarr", "Matsumoto", "Wilson", "Eich", "Johnson"] companies = ["Acme", "Globex", "Initech", "Umbrella", "Hooli", "Stark", "Wayne", "Tyrell", "Cyberdyne", "Aperture"] cities = ["Buenos Aires", "Madrid", "Mexico City", "Bogotá", "Lima", "Santiago", "Montevideo", "Barcelona", "Berlin", "Tokyo", "Toronto", "Lisbon"] weights = [12, 10, 9, 7, 6, 5, 4, 4, 3, 3, 2, 2] session.add_all( Contact(name=f"{rng.choice(first)} {rng.choice(last)}", email=f"user{i}@example.com", company=rng.choice(companies), city=rng.choices(cities, weights)[0]) for i in range(rows) ) session.commit()
def search(text: str, sort: str, descending: bool) -> list[Contact]: """Contacts whose name, email or company contains ``text``, sorted in SQL.""" column = COLUMNS[sort] query = select(Contact).order_by(column.desc() if descending else column, Contact.id) if text: pattern = f"%{text}%" query = query.where(or_(Contact.name.ilike(pattern), Contact.email.ilike(pattern), Contact.company.ilike(pattern))) with Session(engine) as session: return list(session.scalars(query)) # plain objects keep their loaded values after the session closes
def stats() -> dict: """Totals and the five cities with more contacts, for the header.""" with Session(engine) as session: total = session.scalar(select(func.count()).select_from(Contact)) companies = session.scalar(select(func.count(func.distinct(Contact.company)))) cities = session.execute(select(Contact.city, func.count()).group_by(Contact.city) .order_by(func.count().desc()).limit(5)).all() return {"total": total, "companies": companies, "cities": [(city, count) for city, count in cities]}
def save(contact_id: int | None, values: dict) -> Contact | dict: """Inserts or updates a contact. Returns it, or ``{field: message}`` when it can't be saved.""" errors = {} if not values["name"].strip(): errors["name"] = "A contact needs a name" if "@" not in values["email"]: errors["email"] = "Not an email address" if errors: return errors with Session(engine, expire_on_commit=False) as session: contact = session.get(Contact, contact_id) if contact_id else Contact() for key, value in values.items(): setattr(contact, key, value.strip()) session.add(contact) try: session.commit() except IntegrityError: return {"email": "Another contact already uses this email"} return contact
def delete(contact_id: int) -> None: with Session(engine) as session: contact = session.get(Contact, contact_id) if contact is not None: session.delete(contact) session.commit()"""A contacts manager on SQLAlchemy 2.0: 20,000 rows in a virtual table, searched and sorted in SQL.
uv run main.py
The database (contacts.db) is created next to this file on the first run.Walkthrough: https://albakore.github.io/pyzed/integrations/sqlalchemy/"""
from __future__ import annotations
import pyzed as pzfrom pyzed import Command, ui
import db
FIELDS = [("name", "Name", "Ada Lovelace"), ("email", "Email", "ada@example.com"), ("company", "Company", "Analytical Engines"), ("city", "City", "London")]
class Contacts(pz.App): title = "Contacts · SQLAlchemy" size = (1180, 720)
def __init__(self): self.rows: list[db.Contact] = [] self.stats = {"total": 0, "companies": 0, "cities": []} self.query = "" self.sort, self.descending = "Name", False self.loading = True self.editing = None # a Contact, "new" or None self.draft: dict[str, str] = {} self.errors: dict[str, str] = {}
# -- database work runs in a thread with Command.perform ------------------------------ def on_start(self): return Command.perform(db.setup, lambda _: self.reload())
def reload(self): self.loading = True query, sort, descending = self.query, self.sort, self.descending return [ Command.perform(lambda: db.search(query, sort, descending), lambda rows: self.loaded(query, rows)), Command.perform(db.stats, lambda stats: setattr(self, "stats", stats)), ]
def loaded(self, query: str, rows: list[db.Contact]): if query == self.query: # a newer search is on its way: keep waiting for it self.rows, self.loading = rows, False
def search(self, text: str): self.query = text return self.reload()
def sort_by(self, title: str): self.descending = not self.descending if title == self.sort else False self.sort = title return self.reload()
# -- the edit panel ------------------------------------------------------------------ def open(self, contact): self.editing, self.errors = contact, {} self.draft = {key: "" if contact == "new" else getattr(contact, key) for key, _, _ in FIELDS}
def save(self): contact_id = None if self.editing == "new" else self.editing.id values = dict(self.draft) return Command.perform(lambda: db.save(contact_id, values), self.saved)
def saved(self, result): if isinstance(result, dict): self.errors = result return None self.editing = None return self.reload()
def delete(self): contact_id = self.editing.id self.editing = None return Command.perform(lambda: db.delete(contact_id), lambda _: self.reload())
def on_key(self, combo): if combo == "escape": self.editing = None elif combo == "ctrl-n": self.open("new")
# -- view ---------------------------------------------------------------------------- def header(self): cities = self.stats["cities"] return pz.row([ ui.stat("Contacts", f"{self.stats['total']:,}"), ui.stat("Companies", self.stats["companies"]), ui.card(pz.column([ui.muted("Top cities", size="sm"), ui.bar_chart([count for _, count in cities], labels=[c.split()[0] for c, _ in cities], width=360, height=90)], spacing=6)), ], spacing=12)
def panel(self, t): if self.editing is None: return None rows = [ui.field(title, ui.input(self.draft[key], lambda v, k=key: self.draft.__setitem__(k, v), placeholder=hint, width=220), error=self.errors.get(key)) for key, title, hint in FIELDS] new = self.editing == "new" return pz.container( pz.column([ pz.row([ui.heading("New contact" if new else "Edit contact", level=3), pz.hspace(), ui.icon_button("x", lambda: setattr(self, "editing", None), tooltip="Close (Esc)")], align="center"), *rows, pz.row([None if new else ui.button("Delete", self.delete, icon="trash-2", variant="ghost", tint="error"), pz.hspace(), ui.button("Save", self.save, icon="check", variant="solid")], spacing=8), ], spacing=14), padding=16, width=420, height="fill", bg=t.panel, )
def view(self): t = self.resolved_theme() columns = [ ui.Column("Name", "name", width="fill:3"), ui.Column("Email", "email", width="fill:3"), ui.Column("Company", "company", width="fill:2"), ui.Column("City", "city", width="fill:2"), ] toolbar = pz.row([ ui.search_input(self.query, self.search, placeholder="Search by name, email or company…", width=360), ui.spinner() if self.loading else ui.muted(f"{len(self.rows):,} found", size="sm"), pz.hspace(), ui.button("New contact", lambda: self.open("new"), icon="user-plus", keys="ctrl-n"), ], spacing=10, align="center") # Rows come sorted from SQL; sort_by only draws the arrow (re-sorting a sorted list is cheap). table = ui.table(columns, self.rows, sort_by=self.sort, descending=self.descending, on_sort=self.sort_by, on_row_press=self.open, selected=self.editing, striped=True, height="fill") main = pz.column([self.header(), toolbar, table], spacing=14, padding=16) return pz.container(pz.row([main, self.panel(t)]), width="fill", height="fill", bg=t.editor)
if __name__ == "__main__": pz.run(Contacts)La explicación está en SQLAlchemy. Sin ninguna biblioteca, el tutorial
Conectar un backend construye una app de contactos más pequeña sobre el
módulo sqlite3 que viene con Python, con una clase repositorio, un spinner y errores amables:

- Datos en vivo: un panel de cotizaciones alimentado por un generador async.
- Centro de control de cristal: un dashboard con tarjetas de cristal esmerilado sobre un shader en movimiento.
- Integraciones: FastAPI, Django, SQLModel, httpx y más, cada una con una app de escritorio.