mongo_bakery: o fim da factory que você escrevia pra cada Document do MongoEngine

mongo_bakery: o fim da factory que você escrevia pra cada Document do MongoEngine

O teste queria garantir uma coisa só: cancelar um pedido pendente devolve 200. Pra chegar até essa asserção, ele teve que passar por isso primeiro: ...

16 de agosto de 2026 · 7 min · 1437 words · Riverfount
asyncio.TaskGroup: quando gather não é suficiente

asyncio.TaskGroup: quando gather não é suficiente

Um endpoint de checkout dispara três operações concorrentes com asyncio.gather: cobra o cartão, reserva o estoque, envia a notificação de confirmação do pedido. O serviço de pagamento cai no meio da chamada e lança uma exceção. gather propaga esse erro imediatamente para quem está aguardando. O handler captura a exceção, devolve um 500 para o cliente, registra a falha no log. Para quem está de plantão, o sistema reagiu do jeito certo. ...

12 de agosto de 2026 · 7 min · 1351 words · Riverfount
Protocol: duck typing com garantias em tempo de análise

Protocol: duck typing com garantias em tempo de análise

Você herdou um sistema de cálculo de descontos. Não tem classe base, não tem ABC, não tem interface formal nenhuma. Cada tipo de desconto (cupom, fidelidade, campanha sazonal) é só uma classe qualquer com um método aplicar(pedido) que devolve o valor final. O código que orquestra isso nem sabe que tipo de objeto está recebendo, só chama desconto.aplicar(pedido) e segue em frente. Duck typing raiz: se anda como pato e grasna como pato, aplica desconto como pato. ...

22 de julho de 2026 · 6 min · 1198 words · Riverfount
O bug que o mypy teria pego antes do deploy

O bug que o mypy teria pego antes do deploy

A função recebe um pedido vindo de uma integração externa, soma o valor de cada item e aplica um desconto. Em homologação, com os pedidos de teste, funciona perfeitamente. Em produção, na terceira semana, um cliente específico começa a receber o erro TypeError: unsupported operand type(s) for +: 'float' and 'str' em pleno checkout. 1 2 3 4 5 def calcular_total(itens: list) -> float: total = 0 for item in itens: total += item["preco"] * item["quantidade"] return total - item.get("desconto", 0) Note que a assinatura já tem uma anotação de tipo: itens: list. Ela não é falsa, mas é rasa o suficiente para não dizer nada de útil. list não revela que cada elemento é um dicionário com chaves específicas, nem que desconto deveria ser numérico. Essa anotação solta passa pelo code review sem levantar suspeita, porque parece tipagem, mas não carrega informação nenhuma sobre o formato real dos dados. É esse tipo de anotação incompleta, não a ausência total de anotação, que vai aparecer recorrentemente neste artigo como o verdadeiro adversário. ...

26 de junho de 2026 · 8 min · 1682 words · Riverfount
suas queries do SQLAlchemy podem ser cacheadas sem Redis manual

suas queries do SQLAlchemy podem ser cacheadas sem Redis manual

O pool está configurado. As queries têm índice. O lru_cache eliminou as buscas repetidas nos endpoints mais simples. Mesmo assim, um endpoint de relatório continua lento, não porque está mal escrito, mas porque ele é genuinamente caro: agrega dados de várias tabelas, cruza informações de três meses, e faz isso a cada requisição, mesmo que os dados subjacentes não mudem por horas. O lru_cache não resolve. Ele cacheia por argumentos exatos, e os filtros de data variam o suficiente para inviabilizar o hit rate. O Redis resolve, mas exige serializar o resultado manualmente, gerenciar a chave, decidir em que camada a invalidação acontece, código de infraestrutura espalhado pela camada de negócio. O que falta é uma abstração que entenda o ORM. ...

2 de junho de 2026 · 7 min · 1298 words · Riverfount
Por que a aplicação travava mesmo com queries rápidas

Por que a aplicação travava mesmo com queries rápidas

