Skip to main content

A Python library for building reactive web dashboards 100% in Python

Project description

tupyx logo

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

PyPI version Python versions License


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


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.DataFrame e pandas.Series (requer pip 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

tupyx-0.1.3.tar.gz (55.9 kB view details)

Uploaded Source

Built Distribution

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

tupyx-0.1.3-py3-none-any.whl (63.0 kB view details)

Uploaded Python 3

File details

Details for the file tupyx-0.1.3.tar.gz.

File metadata

  • Download URL: tupyx-0.1.3.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

Hashes for tupyx-0.1.3.tar.gz
Algorithm Hash digest
SHA256 13f78c0da94215ed158b3ab808d7cdb6995b5266192236b04933b1a27d1ebbf0
MD5 5ade109e23a52cad8c907efe5f72c21b
BLAKE2b-256 9b6842ea31735f0c1bd6c0ee21d3adfa264dda5a13d4c2ff2bc76a6032a43738

See more details on using hashes here.

File details

Details for the file tupyx-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: tupyx-0.1.3-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

Hashes for tupyx-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 dc164d0bd8c47b10b83c674101fc05fca31f10137a4d5e2e3cc795a3cd9ec4b1
MD5 44cbd353d13680627ef9961b85db72f5
BLAKE2b-256 c691d892d0bd28ac56596ae5523f986fa065abdf184381f278aec396d1966f8c

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