A Python library for building reactive web dashboards 100% in Python
Project description
TUPYX - Toolkit for Unified Python eXperiences
Framework Python para criar dashboards e aplicações web reativos 100% em Python — sem JavaScript, sem HTML manual, sem templates.
Acesse o repositório no github
Instalação
pip install tupyx
Com suporte a pandas:
pip install tupyx[pandas]
Início rápido
tupyx init meu-dashboard
cd meu-dashboard
tupyx run app.py
Abra http://localhost:8000 no browser. O servidor tem hot-reload — salve o arquivo e a página atualiza automaticamente.
Índice
- Instalação
- Início rápido
- Índice
- Estrutura do projeto
- Tags HTML
- Estilos com .sx()
- Pseudo-estilos
- Eventos
- Reactive
- Roteamento com @page
- NavLink
- Input e Select reativos
- Medidas com @measure
- Gráficos
- CLI
- Licença
Estrutura do projeto
meu-dashboard/
├── app.py ← ponto de entrada (define o app e chama run)
├── pages/
│ ├── home.py ← páginas com @page("/rota")
│ └── dash.py
└── pyproject.toml
app.py mínimo:
from tupyx.ui import *
from tupyx.server import run
def app():
return Html(
Head(Title("Meu App")),
Body(Router()).sx(margin="0", background="#f8fafc"),
)
run(app)
Tags HTML
Todos os elementos HTML estão disponíveis como funções Python:
from tupyx.ui import *
# Uso básico: Tag(filhos, atributos)
Div("conteúdo")
P("Parágrafo de texto")
H1("Título principal")
Span("texto inline")
A("Link", href="https://exemplo.com")
Img(src="/logo.png", alt="Logo")
Button("Clique aqui")
# Aninhamento
Div(
H1("Título"),
P("Subtítulo"),
Button("Ação"),
)
# Atributos HTML
A("Documentação", href="/docs", target="_blank")
Img(src="/foto.jpg", alt="Foto", width="200")
Tags disponíveis:
| Categoria | Tags |
|---|---|
| Layout | Div, Span, P, A, Img, Button, Form, Label, Textarea |
| Títulos | H1, H2, H3, H4, H5, H6 |
| Listas | Ul, Ol, Li |
| Semântica | Header, Footer, Main, Section, Article, Nav, Aside |
| Tabelas | Table, Thead, Tbody, Tfoot, Tr, Th, Td, Caption |
| Mídia | Video, Audio, Source, Canvas, Svg |
| Documento | Html, Head, Body, Title, Meta, Link, Script, Style |
| Inline | Strong, Em, Small, Br, Hr, Code, Pre |
| Formulário | Input, Select, Option |
| Navegação | NavLink |
Estilos com .sx()
.sx() aplica CSS inline usando Python snake_case (convertido automaticamente para kebab-case):
Div("Olá").sx(
background="#f0f7ff",
border_radius="12px",
padding="16px 24px",
font_size="14px",
font_weight="600",
color="#0f172a",
display="flex",
align_items="center",
gap="8px",
box_shadow="0 2px 8px rgba(0,0,0,0.08)",
)
Encadeamento:
Button("Salvar").sx(
background="#2563eb",
color="white",
border="none",
border_radius="8px",
padding="10px 20px",
cursor="pointer",
font_size="14px",
)
Pseudo-estilos
Aplique estilos para :hover, :focus e :active:
Button("Hover aqui").sx(
background="#2563eb",
color="white",
padding="10px 20px",
border_radius="8px",
border="none",
cursor="pointer",
).on_hover(
background="#1d4ed8", # escurece no hover
transform="translateY(-1px)",
)
Input(type="text").sx(
border="1px solid #dbeafe",
border_radius="8px",
padding="8px 12px",
outline="none",
).on_focus(
border="1px solid #2563eb",
box_shadow="0 0 0 3px rgba(37,99,235,0.15)",
)
Eventos
Vincule qualquer função Python a eventos do browser:
def home():
def ao_clicar():
print("Clicou!")
def ao_digitar():
pass # o estado já é atualizado automaticamente
return Div(
Button("Clique").on_click(ao_clicar),
Button("Duplo clique").on_dblclick(ao_clicar),
Input(type="text").on_input(ao_digitar),
Input(type="text").on_change(ao_digitar),
Div("Hover").on_mouseover(ao_clicar),
)
Eventos disponíveis:
| Método | Quando dispara |
|---|---|
.on_click(fn) |
clique simples |
.on_dblclick(fn) |
duplo clique |
.on_mouseover(fn) |
mouse entra |
.on_mouseout(fn) |
mouse sai |
.on_mouseenter(fn) |
mouse entra (sem propagar para filhos) |
.on_mouseleave(fn) |
mouse sai (sem propagar para filhos) |
.on_keydown(fn) |
tecla pressionada |
.on_keyup(fn) |
tecla solta |
.on_input(fn) |
a cada caractere digitado |
.on_change(fn) |
valor muda e perde foco |
.on_submit(fn) |
formulário enviado |
.on_focus_event(fn) |
elemento recebe foco |
.on_blur(fn) |
elemento perde foco |
.on_js(evento, js) |
JS puro sem passar pelo servidor |
Reactive
Reactive cria uma variável de estado que persiste entre renders:
@page("/")
def home():
contador = Reactive(0) # valor inicial: 0
nome = Reactive("mundo") # string
itens = Reactive([]) # lista
def incrementar():
contador.update(lambda v: v + 1)
def zerar():
contador.set(0)
return Div(
H1("Contador: ", contador), # exibe o valor automaticamente
P(f"Nome: {nome.get()}"), # lê o valor
Button("+1").on_click(incrementar),
Button("Zerar").on_click(zerar),
)
Métodos:
r = Reactive(10)
r.get() # → 10
r.set(20) # → define 20
r.update(lambda v: v + 5) # → aplica função ao valor atual
r.subscribe(lambda v: print(v))# → callback chamado em toda atualização
str(r) # → "20" (para usar em strings)
r() # → 20 (equivale a .get())
Roteamento com @page
from tupyx.ui import *
from tupyx.server import run
@page("/", redirect=True) # redirect=True → redireciona "/" para esta rota
def home():
return Div(H1("Home"))
@page("/sobre")
def sobre():
return Div(H1("Sobre"))
@page("/produto/{id}") # parâmetro dinâmico na URL
def produto(id):
return Div(H1(f"Produto #{id}"))
def app():
return Html(
Head(Title("Meu App")),
Body(Router()),
)
run(app)
Arquivos em pages/ são descobertos automaticamente — basta criar o arquivo com @page(...).
NavLink
Navegação entre rotas sem recarregar a página (atualiza apenas o conteúdo):
NavLink("Ir para o dashboard", to="/dashboard")
# Com estilo
NavLink("Dashboard", to="/dashboard").sx(
color="#2563eb",
text_decoration="none",
font_weight="500",
)
# Com aparência de botão
NavLink("Ver relatório", to="/relatorio").sx(
background="#2563eb",
color="white",
padding="8px 16px",
border_radius="8px",
text_decoration="none",
display="inline-block",
)
# Barra de navegação completa
Nav(
NavLink("Home", to="/").sx(color="#0f172a", text_decoration="none"),
NavLink("Dashboard", to="/dash").sx(color="#0f172a", text_decoration="none"),
NavLink("Relatórios", to="/relatorios").sx(color="#0f172a", text_decoration="none"),
).sx(display="flex", gap="24px", padding="16px 32px", border_bottom="1px solid #dbeafe")
O botão voltar/avançar do browser funciona corretamente com NavLink.
Input e Select reativos
Use .bind(reactive) para sincronização bidirecional automática:
Input
@page("/")
def home():
nome = Reactive("tupyx")
return Div(
Input(type="text", placeholder="Digite seu nome").bind(nome),
P("Olá, ", nome), # atualiza automaticamente ao digitar
)
Select
@page("/")
def home():
ano = Reactive("2024")
return Div(
Select(
Option("2024", value="2024"),
Option("2023", value="2023"),
Option("2022", value="2022"),
).bind(ano),
P(f"Mostrando dados de {ano.get()}"),
)
@page("/")
def home():
ano = Reactive("2024")
anos = [2024, 2025, 2026]
return Div(
Select(
anos
).bind(ano),
P(f"Mostrando dados de {ano.get()}"),
)
Medidas com @measure
Transforma funções em KPI cards reativos:
dados = [
{"ano": "2024", "val": 320},
{"ano": "2024", "val": 480},
{"ano": "2023", "val": 180},
]
@page("/")
def home():
ano = Reactive("2024")
def filtrado():
return [d for d in dados if d["ano"] == ano.get()]
@measure(label="Total", prefix="R$ ", color="blue")
def total():
return sum(d["val"] for d in filtrado())
@measure(label="Média", prefix="R$ ", color="green")
def media():
d = filtrado()
return round(sum(i["val"] for i in d) / len(d)) if d else 0
@measure(label="Melhor", suffix=" pts", color="purple")
def melhor():
d = filtrado()
return max(i["val"] for i in d) if d else 0
return Div(
Select(
Option("2024", value="2024"),
Option("2023", value="2023"),
).bind(ano),
Div(total, media, melhor).sx(display="flex", gap="16px"),
)
Parâmetros:
| Parâmetro | Descrição | Padrão |
|---|---|---|
label |
título do card | "" |
prefix |
texto antes do valor (ex: "R$ ") |
"" |
suffix |
texto depois do valor (ex: " kg") |
"" |
color |
cor: "blue", "green", "yellow", "red", "purple", "gray" |
"blue" |
Usar a medida em expressões:
total() # valor numérico puro
BarChart(y=total) # passar para gráfico
Gráficos
Todos os gráficos aceitam:
- Listas Python diretamente
@measure(MeasureCard)- Callables (funções que retornam dados)
pandas.DataFrameepandas.Series(requerpip install tupyx[pandas])
Opções comuns via .sx():
chart.sx(width="100%") # largura relativa
chart.sx(width="600px") # largura fixa
chart.sx(height="300px") # altura
chart.sx(hide_tooltip=True) # desativa tooltip
chart.sx(hide_legend=True) # desativa legenda
chart.sx(legend_position="top") # "top", "bottom", "left", "right"
chart.sx(legend_align="start") # "start", "center", "end"
BarChart
Gráfico de barras vertical. Suporta múltiplas séries.
# Série única
BarChart(
y=[320, 480, 210, 590, 430],
x=["Jan", "Fev", "Mar", "Abr", "Mai"],
title="Vendas Mensais",
subtitle="Ano 2024",
)
# Múltiplas séries
BarChart(
y=[[320, 480, 210], [180, 260, 310]],
x=["Jan", "Fev", "Mar"],
legends=["2024", "2023"],
title="Comparativo",
).sx(width="100%")
# Com medida reativa
BarChart(y=minha_medida, x=meses(), title="Vendas").sx(width="100%")
Opções específicas via .sx():
.sx(hide_x=True) # oculta labels do eixo X
.sx(hide_y=True) # oculta labels do eixo Y
.sx(hide_grid_y=True) # oculta linhas de grade
.sx(show_label=True) # exibe valor em cima de cada barra
.sx(hide_crosshair=True) # oculta linha vertical de hover
.sx(bar_radius=8) # border-radius das barras (padrão: 4)
.sx(padding_left=60) # margem interna esquerda
LineChart
Gráfico de linha. Suporta múltiplas séries e área preenchida.
# Série única
LineChart(
y=[100, 200, 150, 300, 250],
x=["Jan", "Fev", "Mar", "Abr", "Mai"],
title="Crescimento",
)
# Múltiplas séries
LineChart(
y=[[100, 200, 150], [80, 160, 200]],
x=["Jan", "Fev", "Mar"],
legends=["Receita", "Custo"],
).sx(width="100%")
Opções específicas via .sx():
.sx(hide_x=True) # oculta labels do eixo X
.sx(hide_y=True) # oculta labels do eixo Y
.sx(hide_points=True) # oculta os pontos na linha
.sx(hide_crosshair=True) # oculta crosshair
.sx(show_grid_x=True) # exibe grade vertical
.sx(hide_grid_y=True) # oculta grade horizontal
.sx(show_label=True) # exibe valor em cada ponto
.sx(fill_area=True) # preenche área abaixo da linha
.sx(label_position="bottom") # posição do label: "top" ou "bottom"
AreaChart
Igual ao LineChart mas com área preenchida por padrão. Ideal para tendências.
AreaChart(
y=[100, 200, 150, 300, 250, 400],
x=["Jan", "Fev", "Mar", "Abr", "Mai", "Jun"],
title="Evolução de Receita",
).sx(width="100%")
# Múltiplas séries sobrepostas
AreaChart(
y=[[100, 200, 150], [80, 160, 130]],
x=["Jan", "Fev", "Mar"],
legends=["Receita", "Custo"],
).sx(width="100%")
Opções específicas via .sx():
.sx(fill_opacity=0.2) # opacidade da área (padrão: 0.10)
.sx(hide_points=True) # oculta pontos
.sx(show_label=True) # exibe valores
.sx(show_grid_x=True) # grade vertical
HorizontalBarChart
Barras horizontais. Ideal para rankings e comparações com labels longos.
HorizontalBarChart(
y=[480, 390, 310, 260, 180],
labels=["Produto A", "Produto B", "Produto C", "Produto D", "Produto E"],
title="Top Produtos",
).sx(width="100%")
Opções específicas via .sx():
.sx(show_label=True) # exibe valor ao lado de cada barra
.sx(show_grid=False) # oculta grade vertical
ScatterChart
Gráfico de dispersão (scatter) e bolhas (bubble).
# Scatter simples — lista de (x, y)
ScatterChart(
data=[(1, 2), (3, 4), (5, 1), (7, 8), (2, 6)],
title="Dispersão",
)
# Múltiplas séries
ScatterChart(
data=[
[(1, 2), (3, 4), (5, 1)], # série A
[(2, 5), (4, 2), (6, 7)], # série B
],
legends=["Grupo A", "Grupo B"],
).sx(width="100%")
# Bubble chart — (x, y, tamanho)
ScatterChart(
data=[(1, 2, 10), (3, 4, 30), (5, 1, 20), (7, 8, 50)],
title="Bolhas",
)
Opções específicas via .sx():
.sx(hide_x=True) # oculta eixo X
.sx(hide_y=True) # oculta eixo Y
.sx(hide_grid_x=True) # oculta grade vertical
.sx(hide_grid_y=True) # oculta grade horizontal
.sx(point_r=8) # raio dos pontos (padrão: 5)
.sx(bubble_max=40) # raio máximo das bolhas (padrão: 28)
.sx(show_labels=True) # exibe labels nos pontos
DonutChart
Gráfico de rosca. Ideal para proporções e partes de um todo.
DonutChart(
data=[320, 480, 210, 150],
labels=["Produto A", "Produto B", "Produto C", "Produto D"],
title="Distribuição de Vendas",
).sx(width="400px")
Opções específicas via .sx():
.sx(inner_radius=0.4) # tamanho do buraco (0 = pizza, 1 = só borda)
.sx(show_label=True) # exibe labels dentro das fatias
.sx(hide_legend=True) # oculta legenda
PieChart
Gráfico de pizza (igual ao DonutChart sem buraco).
PieChart(
data=[40, 30, 20, 10],
labels=["Norte", "Sul", "Leste", "Oeste"],
title="Vendas por Região",
).sx(width="400px")
TableChart
Tabela estilo BI com hover, formatação numérica automática e suporte a pandas.
# Lista de dicts — colunas extraídas automaticamente
TableChart(
data=[
{"Mês": "Jan", "Vendas": 320, "Meta": 300},
{"Mês": "Fev", "Vendas": 480, "Meta": 400},
{"Mês": "Mar", "Vendas": 210, "Meta": 350},
],
title="Resumo Mensal",
).sx(width="100%")
# Lista de listas com colunas explícitas
TableChart(
data=[[320, 300], [480, 400], [210, 350]],
columns=["Vendas", "Meta"],
title="Resultado",
).sx(width="100%")
# Com pandas DataFrame
import pandas as pd
df = pd.read_csv("vendas.csv")
TableChart(data=df, title="Vendas").sx(width="100%")
# Com medida reativa
@measure()
def tabela():
return [d for d in dados if d["ano"] == ano.get()]
TableChart(data=tabela, title="Tabela Reativa").sx(width="100%")
Colunas numéricas são detectadas automaticamente e recebem alinhamento à direita, fonte monoespaçada e formatação com separador de milhar.
CLI
# Criar novo projeto
tupyx init meu-dashboard
# Iniciar servidor com hot-reload
tupyx run app.py
# Porta customizada (no app.py)
run(app, port=3000)
Licença
MIT © GuiEspim18
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tupyx-0.1.4.tar.gz.
File metadata
- Download URL: tupyx-0.1.4.tar.gz
- Upload date:
- Size: 55.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85f4d9c7b0664359cd30f8ccb87abdf85228f7ba4b9d30215043df0b16aa568c
|
|
| MD5 |
368665cc69a46503bab22ae11953fb41
|
|
| BLAKE2b-256 |
ef8010aae31eaae4b67edc989bb4cbc728457ccb414bcf5d4827df8aeafa9e25
|
File details
Details for the file tupyx-0.1.4-py3-none-any.whl.
File metadata
- Download URL: tupyx-0.1.4-py3-none-any.whl
- Upload date:
- Size: 63.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c4d83364c781112e3007a4bc5c50e25624f774e52e3c1ea1f97d27f43672b10
|
|
| MD5 |
5db07ce9114b671e9100d9d8420825e2
|
|
| BLAKE2b-256 |
d9f18d8b8a817c54c9cb0a361c0c913287f2580672b4facbf41d07f2e1e86ef9
|