O profiling não encontrou nada. As queries estão com índice, rodam em menos de 5ms, e o cache eliminou as buscas repetidas. Mas sob carga — dez, vinte requisições simultâneas — a aplicação trava. Requisições acumulam na fila, o tempo de resposta explode, e o log mostra um erro que parece absurdo: TimeoutError: QueuePool limit of size 5 overflow 10 reached. O banco de dados não está sobrecarregado. As queries são rápidas. O problema está entre a aplicação e o banco — no pool de conexões que ninguém configurou. ...

5 de maio de 2026 · 9 min · 1717 words · Riverfount
Sua aplicação está buscando os mesmos dados várias vezes

Sua aplicação está buscando os mesmos dados várias vezes

O profiling apontou um endpoint lento. Você abre o relatório do cProfile, ordena por cumtime, e o topo está dominado por chamadas ao banco de dados. Antes de qualquer coisa: se o problema for N+1 queries, cache não é a solução — é um emplastro. N+1 se resolve na query, com joins, selectinload ou subqueries conforme o ORM. Depois disso, índices. Cache entra só se, após a query estar correta e os índices no lugar, a performance ainda não for suficiente. ...

17 de abril de 2026 · 8 min · 1678 words · Riverfount
Hypothesis encontra os bugs que seus testes ignoram

Hypothesis encontra os bugs que seus testes ignoram

A suite de testes está verde. Fixtures bem organizadas, parametrize cobrindo os casos óbvios, mocks isolando as dependências externas. Cobertura em 94%. O PR passa no CI e vai para produção. Três dias depois, um usuário reporta um comportamento estranho. Você reproduz o bug localmente com um input que nunca ocorreu a ninguém testar: uma string vazia em que se esperava pelo menos um caractere, um número negativo em que a função assumia valores positivos, uma lista com um único elemento no qual a lógica de comparação silenciosamente quebra. O teste que teria pego isso seria trivial de escrever — se alguém tivesse pensado em escrever. ...

14 de abril de 2026 · 8 min · 1637 words · Riverfount
dataclass, NamedTuple, attrs ou pydantic: qual usar de verdade?

dataclass, NamedTuple, attrs ou pydantic: qual usar de verdade?

Existe um ponto no crescimento de qualquer projeto Python em que os dicionários começam a doer. Não de vez — vai acontecendo aos poucos. Você passa um dict para uma função, a função passa para outra, e em algum momento ninguém mais sabe ao certo quais chaves estão garantidas, qual é o tipo de cada valor, ou o que acontece se uma chave estiver faltando. 1 2 3 4 def calcular_desconto(pedido: dict) -> float: # pedido tem "valor"? "valor_bruto"? "subtotal"? # "cliente" é um dict também? tem "nivel"? return pedido["valor"] * _fator(pedido["cliente"]["nivel"]) Funciona. Ninguém vai questionar em code review. O problema aparece três meses depois, quando alguém passa um pedido sem a chave "nivel" — ou quando você tenta debugar e o repr do dicionário tem quarenta chaves misturadas. ...

10 de abril de 2026 · 9 min · 1731 words · Riverfount
O .env que você não deveria ter commitado

O .env que você não deveria ter commitado

Existe uma busca no GitHub que retorna milhares de resultados úteis para um atacante: filename:.env DB_PASSWORD. Repositórios públicos com arquivos .env commitados por acidente, contendo senhas de banco, chaves de API, segredos JWT — tudo em texto claro, indexado, pesquisável. Não é incompetência. É o resultado natural de uma prática que parece razoável: colocar credenciais num arquivo, adicionar esse arquivo ao .gitignore, e confiar que o .gitignore vai proteger. Funciona até o dia que não funciona — um git add . no momento errado, um novo membro do time que clona o repo e cria o .env a partir do .env.example sem perceber que o exemplo já tem valores reais, ou um editor que cria arquivos temporários fora do padrão ignorado. ...

6 de abril de 2026 · 12 min · 2546 words · Riverfount