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.4.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.4-py3-none-any.whl (63.0 kB view details)

Uploaded Python 3

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

Hashes for tupyx-0.1.4.tar.gz
Algorithm Hash digest
SHA256 85f4d9c7b0664359cd30f8ccb87abdf85228f7ba4b9d30215043df0b16aa568c
MD5 368665cc69a46503bab22ae11953fb41
BLAKE2b-256 ef8010aae31eaae4b67edc989bb4cbc728457ccb414bcf5d4827df8aeafa9e25

See more details on using hashes here.

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

Hashes for tupyx-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 1c4d83364c781112e3007a4bc5c50e25624f774e52e3c1ea1f97d27f43672b10
MD5 5db07ce9114b671e9100d9d8420825e2
BLAKE2b-256 d9f18d8b8a817c54c9cb0a361c0c913287f2580672b4facbf41d07f2e1e86ef9

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