dashpacks
Biblioteca compartilhada pelas stacks DashFy, DashTube, DashTime, DashHealth, DashBook e DashDog — e por todas as ferramentas Dash que vierem. Serão catorze.
Por que existe
As quatro stacks nasceram de um monorepo e mantinham uma cópia da camada
common cada uma. A medição de 2026-09-08 mostrou o resultado: dos 34 arquivos
dessa camada, 19 eram byte a byte idênticos nas quatro, e outros 9 divergiam
apenas porque uma stack ficara para trás.
As cópias não evoluíram em direções diferentes. Ficaram paradas em versões diferentes do mesmo código — e três defeitos vieram daí, todos na mesma semana:
- a correlação por
request_idnão chegava ao log nas quatro stacks - as regras de exclusão de chave estrangeira divergiram com o modelo idêntico
- a redefinição administrativa de senha foi portada três vezes à mão
A decisão e o levantamento estão no ADR 0003 do DashTube.
O que entra aqui
A regra é não depender do que fica nas stacks. Ser idêntico nas quatro não basta: três serviços eram idênticos e não puderam vir nesta fase, porque importam os modelos de usuário.
Hoje:
| módulo | o que faz |
|---|---|
dashpacks.observability.context |
request_id e job_id em ContextVar |
dashpacks.observability.logging |
formato JSON com os identificadores |
dashpacks.observability.middleware |
fixa o request_id por requisição |
dashpacks.observability.job_metrics |
métricas dos trabalhos de fundo |
dashpacks.services.jobs |
fila serializada de trabalhos |
dashpacks.services.job_registry |
registro dos trabalhos disponíveis |
dashpacks.services.password_policy |
validação de força de senha |
Como usar
No requirements.txt da stack, com versão fixa:
dashpacks @ git+ssh://git@github.com/rikemorais/dashpacks@v0.1.0
A versão é fixada por tag de propósito: nenhuma stack sobe sozinha, e a atualização passa por PR e CI em cada uma. É o que impede uma versão ruim de alcançar as quatro de uma vez.
from dashpacks.observability.logging import configure_logging
from dashpacks.observability.middleware import RequestContextMiddleware
# Depois das migrações, não antes: o fileConfig do Alembic reinstala o
# formatador da raiz, e o efeito é silencioso.
configure_logging(json=settings.log_json, level=settings.log_level)
configure_logging recebe a configuração por parâmetro em vez de importar um
settings. São quatro aplicações com configurações próprias, e ler a de uma
delas aqui dentro amarraria as quatro ao mesmo objeto.
O pacote de frontend
O mesmo repositório traz um pacote npm, em js/, com o que é comum ao frontend
das quatro stacks. A regra de entrada é a mesma: não pode depender do que fica
nas stacks, e ser idêntico nas quatro não basta.
| módulo | o que faz |
|---|---|
usePolling |
consulta periódica com cancelamento |
PollingController |
o controlador por trás dela, testável sem React |
onSessionExpired |
avisa a aplicação quando a sessão cai |
flagOf |
bandeira do país a partir do código |
titleCasePt |
capitalização que respeita as preposições do português |
O que é tema, cor e marca de cada produto não mora aqui: entra por propriedade.
npm install @rikemorais/dashpacks@0.3.4
Do registro público do npm, e não do GitHub. O npm 12 desabilitou por padrão
as duas alternativas — allow-git e allow-remote vêm como "none" —, e
instalar da release ou do repositório recusa com EALLOWREMOTE e EALLOWGIT.
Ligar essas opções resolveria, e foi descartado: elas valem para todas as dependências do projeto, não só para esta, e existem para barrar dependência de origem não verificada.
O GitHub Packages seria a outra saída, e reintroduz o problema que o lado Python evitou: exige token de autenticação nas quatro CIs e na máquina local, mesmo para pacote público.
Uma stack nova
A lista abaixo não é sugestão. Cada item está aqui porque a falta dele já custou alguma coisa em pelo menos uma stack.
- Consumir a
@rikemorais/dashpacksno frontend e adashpacksno backend -
LoginPageda biblioteca, com oidda ferramenta — nunca uma cópia. A tela de login já existiu em seis cópias, separadas por uma linha: o nome do produto -
LancadorDeAplicativos,MarcaDaFerramentaeAcoesDeContano cabeçalho, com Configurações e Perfil no mesmo lugar das outras -
ConfiguracoesPagecom a aba Aparência - Entrada em
js/src/lib/ferramentas.ts: id igual ao subdomínio, ícone do lucide e cor base - Ícones gerados por
scripts/gerar-icones.mjs, e o favicon embutido como data URI — o Cloudflare Access devolve 302 no lugar da imagem quando o navegador pede o favicon sem sessão - Tokens de cor completos no CSS:
--bg,--text,--surface,--surface2,--border,--muted,--accente--danger. Faltando algum, o componente compartilhado renderiza sem cor de destaque - Rotas de conta no backend:
/auth/password,/auth/totp/newe/auth/totp/confirm, e trocar senha ou autenticador derruba as outras sessões - Orçamento de desempenho ligado ao
npm run build, e verificado apertando o limite de propósito — não por leitura -
.github/dependabot.ymlcomnpmem/frontend(diário) egithub-actionsna raiz (semanal) - Teste que afirma o valor da chave do
localStoragedo tema e da cor base - Toda mudança de estado gravando evento em UTC, se o domínio tiver estado que muda — ver a seção abaixo
Duas armadilhas que já pegaram
O Dependabot lê a branch padrão. Configuração mergeada em dev não vale
enquanto a main não a tiver. Já aconteceu: o arquivo estava lá, mergeado, e o
Dependabot não rodava em quatro das seis stacks.
A chave do localStorage não pode mudar de valor. Ao adotar o tema
compartilhado, é tentador padronizar a chave. Quem já escolheu um tema perde a
escolha em silêncio na primeira visita depois do deploy. O DashDog continua com
dashdog-theme, com hífen, por isso.
Data e hora de toda mudança de estado
Regra da plataforma, decidida no ADR 0005. Vale para qualquer stack cujo domínio mude de estado.
Toda mudança que o usuário provoca vira um evento, gravado em UTC, ao lado do estado corrente.
O que motivou: no DashFy, o updated_at era sobrescrito a cada alteração e a
nota anterior deixava de existir. Uma faixa podia ser cinco estrelas hoje e
quatro amanhã, e essa mudança sumia. Remover a nota apagava a linha, levando
junto o fato de que houve nota um dia.
Como fazer
- O estado corrente continua onde está. A tabela que a tela lê não muda: ler o estado atual não pode custar uma varredura de histórico.
- Uma tabela de eventos por domínio, só de inserção. Sem
UPDATEe semDELETE: um evento corrigido depois é outro evento. - Cada evento carrega o tipo da ação. Sem ele, ações diferentes sobre o mesmo registro ficam indistinguíveis — no DashFy, favoritar e reavaliar produziriam linhas idênticas.
- Remover um valor também é evento, com o campo nulo.
- Eventos bloqueiam a exclusão do usuário, como o estado que descrevem. Se a nota impede a exclusão, a história dela não pode sumir sozinha.
O tempo
DateTime(timezone=True) e datetime.now(UTC). Nada de horário local
persistido — ele é irreversível, porque não dá para recuperar o instante que
representava depois que o fuso muda.
A API não converte: devolve o instante com o deslocamento explícito. O servidor não sabe onde quem lê está.
Séries temporais não são agregadas no servidor. Um evento das 23h em UTC
pode ser das 20h do mesmo dia em São Paulo, e o agrupamento por dia mudaria de
balde conforme o fuso. Quem sabe o fuso é a tela — use diaDoInstante daqui.
O que a biblioteca oferece
| símbolo | para quê |
|---|---|
criarFuso(chave) |
a escolha do usuário, por ferramenta e por navegador |
formatarInstante(iso, fuso) |
escrever um instante UTC no fuso de exibição |
diaDoInstante(iso, fuso) |
o dia AAAA-MM-DD para agrupar séries na tela |
<Aparencia fuso={fuso} /> |
o seletor, na aba Aparência das Configurações |
Ao adotar numa stack que já tem dados
O histórico começa quando a tabela de eventos passa a existir: evento não se
inventa para trás. Onde houver updated_at, ele pode semear um primeiro
evento — desde que o tipo diga que é semente. Ele representa a última mudança,
não todas, e ler o histórico achando que é completo desde o começo seria pior
que não tê-lo.
Permissões, vindas do DashPass
Regra do ADR 0007: o DashPass administra quem acessa o quê, e publica um documento assinado. Cada stack busca esse documento e guarda a última versão válida.
from dashpacks.acesso import LeitorDePermissoes
permissoes = LeitorDePermissoes(
url_do_documento="https://dashpass.rikemorais.com/api/permissoes",
url_das_chaves="https://dashpass.rikemorais.com/api/permissoes/chaves",
ferramenta="dashfy",
)
if not permissoes.pode_entrar(email):
raise HTTPException(403, "Sem acesso a esta ferramenta.")
Requer o extra: pip install dashpacks[acesso].
Três propriedades, e por que elas importam
- Nenhuma requisição consulta o DashPass. A cópia local responde; o intervalo padrão é de cinco minutos.
- O DashPass fora do ar não tira acesso de ninguém. A última cópia continua valendo, e a falha é registrada em vez de virar recusa.
- Uma stack que nunca leu o documento recusa. Liberar por falta de informação é o modo de falha perigoso.
O custo declarado: revogar não é instantâneo. Vale a partir da próxima leitura, e o intervalo é o tamanho dessa janela.
Versão
Os dois pacotes compartilham a versão do repositório. Uma tag vX.Y.Z
publica os dois, mesmo que só um tenha mudado.
Uma consequência aprendida na prática: uma versão publicada e depois
despublicada no npm fica queimada para sempre — o registro recusa publicar
sobre ela com You cannot publish over the previously published versions. Foi o
que aconteceu com a 0.2.0, e a saída foi subir para a 0.3.0. Não há como
reaproveitar o número.
A alternativa seria versionar cada um por conta própria, com tags py- e js-.
Foi descartada porque dobra o que é preciso lembrar na hora de publicar, e o
custo de subir um pacote que não mudou é zero: quem consome fixa a versão e só
atualiza quando quiser.
Consequência prática: a v0.2.0 traz o pacote npm novo e o Python sem
mudança de código desde a v0.1.0. Uma stack que só usa o Python não tem
motivo para atualizar.
Publicar uma versão
git tag -a v0.2.1 -m "v0.2.1" && git push origin v0.2.1
Depois, anexar os dois artefatos à release: a wheel do Python (python -m build --wheel) e o tarball do npm (npm pack --workspace js).
Depois, subir a versão no requirements.txt de cada stack, por PR. O atraso de
uma stack fica visível no arquivo, em vez de invisível na cópia.
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 dashpacks-0.8.0.tar.gz.
File metadata
- Download URL: dashpacks-0.8.0.tar.gz
- Upload date:
- Size: 31.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a6ffc99f1fc06887b39085d588d2550c3f60cf1a40d3fec61a03643435495dd
|
|
| MD5 |
2162e594dd19dcfd98e8ccd5e8352517
|
|
| BLAKE2b-256 |
643a684e188e98431c9d831a4440be59e61158aea27adbc5caa5a0c01b6942bb
|
File details
Details for the file dashpacks-0.8.0-py3-none-any.whl.
File metadata
- Download URL: dashpacks-0.8.0-py3-none-any.whl
- Upload date:
- Size: 21.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aac4f2e8480705fdc90da5a6d1f360f2ddfd84515780524617ab7fb390a74195
|
|
| MD5 |
3ae175a67d5518cb89ffae69c9419430
|
|
| BLAKE2b-256 |
bc06ae92251eb56a8f38c7d2079ce6606ce2eb165873b131ac692c4a77fe9d74
|