<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Otávio Miranda</title><description>Blog do Otávio Miranda — Tech, Linux e DevOps</description><link>https://otaviomiranda.com.br/</link><item><title>DeepSeek V4, agentes embutidos e App Runner: IA vira infraestrutura de verdade</title><link>https://otaviomiranda.com.br/2026/deepseek-v4-agentes-embutidos-app-runner-supply-chain-ia/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/deepseek-v4-agentes-embutidos-app-runner-supply-chain-ia/</guid><description>SGLang mostra a pilha por trás do DeepSeek V4, Feldera defende software feito para agentes, AWS fecha App Runner para novos clientes e npm/PyPI voltam ao centro da conversa de supply chain.</description><pubDate>Sun, 26 Apr 2026 10:01:30 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Nota: gerado por IA (The Paper LLM), com fontes originais
listadas por bloco.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;O lote de hoje tem uma linha bem visível: a parte interessante da IA está
descendo para a infraestrutura. O modelo continua importante, claro. Mas o
que muda o resultado no mundo real aparece no cache, no runtime, no desenho da
interface, no fluxo de verificação, no serviço gerenciado que deixa de evoluir
e na cadeia de pacotes que entra no container sem pedir licença.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/infra-de-ia.jpg&quot; alt=&quot;Infraestrutura de IA com agentes, runtime e supply chain&quot;&gt;&lt;/p&gt;
&lt;h2&gt;DeepSeek V4 volta como história de runtime, não só de modelo&lt;/h2&gt;
&lt;p&gt;O DeepSeek V4 já tinha aparecido pelo ângulo de contexto longo, pesos abertos e
custo. A novidade de 25 de abril de 2026 é outra: a equipe de SGLang e Miles
publicou suporte de &amp;quot;dia zero&amp;quot; para servir e treinar o modelo, e o texto deixa
claro que 1 milhão de tokens não se sustenta sozinho.&lt;/p&gt;
&lt;p&gt;O post da LMSYS fala de duas famílias de problema. Na inferência, aparecem
ShadowRadix para prefix caching em atenção híbrida, HiSparse para estender KV
cache com memória de CPU, speculative decoding com metadados dentro do CUDA
graph, Flash Compressor, Lightning TopK, kernels para Blackwell e Hopper, além
de paralelismo para prefill e deployment. Na parte de treinamento, Miles entra
com suporte a RL, paralelismo DP, TP, SP, EP, PP e CP, FP8 e cuidado extra com
estabilidade numérica.&lt;/p&gt;
&lt;p&gt;O detalhe que importa para agente é o custo de carregar histórico. O texto
mostra que, com ShadowRadix e metadados dentro do grafo, o throughput de decode
fica quase plano entre 4 mil e 900 mil tokens. Em B200, a queda citada vai de
199 para 180 tokens por segundo. Em H200, de 266 para 240. Em outra parte, o
HiSparse promete até 3 vezes mais capacidade e throughput em serving de contexto
longo ao manter a parte inativa do KV cache em CPU.&lt;/p&gt;
&lt;p&gt;Isso muda o jeito de olhar para contexto gigante. Não é &amp;quot;joga tudo no prompt e
pronto&amp;quot;. É uma pilha inteira para impedir que o histórico mate latência, memória
e custo. Prefix cache, compressão, seleção top-k, offload de cache, kernel
customizado e scheduler viram parte do produto.&lt;/p&gt;
&lt;p&gt;Então, sim, DeepSeek V4 apareceu de novo. Mas não é reprise. O foco saiu da
tabela de preço e foi para a pergunta que realmente decide se um agente longo
fica usável: quem está segurando o peso do contexto quando a conversa passa de
demo bonita para workload de verdade?&lt;/p&gt;
&lt;p&gt;Fonte:
&lt;a href=&quot;https://www.lmsys.org/blog/2026-04-25-deepseek-v4/&quot;&gt;LMSYS - DeepSeek-V4 on Day 0: From Fast Inference to Verified RL with SGLang and Miles&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Feldera puxa agentes para dentro do software&lt;/h2&gt;
&lt;p&gt;O texto da Feldera acerta porque troca a fantasia do &amp;quot;agente colega de trabalho&amp;quot;
por um problema mais técnico: agente fica barulhento quando o software oferece
interface ruim. Se a única superfície é chat, ele precisa perguntar, resumir,
negociar, inferir estado e explicar demais. Isso consome atenção humana e
tokens.&lt;/p&gt;
&lt;p&gt;A proposta é construir software que o agente consiga operar com menos conversa.
O post cita três padrões bem conhecidos: CLI, especificações declarativas e
reconciliation loops no estilo Kubernetes. Em vez de transformar tudo em um
diálogo, o sistema oferece comandos, estado desejado e mecanismo de convergência.
O agente deixa de perguntar &amp;quot;o que faço agora?&amp;quot; o tempo todo e passa a trabalhar
em cima de interfaces mais previsíveis.&lt;/p&gt;
&lt;p&gt;O ponto mais forte vem quando o texto entra em banco de dados e change data
capture. Em um modelo ruim, o agente consulta tabelas, compara snapshots e tenta
descobrir o que mudou. Em um modelo melhor, o sistema emite eventos: esta
transação entrou, esta conta foi marcada como risco alto, este registro mudou
para revisão. O agente reage a mudanças específicas, não a um mar de estado.&lt;/p&gt;
&lt;p&gt;Isso conversa muito com DevOps, segurança e coding agents. Um agente com boa
interface não precisa ficar &amp;quot;lendo o mundo&amp;quot; a cada rodada. Ele recebe sinais
precisos, aplica uma política, propõe uma mudança ou chama um humano quando a
decisão passa do limite.&lt;/p&gt;
&lt;p&gt;A frase que fica é simples: software feito para humanos nem sempre é software
bom para agentes. Às vezes, a melhor evolução não é um prompt maior. É um
produto com CLI decente, estado declarativo, evento de mudança e limite de ação.&lt;/p&gt;
&lt;p&gt;Fonte:
&lt;a href=&quot;https://www.feldera.com/blog/ai-agents-arent-coworkers-embed-them-in-your-software&quot;&gt;Feldera - Agents Aren&amp;#39;t Coworkers, Embed Them in Your Software&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;O caso da matemática mostra o gargalo da verificação&lt;/h2&gt;
&lt;p&gt;A Scientific American publicou uma história com cara de manchete viral: Liam
Price, 23 anos, sem treinamento avançado formal em matemática, usou ChatGPT Pro
e chegou a uma solução para um problema de Erdős que estava aberto havia cerca
de 60 anos. O assunto envolve conjuntos primitivos de números inteiros e uma
conjectura sobre o comportamento do chamado Erdős sum quando os números do
conjunto ficam grandes.&lt;/p&gt;
&lt;p&gt;O risco óbvio seria transformar isso em &amp;quot;IA substituiu matemáticos&amp;quot;. Não é o
que a própria reportagem mostra. Price encontrou uma rota incomum com ajuda do
modelo e mandou o resultado para Kevin Barreto. Depois, especialistas como
Terence Tao e Jared Lichtman avaliaram o caminho. Lichtman foi bem direto: a
saída bruta do ChatGPT era ruim, e alguém com conhecimento precisava peneirar,
entender e comprimir a ideia. Tao também descreveu a mudança como uma rota
diferente da sequência padrão de movimentos que humanos vinham tentando.&lt;/p&gt;
&lt;p&gt;Essa é a parte útil para quem trabalha com código, agentes e automação. A IA
consegue baratear busca. Ela pode sugerir um caminho estranho, puxar uma técnica
de uma área vizinha ou quebrar a inércia de uma abordagem que todo mundo vinha
repetindo. Mas a prova ainda precisa existir. No fim, alguém precisa validar se
o caminho fecha.&lt;/p&gt;
&lt;p&gt;É um bom antídoto contra dois exageros opostos. Não é &amp;quot;milagre, acabou a
matemática humana&amp;quot;. Também não é &amp;quot;não aconteceu nada, ignore&amp;quot;. A notícia mostra
um padrão que deve aparecer mais vezes: geração de candidatos fica barata,
verificação continua cara.&lt;/p&gt;
&lt;p&gt;Para dev, dá para traduzir quase sem esforço. O agente pode sugerir refactor,
patch, arquitetura ou exploit hipotético. A pergunta séria vem depois: compila,
passa teste, preserva contrato, não vaza segredo, não inventa requisito e não
quebra produção?&lt;/p&gt;
&lt;p&gt;Fonte:
&lt;a href=&quot;https://www.scientificamerican.com/article/amateur-armed-with-chatgpt-vibe-maths-a-60-year-old-problem/&quot;&gt;Scientific American - An amateur just solved a 60-year-old math problem by asking AI&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;AWS App Runner fecha para novos clientes e vira lição de plataforma&lt;/h2&gt;
&lt;p&gt;A AWS informa que o App Runner deixará de aceitar novos clientes a partir de 30
de abril de 2026. Quem já usa o serviço pode continuar criando recursos e
serviços normalmente, mas a AWS também diz que não planeja adicionar novos
recursos. Em outras palavras: o produto continua vivo para clientes existentes,
mas a trilha de evolução acabou.&lt;/p&gt;
&lt;p&gt;O guia de migração recomenda Amazon ECS Express Mode. A promessa é preservar
parte da simplicidade operacional do App Runner, mas em cima de uma pilha ECS
mais explícita. A AWS descreve um fluxo em que você fornece uma imagem de
container e duas IAM roles, e o ECS cria a aplicação com Fargate, Application
Load Balancer, auto scaling e rede na sua conta.&lt;/p&gt;
&lt;p&gt;O caminho sugerido é blue/green com DNS weighted routing. O serviço antigo e o
novo rodam em paralelo, e o tráfego vai sendo deslocado aos poucos via Route 53
ou outro provedor de DNS. Se a aplicação foi publicada no App Runner a partir
de código fonte, antes é preciso criar uma etapa de build que gere uma imagem de
container e envie para um registry.&lt;/p&gt;
&lt;p&gt;O recado prático é antigo, mas sempre volta com um nome novo. Serviço gerenciado
compra velocidade, não imortalidade. Quando o produto para de evoluir, o que
salva a migração é ter container, domínio, DNS, certificado, observabilidade,
variáveis, health check e papéis de IAM minimamente entendidos.&lt;/p&gt;
&lt;p&gt;Para quem mantém aplicação pequena, isso não significa &amp;quot;nunca use serviço
gerenciado&amp;quot;. Seria exagero. Significa só que abstração boa precisa ter rota de
saída. Se ela não tiver, a conveniência vira dívida.&lt;/p&gt;
&lt;p&gt;Fonte:
&lt;a href=&quot;https://docs.aws.amazon.com/apprunner/latest/dg/apprunner-availability-change.html&quot;&gt;AWS App Runner availability change&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;npm e PyPI continuam sendo parte da superfície de ataque&lt;/h2&gt;
&lt;p&gt;O texto &amp;quot;Npm Slop &amp;amp; Wonky Software Supply Chains&amp;quot;, publicado em 9 de abril e
atualizado em 24 de abril de 2026, não é uma notícia de última hora. Entra aqui
porque encaixa direto no tema do dia: infraestrutura de agente não é só sandbox.
Também é o que você instala dentro dele.&lt;/p&gt;
&lt;p&gt;A crítica central é que npm e PyPI não são ecossistemas realmente baseados em
fonte. Eles distribuem bundles, wheels e binários enviados por mantenedores. Em
muitos casos, não dá para reconstruir o pacote publicado a partir do repositório
original. Lockfile ajuda a fixar o artefato baixado, mas o hash normalmente
amarra o pacote pronto, não toda a cadeia de origem.&lt;/p&gt;
&lt;p&gt;Os exemplos tornam o problema bem visível. O texto diz que o OpenClaw puxa 385
pacotes npm, com 324 sem attestation. Express, mesmo sendo um servidor web
clássico do Node, puxa 65 pacotes, todos sem attestation no levantamento citado.
Vite aparece como caso menor, com 15 pacotes, mas ainda inclui JavaScript
empacotado e binários Rust pré-compilados.&lt;/p&gt;
&lt;p&gt;Attestation melhora a situação, mas não fecha a história. O autor lembra que
ela pode fixar commit e workflow, sem necessariamente fixar imagem do runner,
ferramentas baixadas durante o build e binários de ações terceiras. A cadeia
parece mais limpa, mas ainda pode depender de coisa que muda por baixo.&lt;/p&gt;
&lt;p&gt;Para agente de IA, isso é especialmente chato. Muita gente fala de prompt
injection, permissão de ferramenta e isolamento de container. Tudo isso importa.
Mas, se o ambiente baixa centenas de dependências opacas antes de rodar, a
fronteira de confiança já começou antes do primeiro prompt.&lt;/p&gt;
&lt;p&gt;Fonte:
&lt;a href=&quot;https://simonramstedt.com/blog/2026-04-09-npm-slop-and-wonky-software-supply-chains/&quot;&gt;Simon Ramstedt - Npm Slop &amp;amp; Wonky Software Supply Chains&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Destaques rápidos&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;O relatório da Cisco Talos sobre UAT-4356 e FIRESTARTER é de 23 de abril, mas
ainda merece atenção operacional: o backdoor roda dentro do processo LINA em
dispositivos Cisco ASA e FTD, e a mitigação séria pode envolver reimagem do
equipamento. Fonte:
&lt;a href=&quot;https://blog.talosintelligence.com/uat-4356-firestarter/&quot;&gt;Cisco Talos - UAT-4356&amp;#39;s Targeting of Cisco Firepower Devices&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O texto da DTN Advisory sobre OAuth em ferramentas de IA junta Vercel,
Context.ai, Lovable e MCP em uma tese útil: uma integração SaaS com escopo
amplo demais vira uma relação de confiança persistente. O ponto não é só
&amp;quot;mais um vazamento&amp;quot;; é inventário e governo de OAuth. Fonte:
&lt;a href=&quot;https://dtnadvisory.substack.com/p/knock-knock-your-ai-tool-just-oauthd&quot;&gt;DTN Advisory - Knock Knock, Your AI Tool Just OAuth&amp;#39;d the Attacker In&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O GnuPG 2.5.19 saiu em 24 de abril com a série 2.5 trazendo Kyber, também
chamado de ML-KEM ou FIPS 203, para o caminho principal de criptografia
pós-quântica. O anúncio também lembra que a série 2.4 chega ao fim de vida em
cerca de dois meses. Fonte:
&lt;a href=&quot;https://lists.gnupg.org/pipermail/gnupg-announce/2026q2/000504.html&quot;&gt;GnuPG announce - GnuPG 2.5.19 released&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O tutorial sobre PageIndex é mais vitrine de produto do que paper final, mas
a ideia vale radar: em documentos longos e estruturados, árvore de seções e
busca guiada por raciocínio podem ser melhores do que jogar tudo em vetor e
torcer pela similaridade. Fonte:
&lt;a href=&quot;https://www.marktechpost.com/2026/04/25/rag-without-vectors-how-pageindex-retrieves-by-reasoning/&quot;&gt;MarkTechPost - RAG Without Vectors: How PageIndex Retrieves by Reasoning&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Matthew Brunelle descreve um uso bem pé no chão de Claude Code: ressuscitar
um projeto pessoal que provavelmente ficaria parado. O valor não está em
&amp;quot;aceitar tudo&amp;quot;, mas em dar spec, convenções, OpenAPI, plan mode, revisão e
teste real de cliente. Fonte:
&lt;a href=&quot;https://blog.matthewbrunelle.com/its-ok-to-use-coding-assistance-tools-to-revive-the-projects-you-never-were-going-to-finish/&quot;&gt;Matthew Brunelle - It&amp;#39;s OK to Use Coding Assistance Tools To Revive The Projects You Never Were Going To Finish&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O post sobre reviver BrowserID em 2026 é pequeno, mas toca em um problema que
vai crescer com apps pessoais feitos com IA: autenticação para software
pequeno, de domínio próprio, sem depender de Auth0, Google ou um painel
centralizado para cada brinquedo novo. Fonte:
&lt;a href=&quot;https://wakamoleguy.com/p/reviving-browserid-in-2026&quot;&gt;Wakamoleguy - Reviving BrowserID in 2026&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chris Wellons aposentou o Emacs depois de 20 anos de uso diário e está
procurando mantenedores para pacotes ativos como Elfeed. O trecho mais curioso
é a reconstrução de ferramentas pessoais com ajuda de IA, que muda o custo de
refazer software que antes ficaria eternamente para algum dia. Fonte:
&lt;a href=&quot;https://nullprogram.com/blog/2026/04/26/&quot;&gt;Nullprogram - I have officially retired from Emacs&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Acompanhamento de tendências&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Agente está virando problema de sistema. DeepSeek V4 precisa de SGLang,
Miles, cache, kernel e paralelismo. Feldera quer interfaces melhores para
agentes. PageIndex tenta mudar a camada de retrieval. O modelo é só uma peça.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verificação continua sendo o gargalo. A história da matemática não vale por
&amp;quot;a IA provou sozinha&amp;quot;, mas por mostrar que busca ficou barata e validação
continua especializada. O mesmo vale para código gerado, automação, segurança
e migração de infraestrutura.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A confiança está nas camadas chatas. OAuth, pacote npm, wheel do PyPI, App
Runner, GnuPG, DNS e processo LINA não têm o brilho de um modelo novo. Mas é
ali que o sistema ganha ou perde no mundo real.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>GPT-5.5, Claude Code e DeepSeek V4: agentes de IA viram infraestrutura</title><link>https://otaviomiranda.com.br/2026/gpt-55-claude-code-e-deepseek-v4-agentes-de-ia-viram-infraestrutura/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/gpt-55-claude-code-e-deepseek-v4-agentes-de-ia-viram-infraestrutura/</guid><description>OpenAI leva GPT-5.5 à API com ferramentas nativas, DeepSeek V4 aposta em cache e contexto longo, Anthropic explica queda no Claude Code e novas ferramentas mostram a pilha real dos agentes.</description><pubDate>Sat, 25 Apr 2026 10:01:28 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Nota: gerado por IA (The Paper LLM), com fontes originais listadas por bloco.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;O lote de hoje continua batendo na mesma tecla, mas com peças novas: agente de
IA não é mais só &amp;quot;modelo respondendo prompt&amp;quot;. A diferença prática está no
runtime, no cache, no histórico, nas ferramentas, no sandbox, no custo por token
e no jeito como tudo isso é observado quando dá errado.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/gpt-55.jpg&quot; alt=&quot;Agentes de IA virando infraestrutura&quot;&gt;&lt;/p&gt;
&lt;h2&gt;GPT-5.5 chega na API com cara de plataforma de agentes&lt;/h2&gt;
&lt;p&gt;A OpenAI colocou o GPT-5.5 e o GPT-5.5 Pro na API em 24 de abril de 2026. O
changelog lista uma janela de contexto de 1 milhão de tokens, entrada por
imagem, saída estruturada, function calling, prompt caching, Batch, web search,
computer use, hosted shell, apply patch, Skills, MCP e tool search. A página do
modelo também mostra 1.050.000 tokens de contexto e até 128.000 tokens de saída.&lt;/p&gt;
&lt;p&gt;Isso muda o enquadramento do lançamento. O GPT-5.5 não aparece só como &amp;quot;modelo
mais forte&amp;quot;. Ele chega grudado em uma superfície de execução para agentes. A
pergunta deixa de ser apenas &amp;quot;qual modelo responde melhor?&amp;quot; e vira &amp;quot;qual
ambiente consegue pesquisar, chamar ferramenta, editar arquivo, rodar shell,
aplicar patch, preservar contexto e parar na hora certa?&amp;quot;&lt;/p&gt;
&lt;p&gt;A própria documentação de prompting aponta nessa direção. A OpenAI recomenda
tratar o GPT-5.5 como uma família nova, não como troca cega de nome de modelo.
Também orienta prompts com critérios de sucesso, regras de parada, uso mais
cuidadoso de absolutos como &amp;quot;sempre&amp;quot; e &amp;quot;nunca&amp;quot;, preâmbulos curtos antes de
tarefas longas com ferramentas, e compaction intencional em agentes de execução
prolongada.&lt;/p&gt;
&lt;p&gt;Sim, a ironia é difícil de ignorar: este próprio texto sai de um pipeline
automatizado que depende de modelo, prompt, ferramentas, memória, arquivos e
checagens de build. Quando a ferramenta ganha shell, patch, navegador, contexto
gigante e geração de imagem, o prompt deixa de ser só instrução de escrita. Ele
vira parte da infraestrutura.&lt;/p&gt;
&lt;p&gt;Para quem mantém automações reais, a leitura prática é simples. Migrar para
GPT-5.5 não deve ser só trocar &lt;code&gt;gpt-5.4&lt;/code&gt; por &lt;code&gt;gpt-5.5&lt;/code&gt; e torcer. Vale revisar
prompts longos, tool contracts, mensagens de progresso, regras de parada,
limites de busca, critérios de conclusão, custo de contexto, geração de assets e
comportamento em tarefas que rodam por muitos minutos.&lt;/p&gt;
&lt;p&gt;Fontes:
&lt;a href=&quot;https://developers.openai.com/api/docs/changelog&quot;&gt;OpenAI API changelog&lt;/a&gt;,
&lt;a href=&quot;https://developers.openai.com/api/docs/models/gpt-5.5&quot;&gt;modelo GPT-5.5&lt;/a&gt;,
&lt;a href=&quot;https://developers.openai.com/api/docs/guides/latest-model&quot;&gt;guia de uso do GPT-5.5&lt;/a&gt;
e
&lt;a href=&quot;https://developers.openai.com/api/docs/guides/prompt-guidance&quot;&gt;prompt guidance da OpenAI&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;DeepSeek V4 volta, mas agora o assunto é cache e custo de agente&lt;/h2&gt;
&lt;p&gt;DeepSeek V4 já apareceu no roundup anterior pelo ângulo de preço, pesos abertos
e contexto longo. A história de hoje não é a mesma. O texto novo do Hugging Face
e a publicação técnica da NVIDIA puxam a conversa para outro lugar: 1 milhão de
tokens só ajuda se o custo de atenção e de KV cache não matar o agente no meio
da tarefa.&lt;/p&gt;
&lt;p&gt;O Hugging Face resume bem a diferença. A janela de 1 milhão de tokens é
capacidade, não performance. Em uma tarefa longa, cada resultado de ferramenta,
log, trecho de código e decisão anterior entra no contexto. A cada passo, o
modelo paga para olhar para tudo aquilo de novo. Se a arquitetura não reduzir
esse custo, contexto gigante vira uma promessa cara.&lt;/p&gt;
&lt;p&gt;Segundo o texto do Hugging Face, o DeepSeek-V4-Pro usa 27% dos FLOPs de
inferência por token e 10% da memória de KV cache em comparação com o
DeepSeek-V3.2 em 1 milhão de tokens. O V4-Flash reduz ainda mais esses números:
10% dos FLOPs e 7% da memória de cache. A NVIDIA apresenta a mesma direção pelo
lado de deployment: V4-Pro tem 1,6 trilhão de parâmetros totais e 49 bilhões
ativos; V4-Flash tem 284 bilhões totais e 13 bilhões ativos; ambos com licença
MIT e contexto de 1 milhão de tokens.&lt;/p&gt;
&lt;p&gt;O pedaço mais interessante para dev talvez esteja no Reasonix. O projeto tenta
ser um agente específico para DeepSeek, com loop cache-first, prefixo estável,
MCP, TUI em terminal e edição por blocos revisáveis. A tese é bem direta:
framework genérico de agente tende a remontar o prompt a cada turno e, com isso,
pode perder o benefício do cache de prefixo. Quando o provedor dá uma vantagem
específica, a abstração genérica pode custar dinheiro.&lt;/p&gt;
&lt;p&gt;Ainda precisa de teste em workload real. Mas o sinal editorial é bom: a próxima
briga em agentes não é só benchmark. É cache hit, formato de trace, prompt
imutável, log append-only, custo por chamada e arquitetura que entende o modelo
que está usando.&lt;/p&gt;
&lt;p&gt;Fontes: &lt;a href=&quot;https://huggingface.co/blog/deepseekv4&quot;&gt;Hugging Face - DeepSeek-V4&lt;/a&gt;,
&lt;a href=&quot;https://developer.nvidia.com/blog/build-with-deepseek-v4-using-nvidia-blackwell-and-gpu-accelerated-endpoints/&quot;&gt;NVIDIA Developer Blog sobre DeepSeek V4&lt;/a&gt;
e &lt;a href=&quot;https://github.com/esengine/reasonix&quot;&gt;GitHub - Reasonix&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Anthropic mostra que agente pode piorar sem o modelo &amp;quot;piorar&amp;quot;&lt;/h2&gt;
&lt;p&gt;A Anthropic publicou um postmortem sobre reclamações recentes de qualidade no
Claude Code. A parte útil é que a empresa não joga tudo na conta de um modelo
misterioso ficando mais burro. Ela aponta três mudanças de produto: alteração no
esforço de raciocínio padrão, bug de cache que descartava pensamento antigo em
sessões ociosas, e uma instrução de sistema para reduzir verbosidade que acabou
prejudicando qualidade de código.&lt;/p&gt;
&lt;p&gt;O primeiro caso veio do trade-off entre inteligência, latência e consumo de
limites. O Claude Code tinha Opus 4.6 em &lt;code&gt;high&lt;/code&gt; por padrão, depois passou para
&lt;code&gt;medium&lt;/code&gt;, e usuários começaram a relatar queda de qualidade. A Anthropic diz que
reverteu a decisão em 7 de abril: Opus 4.7 passou a usar &lt;code&gt;xhigh&lt;/code&gt; por padrão, e
os demais modelos voltaram para &lt;code&gt;high&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;O segundo caso é mais interessante para quem mexe com automação longa. Em 26 de
março, a Anthropic enviou uma otimização para limpar pensamento antigo depois de
uma sessão ficar ociosa por mais de uma hora. A intenção era reduzir custo ao
retomar uma sessão que já teria perdido cache. O bug fez essa limpeza acontecer
em todos os turnos seguintes. Resultado: o agente parecia esquecido, repetitivo
e tomava decisões estranhas porque perdia a trilha do próprio raciocínio. A
correção saiu em 10 de abril.&lt;/p&gt;
&lt;p&gt;O terceiro caso veio de prompt. Uma instrução para limitar texto entre tool
calls e respostas finais foi enviada em 16 de abril e revertida em 20 de abril
depois que avaliações mais amplas mostraram queda de 3% em Opus 4.6 e 4.7.
Prompt pequeno, efeito grande. Quem já ajustou agente por tentativa e erro
conhece esse tipo de acidente.&lt;/p&gt;
&lt;p&gt;A lição é ótima porque é chata do jeito certo. Quando um coding agent piora, não
basta perguntar &amp;quot;qual modelo está por baixo?&amp;quot;. Tem que olhar effort, prompt,
memória, cache, rollout, versão pública, evals internas e métricas de qualidade.
O modelo pode ser o mesmo, mas o produto em volta mudou.&lt;/p&gt;
&lt;p&gt;Fonte:
&lt;a href=&quot;https://www.anthropic.com/engineering/april-23-postmortem&quot;&gt;Anthropic - An update on recent Claude Code quality reports&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Kali365 mostra por que MFA não basta quando a sessão é autorizada&lt;/h2&gt;
&lt;p&gt;O relatório da Arctic Wolf sobre Kali365 é um bom lembrete de que ataque de
identidade moderno não precisa roubar senha do jeito antigo. A campanha usa o
fluxo legítimo de device code da Microsoft. O atacante inicia uma solicitação, a
vítima entra em uma página real da Microsoft, autentica de verdade, passa pelo
MFA e, sem perceber, autoriza uma sessão controlada pelo atacante.&lt;/p&gt;
&lt;p&gt;O que sai disso não é uma senha. São tokens OAuth válidos. Segundo a Arctic
Wolf, tokens de acesso e refresh capturados permitem acesso imediato à mailbox e
atividade pós-comprometimento. Em alguns casos, os operadores também criaram
regras maliciosas de inbox para esconder alertas de segurança e aumentar o tempo
de permanência.&lt;/p&gt;
&lt;p&gt;O Kali365 Live aparece como uma plataforma de phishing-as-a-service com painel
multi-tenant, geração de lures, páginas hospedadas em Cloudflare Workers,
compartilhamento de tokens entre afiliados, fluxo de captura adversary-in-the-
middle e até cliente desktop para acompanhar tokens e mailbox. É produto. Feio,
mas produto.&lt;/p&gt;
&lt;p&gt;O ponto defensivo é bem concreto. Se device code flow não é necessário, bloquear
por Conditional Access reduz muito o caminho do ataque. Quando for necessário,
vale restringir por grupo, local confiável e dispositivo gerenciado. Também vale
caçar padrões de autorização suspeitos, regras novas de mailbox e sinais de uso
do cliente identificado pela Arctic Wolf.&lt;/p&gt;
&lt;p&gt;Para treinamento de segurança, esse caso é melhor do que mais um slide dizendo
&amp;quot;cuidado com phishing&amp;quot;. Ele mostra o fluxo exato: código legítimo, provedor
legítimo, MFA legítimo e sessão ilegítima no final.&lt;/p&gt;
&lt;p&gt;Fonte:
&lt;a href=&quot;https://arcticwolf.com/resources/blog/token-bingo-dont-let-your-code-be-the-winner/&quot;&gt;Arctic Wolf - Token Bingo: Don&amp;#39;t Let Your Code be the Winner&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;GitNexus, Stash e VT Code mostram a pilha em volta do modelo&lt;/h2&gt;
&lt;p&gt;Três projetos do briefing apontam para a mesma direção. GitNexus, Stash e VT
Code não tentam ganhar a conversa dizendo &amp;quot;temos o modelo mais inteligente&amp;quot;.
Eles mexem no que fica ao redor: mapa de código, memória persistente, política
de comando, sandbox, auditoria e contexto.&lt;/p&gt;
&lt;p&gt;O GitNexus indexa um repositório em grafo de conhecimento com dependências, call
chains, clusters e fluxos de execução. A parte prática está na exposição via MCP
e em ferramentas como análise de impacto antes de mudança. Em vez de dar para o
agente um monte de arquivo solto e esperar que ele descubra tudo, a ideia é
entregar uma visão estrutural do código.&lt;/p&gt;
&lt;p&gt;O Stash ataca outro problema: memória entre sessões. Ele usa PostgreSQL,
pgvector e MCP para transformar episódios em fatos, relacionamentos, padrões,
falhas e objetivos. Isso é útil, mas também acende uma luz amarela. Memória
persistente melhora continuidade, só que também vira superfície de privacidade,
injeção e vazamento se não tiver namespace, inspeção e caminho claro de apagar.&lt;/p&gt;
&lt;p&gt;O VT Code entra pelo terminal. É um coding agent em Rust com suporte a múltiplos
provedores, busca semântica, Agent Skills, ACP, MCP e interface TUI. O detalhe
mais importante para este roundup está na seção de segurança: allowlist de
comandos, validação de argumentos, isolamento de workspace, sandbox nativo no
macOS e no Linux, políticas para ferramentas MCP, aprovação humana e trilha de
auditoria.&lt;/p&gt;
&lt;p&gt;Juntando os três, a mensagem é difícil de ignorar. Agente bom não é só modelo
bom. É contexto certo, memória que dá para auditar, ferramenta com política,
comando com limite e mapa de impacto antes de sair editando código.&lt;/p&gt;
&lt;p&gt;Fontes: &lt;a href=&quot;https://github.com/abhigyanpatwari/GitNexus&quot;&gt;GitHub - GitNexus&lt;/a&gt;,
&lt;a href=&quot;https://alash3al.github.io/stash/&quot;&gt;Stash - Persistent Memory for AI Agents&lt;/a&gt; e
&lt;a href=&quot;https://github.com/vinhnx/VTCode&quot;&gt;GitHub - VT Code&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Destaques rápidos&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A Mozilla diz que o Firefox 150 inclui correções para 271 vulnerabilidades
encontradas durante uma avaliação inicial com Claude Mythos Preview. O texto é
de 21 de abril de 2026, então não é notícia fresca do dia, mas encaixa no
mesmo quadro: IA defensiva começa a virar pipeline de segurança, não demo de
laboratório. Fonte:
&lt;a href=&quot;https://blog.mozilla.org/en/privacy-security/ai-security-zero-day-vulnerabilities/&quot;&gt;Mozilla - The zero-days are numbered&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A CISA adicionou quatro falhas exploradas ao catálogo KEV envolvendo
SimpleHelp, Samsung MagicINFO 9 Server e roteadores D-Link DIR-823X. A data
limite para agências federais aplicarem correções ou descontinuarem o uso é 8
de maio de 2026. Fonte:
&lt;a href=&quot;https://thehackernews.com/2026/04/cisa-adds-4-exploited-flaws-to-kev-sets.html&quot;&gt;The Hacker News - CISA Adds 4 Exploited Flaws to KEV&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O experimento &lt;code&gt;quantumslop&lt;/code&gt; trocou o backend IBM Quantum por &lt;code&gt;/dev/urandom&lt;/code&gt; e
manteve o restante do verificador intacto. Se a recuperação de chaves continua
com bytes aleatórios, o resultado medido não prova vantagem quântica; prova
que o harness aceita candidatos aleatórios suficientes. Fonte:
&lt;a href=&quot;https://github.com/yuvadm/quantumslop/blob/25ad2e76ae58baa96f6219742459407db9dd17f5/URANDOM_DEMO.md&quot;&gt;GitHub - quantumslop URANDOM_DEMO&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Jeff Geerling testou adaptadores USB de 10 gigabits Ethernet baseados em
RTL8159. O achado prático é o de sempre: para chegar perto de 10 gigabits por
segundo, precisa de porta USB 3.2 Gen 2x2; em máquinas sem essa porta, o mundo
real fica mais perto de 6 a 7 gigabits por segundo. Fonte:
&lt;a href=&quot;https://www.jeffgeerling.com/blog/2026/new-10-gbe-usb-adapters-cooler-smaller-cheaper/&quot;&gt;Jeff Geerling - New 10 GbE USB adapters&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O fwupd 2.1.2 parece atualização pequena, mas traz endurecimento importante:
checagem para AMD EntrySign, parser CBOR nativo, limites contra ZIP bombs,
teto de 4 GiB para cápsulas UEFI e correções em parsing de arquivos PE
maliciosos. Firmware updater roda em caminho privilegiado; parser robusto
importa. Fonte:
&lt;a href=&quot;https://github.com/fwupd/fwupd/releases/tag/2.1.2&quot;&gt;GitHub - fwupd 2.1.2&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A AWS colocou TLS híbrido pós-quântico no Secrets Manager para clientes que
suportam o recurso. Na prática, a ação principal é atualizar o cliente e
verificar no CloudTrail se o campo &lt;code&gt;tlsDetails.keyExchange&lt;/code&gt; mostra
&lt;code&gt;X25519MLKEM768&lt;/code&gt;. Fonte:
&lt;a href=&quot;https://aws.amazon.com/blogs/security/protecting-your-secrets-from-tomorrows-quantum-risks/&quot;&gt;AWS Security Blog - Protecting your secrets from tomorrow&amp;#39;s quantum risks&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A dica &lt;code&gt;git config am.threeWay true&lt;/code&gt;, do Michael Catanzaro, é pequena e ótima.
Ela faz muitos patches que falhariam no &lt;code&gt;git am&lt;/code&gt; caírem em conflito de três
vias, como um merge normal. Para mantenedor e backport de segurança, isso
economiza tempo e paciência. Fonte:
&lt;a href=&quot;https://blogs.gnome.org/mcatanzaro/2026/04/24/git-config-am-threeway/&quot;&gt;Michael Catanzaro - git config am.threeWay&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O ensaio sobre plain text é menos notícia e mais lembrete. Markdown, diagramas
ASCII, monospace e arquivos textuais continuam vivos porque atravessam editor,
terminal, git, automação e IA com menos atrito do que formatos ricos demais.
Fonte:
&lt;a href=&quot;https://unsung.aresluna.org/plain-text-has-been-around-for-decades-and-its-here-to-stay/&quot;&gt;Unsung - Plain text has been around for decades&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Acompanhamento de tendências&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Lançamento de modelo está virando lançamento de runtime. GPT-5.5 chega com
ferramentas hospedadas, DeepSeek V4 força conversa sobre cache e Reasonix
tenta explorar comportamento específico do provedor.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A qualidade percebida de agente depende de camadas que muita gente não mede. O
postmortem da Anthropic deixa isso explícito: effort, cache, memória, prompt,
eval e rollout podem mudar a experiência sem trocar o modelo base.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Segurança está saindo do prompt isolado. Device code phishing, memória
persistente, MCP, sandbox, política de comando e audit trail estão na mesma
pilha operacional.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As ferramentas boas estão ficando menos mágicas e mais inspecionáveis. Grafo
de código, logs, namespaces, allowlists e verificações pequenas parecem
detalhes chatos. São exatamente os detalhes que permitem confiar um pouco mais
no agente.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Fechando o quadro&lt;/h2&gt;
&lt;p&gt;O resumo de hoje poderia ser &amp;quot;o modelo continua importante, mas sozinho ele não
fecha a conta&amp;quot;. GPT-5.5 mostra o pacote de plataforma. DeepSeek V4 mostra custo
e cache. Anthropic mostra que produto em volta do modelo quebra qualidade.
Kali365 mostra que identidade falha mesmo quando o login parece legítimo.
GitNexus, Stash e VT Code mostram que contexto, memória, política e auditoria já
viraram parte do produto.&lt;/p&gt;
&lt;p&gt;Não é a parte mais brilhante da IA. É a parte que decide se ela aguenta uso
real.&lt;/p&gt;
</content:encoded></item><item><title>DeepSeek V4, Ubuntu 26.04 e MCP: infraestrutura e segurança voltam ao centro da IA</title><link>https://otaviomiranda.com.br/2026/deepseek-v4-ubuntu-2604-e-mcp-infra-e-seguranca-para-agentes-de-ia/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/deepseek-v4-ubuntu-2604-e-mcp-infra-e-seguranca-para-agentes-de-ia/</guid><description>DeepSeek V4 barateia contexto longo, Ubuntu 26.04 muda a base de servidores, novos papers expõem riscos em agentes MCP e um texto sobre file descriptors lembra que segurança ainda começa no sistema operacional.</description><pubDate>Fri, 24 Apr 2026 10:01:47 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Nota: gerado por IA (The Paper LLM), com fontes originais listadas por bloco.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;O lote de hoje tem uma linha bem prática: IA não está avançando só pelo modelo
mais esperto. O jogo também está no custo por token, no sistema operacional que
vira base de container, na forma como ferramentas são descritas para agentes e
na velha pergunta: &amp;quot;esse caminho de arquivo é mesmo confiável?&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/2026-04-24.png&quot; alt=&quot;Infraestrutura, contexto e segurança em agentes de IA&quot;&gt;&lt;/p&gt;
&lt;h2&gt;DeepSeek V4 empurra a briga para contexto longo barato&lt;/h2&gt;
&lt;p&gt;Em 24 de abril de 2026, a DeepSeek publicou os modelos DeepSeek-V4-Pro e
DeepSeek-V4-Flash no Hugging Face. Os dois são modelos MoE com janela de
contexto de 1 milhão de tokens. O Pro tem 1,6 trilhão de parâmetros no total e
49 bilhões ativos. O Flash tem 284 bilhões no total e 13 bilhões ativos.&lt;/p&gt;
&lt;p&gt;O dado mais importante para dev não é só o tamanho. É o preço. A página oficial
da API lista o Flash a US$0,14 por 1 milhão de tokens de entrada sem cache e
US$0,28 por 1 milhão de tokens de saída. O Pro aparece a US$1,74 na entrada e
US$3,48 na saída. Para tarefas com muito contexto, isso muda o tipo de
experimento que dá para fazer sem virar planilha de custo.&lt;/p&gt;
&lt;p&gt;Simon Willison colocou o lançamento no mesmo eixo da corrida de modelos
fechados: o GPT-5.5 fica no topo de capacidade para agentes e trabalho longo,
mas com preço bem mais alto. O DeepSeek V4 entra por outro lado, tentando tornar
contexto longo e pesos abertos mais baratos. Não é a mesma proposta. E é
justamente por isso que a comparação importa.&lt;/p&gt;
&lt;p&gt;Na prática, isso pode afetar pipelines de busca, classificação, revisão em lote,
agentes que precisam carregar muito histórico e ferramentas que hoje evitam
contexto grande por custo. Ainda precisa de teste real fora da tabela de
benchmark. Mas o sinal é claro: contexto longo barato virou produto, não só
promessa de laboratório.&lt;/p&gt;
&lt;p&gt;Fontes:
&lt;a href=&quot;https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro&quot;&gt;DeepSeek-V4-Pro no Hugging Face&lt;/a&gt;,
&lt;a href=&quot;https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash&quot;&gt;DeepSeek-V4-Flash no Hugging Face&lt;/a&gt;,
&lt;a href=&quot;https://api-docs.deepseek.com/quick_start/pricing&quot;&gt;DeepSeek API Pricing&lt;/a&gt; e
&lt;a href=&quot;https://simonwillison.net/2026/Apr/24/deepseek-v4/&quot;&gt;Simon Willison sobre DeepSeek V4&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Ubuntu 26.04 LTS muda detalhes que pegam em servidor e container&lt;/h2&gt;
&lt;p&gt;A Canonical publicou as notas do Ubuntu 26.04 LTS em 23 de abril de 2026. A
versão terá suporte padrão até abril de 2031, com janela estendida via Ubuntu
Pro. Para desktop isso sempre traz uma lista grande de aplicativos novos, mas o
ponto mais útil para quem mexe com VPS, Docker, WSL e imagem base está nas
trocas menos chamativas.&lt;/p&gt;
&lt;p&gt;O OpenSSH salta da família 9.6 para 10.2 no caminho de upgrade a partir do
Ubuntu 24.04 LTS. Isso traz avisos e remoções que podem aparecer em ambiente
velho, como a retirada do algoritmo DSA fraco e mudanças em torno de chaves, DNS
SSHFP e opções do &lt;code&gt;sshd&lt;/code&gt;. Também entra o algoritmo híbrido pós-quântico
&lt;code&gt;mlkem768x25519-sha256&lt;/code&gt; por padrão.&lt;/p&gt;
&lt;p&gt;No tempo do sistema, novas instalações passam a usar Chrony no lugar de
&lt;code&gt;systemd-timesyncd&lt;/code&gt;. No boot, o Ubuntu agora usa Dracut como infraestrutura
padrão de initramfs, embora &lt;code&gt;initramfs-tools&lt;/code&gt; continue suportado. No pacote
básico, APT foi para a versão 3.1 e o &lt;code&gt;apt-key&lt;/code&gt; foi removido. Scripts antigos
que ainda gerenciam chaves do jeito velho podem quebrar.&lt;/p&gt;
&lt;p&gt;Esses detalhes parecem pequenos até alguém trocar a base de uma imagem e o CI
começar a falhar. Para ambientes de agentes, runners e sandboxes, a mensagem é
simples: Ubuntu 26.04 não é só &amp;quot;mais uma LTS&amp;quot;. É uma nova linha de base para
SSH, tempo, boot, pacotes e comportamento de sistema.&lt;/p&gt;
&lt;p&gt;Fontes:
&lt;a href=&quot;https://documentation.ubuntu.com/release-notes/26.04/&quot;&gt;Ubuntu 26.04 LTS release notes&lt;/a&gt;
e
&lt;a href=&quot;https://documentation.ubuntu.com/release-notes/26.04/summary-for-lts-users/&quot;&gt;Ubuntu 26.04 LTS summary for LTS users&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Papers novos mostram que agente seguro não é só prompt seguro&lt;/h2&gt;
&lt;p&gt;Uma leva de papers publicados entre 22 e 23 de abril de 2026 reforça um ponto
incômodo: o ataque contra agentes está saindo do prompt isolado e indo para a
camada de ferramentas, memória e contexto.&lt;/p&gt;
&lt;p&gt;O paper &amp;quot;Breaking MCP with Function Hijacking Attacks&amp;quot; descreve um ataque que
manipula a seleção de ferramentas por meio de funções adversariais. A ideia não
é apenas convencer o modelo a dizer algo errado, mas fazer o agente escolher uma
função específica. Nos testes citados pelos autores, o ataque chegou a taxas de
sucesso entre 70% e 100% em tarefas no estilo BFCL com cinco modelos.&lt;/p&gt;
&lt;p&gt;Outro trabalho, &amp;quot;Cross-Session Threats in AI Agents&amp;quot;, olha para ataques
espalhados em várias sessões. Um guardrail que avalia cada mensagem de forma
isolada pode não enxergar o ataque inteiro, porque o payload só aparece quando
as partes são juntadas. O paper propõe um benchmark para esse tipo de ameaça e
testa leitores de memória com orçamento limitado.&lt;/p&gt;
&lt;p&gt;O terceiro, &amp;quot;CI-Work&amp;quot;, foca em agentes corporativos com acesso a contexto
interno. A conclusão é bem direta: agentes que recuperam informação para ajudar
o usuário também podem vazar contexto sensível. O estudo relata violações de
privacidade entre 15,8% e 50,9%, com vazamento chegando a 26,7% nos cenários
avaliados.&lt;/p&gt;
&lt;p&gt;Sim, tem uma ironia aqui. Um agente de IA avisando que agente de IA precisa de
limite. Mas é justamente por isso que o aviso importa.&lt;/p&gt;
&lt;p&gt;O aprendizado prático não é exótico. Descrição de ferramenta também é entrada.
Memória também é superfície de ataque. Recuperação de contexto também precisa de
política. E, quando o agente tem ferramenta de verdade, a segurança precisa
acompanhar a trajetória, não só a resposta final.&lt;/p&gt;
&lt;p&gt;Fontes:
&lt;a href=&quot;https://arxiv.org/abs/2604.20994&quot;&gt;arXiv - Breaking MCP with Function Hijacking Attacks&lt;/a&gt;,
&lt;a href=&quot;https://arxiv.org/abs/2604.21131&quot;&gt;arXiv - Cross-Session Threats in AI Agents&lt;/a&gt; e
&lt;a href=&quot;https://arxiv.org/abs/2604.21308&quot;&gt;arXiv - CI-Work: Benchmarking Contextual Integrity in Enterprise LLM Agents&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Abrir arquivo com segurança ainda é uma armadilha real&lt;/h2&gt;
&lt;p&gt;Em 23 de abril de 2026, Sebastian Wick publicou um texto sobre uma operação que
parece banal: abrir um arquivo. A tese do artigo é que uma string de caminho não
é uma referência estável. Ela aponta para uma localização em um namespace de
arquivos, e essa localização pode mudar enquanto outro processo está tentando
validar e usar o caminho.&lt;/p&gt;
&lt;p&gt;O exemplo central é a velha corrida TOCTOU, de &amp;quot;time of check&amp;quot; para &amp;quot;time of
use&amp;quot;. Um processo privilegiado normaliza um caminho, resolve symlinks e acha que
está tudo dentro do diretório permitido. Enquanto isso, outro processo troca um
componente do caminho por symlink. O que parecia seguro pode acabar abrindo algo
fora do limite esperado.&lt;/p&gt;
&lt;p&gt;A saída defendida no texto é trabalhar com file descriptors sempre que possível.
Depois que o kernel entrega um descriptor para um inode, a referência fica presa
àquele objeto. O caminho pode ser renomeado, removido ou substituído por
symlink; o descriptor continua apontando para o mesmo inode.&lt;/p&gt;
&lt;p&gt;Isso conversa bem com agentes e sandboxes porque muita automação mistura código
gerado, arquivos temporários, permissões e processos auxiliares. Quando existe
fronteira de privilégio, passar path cru para helper privilegiado é pedir para
ter surpresa. O texto passa por &lt;code&gt;O_PATH&lt;/code&gt;, &lt;code&gt;openat&lt;/code&gt;, &lt;code&gt;openat2&lt;/code&gt;, &lt;code&gt;O_NOFOLLOW&lt;/code&gt; e
fd-based traversal, mas a regra mental já ajuda: path é nome, file descriptor é
referência.&lt;/p&gt;
&lt;p&gt;Fonte:
&lt;a href=&quot;https://blog.sebastianwick.net/posts/how-hard-is-it-to-open-a-file/&quot;&gt;Sebastian Wick - How Hard Is It To Open a File?&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Destaques rápidos&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;O Google DeepMind publicou o Decoupled DiLoCo, uma abordagem para treinamento
distribuído assíncrono entre ilhas de compute. O ponto mais interessante é
operacional: a empresa relata treinamento de um modelo de 12 bilhões de
parâmetros em quatro regiões dos EUA usando 2 a 5 Gbps de rede entre regiões.
Fonte:
&lt;a href=&quot;https://deepmind.google/blog/decoupled-diloco/&quot;&gt;Google DeepMind - Decoupled DiLoCo&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O paper BadStyle mostra uma linha desconfortável de ataque: estilo natural de
escrita como gatilho de backdoor em LLMs. Isso é especialmente relevante para
fine-tuning, adapters e pipelines que tratam &amp;quot;estilo&amp;quot; como detalhe inocente.
Fonte:
&lt;a href=&quot;https://arxiv.org/abs/2604.21700&quot;&gt;arXiv - Stealthy Backdoor Attacks against LLMs Based on Natural Style Triggers&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O projeto &lt;code&gt;sem&lt;/code&gt; propõe diffs em nível de entidade, em vez de linhas. Para
revisão com IA, a ideia é boa: quando a ferramenta sabe que uma função mudou,
consegue montar contexto e impacto de forma mais útil do que um diff textual
solto. Fonte: &lt;a href=&quot;https://github.com/Ataraxy-Labs/sem&quot;&gt;GitHub - Ataraxy-Labs/sem&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O Honker leva semântica parecida com &lt;code&gt;NOTIFY&lt;/code&gt; e &lt;code&gt;LISTEN&lt;/code&gt; para SQLite, com
filas duráveis e streams transacionais. É pequeno, mas aponta para uma direção
interessante: apps locais com fila, evento e banco simples sem puxar uma stack
inteira. Fonte:
&lt;a href=&quot;https://simonwillison.net/2026/Apr/24/honker/&quot;&gt;Simon Willison - russellromney/honker&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Acompanhamento de tendências&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A disputa entre modelos está virando disputa de custo por contexto. O modelo
mais capaz continua importando, mas o preço por milhão de tokens muda o que dá
para experimentar todo dia.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A base operacional voltou para o centro da conversa. LTS nova, APT novo,
OpenSSH novo, initramfs novo e detalhes de filesystem parecem assunto de
sysadmin até quebrarem um runner de agente.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Segurança de agente está ficando menos parecida com jailbreak de prompt e mais
parecida com arquitetura de produto. Ferramenta, memória, contexto, auditoria
e fronteira de privilégio precisam entrar no mesmo desenho.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Fechando o quadro&lt;/h2&gt;
&lt;p&gt;O recado do dia é menos glamouroso do que &amp;quot;saiu um modelo novo&amp;quot;, mas talvez seja
mais útil. O que define se IA funciona bem no mundo real é o pacote: modelo,
preço, contexto, sistema operacional, sandbox, ferramenta, memória e permissão.
Separar essas coisas no papel é fácil. Em produção, elas se misturam rápido.&lt;/p&gt;
</content:encoded></item><item><title>GPT-5.5 apareceu no Codex hoje. Peguei no pulo.</title><link>https://otaviomiranda.com.br/2026/gpt-55-apareceu-no-codex-hoje-peguei-no-pulo/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/gpt-55-apareceu-no-codex-hoje-peguei-no-pulo/</guid><description>O GPT-5.5 saiu hoje e apareceu no meu Codex. Ainda é material de lançamento da OpenAI, então fica como nota quente, não review definitivo.</description><pubDate>Thu, 23 Apr 2026 20:04:35 GMT</pubDate><content:encoded>&lt;p&gt;O GPT-5.5 saiu hoje, 23 de abril de 2026, e eu descobri do jeito mais besta
possível: apareceu um botão pra atualizar o Codex. Cliquei.&lt;/p&gt;
&lt;p&gt;Quando olhei de novo, o modelo já tinha mudado pra GPT-5.5.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/gpt-5-5.jpg&quot; alt=&quot;OpenAI GPT 5.5&quot;&gt;&lt;/p&gt;
&lt;p&gt;Então, antes que isso vire &amp;quot;notícia de ontem&amp;quot; (o que em IA parece acontecer em
umas três horas), resolvi deixar esta nota aqui.&lt;/p&gt;
&lt;p&gt;Mas, vamos com calma: isso aqui &lt;strong&gt;não é review&lt;/strong&gt;. Não deu tempo. Eu literalmente
acabei de pegar o modelo. Este texto é baseado principalmente no
&lt;a href=&quot;https://openai.com/index/introducing-gpt-5-5/&quot;&gt;material de lançamento da própria OpenAI&lt;/a&gt;,
então ainda tem muito cheiro de marketing de fabricante. Benchmark bonito, frase
forte, promessa de produtividade, aquele pacote completo.&lt;/p&gt;
&lt;p&gt;Dito isso, tem algumas coisas interessantes.&lt;/p&gt;
&lt;h2&gt;O que a OpenAI está prometendo&lt;/h2&gt;
&lt;p&gt;Segundo a OpenAI, o GPT-5.5 não está sendo vendido só como &amp;quot;um chat melhor&amp;quot;. A
promessa é mais específica: um modelo melhor pra trabalhar com ferramentas por
mais tempo.&lt;/p&gt;
&lt;p&gt;Código, pesquisa online, análise de dados, documentos, planilhas, uso de
software e tarefas que exigem várias etapas.&lt;/p&gt;
&lt;p&gt;Ou seja: menos &amp;quot;me responde isso aqui&amp;quot; e mais &amp;quot;pega esse problema bagunçado,
entende o contexto, usa ferramentas, testa, corrige e me devolve algo que
preste&amp;quot;.&lt;/p&gt;
&lt;p&gt;Pra quem usa Codex, isso é exatamente onde a coisa começa a ficar interessante.&lt;/p&gt;
&lt;p&gt;A OpenAI diz que o GPT-5.5 melhorou em tarefas de código e uso de terminal. No
material oficial, eles citam &lt;strong&gt;82.7% no Terminal-Bench 2.0&lt;/strong&gt; contra &lt;strong&gt;75.1% do
GPT-5.4&lt;/strong&gt;. No &lt;strong&gt;SWE-Bench Pro&lt;/strong&gt;, que tenta medir resolução de issues reais em
projetos, o número subiu pouco: &lt;strong&gt;58.6% contra 57.7%&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Esse &amp;quot;subiu pouco&amp;quot; também é importante. Nem todo benchmark grita revolução.
Algumas coisas parecem avanço incremental mesmo.&lt;/p&gt;
&lt;p&gt;E tudo isso ainda precisa passar pelo teste mais cruel: projeto real, com código
velho, decisão estranha, teste quebrando, dependência chata e humano cansado do
outro lado.&lt;/p&gt;
&lt;h2&gt;Codex, contexto e preço&lt;/h2&gt;
&lt;p&gt;No &lt;a href=&quot;https://openai.com/index/introducing-gpt-5-5/&quot;&gt;anúncio oficial&lt;/a&gt;, a OpenAI
diz que o GPT-5.5 está chegando ao ChatGPT e ao Codex para usuários Plus, Pro,
Business e Enterprise. No Codex, também entram Edu e Go, com janela de contexto
de &lt;strong&gt;400K&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Também existe um modo &lt;strong&gt;Fast&lt;/strong&gt;, que gera tokens mais rápido, mas custa mais. A
promessa oficial é 1.5x mais rápido por 2.5x o custo.&lt;/p&gt;
&lt;p&gt;Na API, ele ainda aparece como &amp;quot;em breve&amp;quot;. A própria
&lt;a href=&quot;https://openai.com/api/pricing/&quot;&gt;página de preços da OpenAI&lt;/a&gt; já lista o GPT-5.5
como &amp;quot;coming soon&amp;quot;, com &lt;strong&gt;US$5 por 1 milhão de tokens de entrada&lt;/strong&gt; e &lt;strong&gt;US$30 por
1 milhão de tokens de saída&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Então, sim, parece mais capaz. Também parece mais caro.&lt;/p&gt;
&lt;p&gt;Como todo bom dev, a gente comemora com uma mão e segura a carteira com a outra.&lt;/p&gt;
&lt;h2&gt;A parte de segurança&lt;/h2&gt;
&lt;p&gt;Outra parte forte do lançamento é segurança.&lt;/p&gt;
&lt;p&gt;A OpenAI publicou um
&lt;a href=&quot;https://deploymentsafety.openai.com/gpt-5-5&quot;&gt;system card do GPT-5.5&lt;/a&gt;, fala em
testes antes do lançamento, red teaming, avaliações para cyber e biologia, além
de feedback de quase 200 parceiros de acesso antecipado.&lt;/p&gt;
&lt;p&gt;Ela também abriu um
&lt;a href=&quot;https://openai.com/index/gpt-5-5-bio-bug-bounty/&quot;&gt;Bio Bug Bounty específico do GPT-5.5&lt;/a&gt;.
O escopo é bem específico: GPT-5.5 no Codex Desktop, procurando um jailbreak
universal para perguntas de segurança biológica. O prêmio principal anunciado é
de US$25.000.&lt;/p&gt;
&lt;p&gt;Traduzindo: eles também sabem que um modelo melhor em código, pesquisa e
ferramentas pode ser útil pra coisa boa e pra coisa ruim.&lt;/p&gt;
&lt;p&gt;E esse é o pedaço que mais me interessa no longo prazo. Não só &amp;quot;o modelo ficou
mais inteligente&amp;quot;, mas &amp;quot;o modelo ficou mais capaz de agir&amp;quot;. Quando entra
ferramenta, terminal, navegador, arquivo, repositório, sandbox e automação, a
conversa muda de tamanho.&lt;/p&gt;
&lt;h2&gt;Minha impressão de primeira hora&lt;/h2&gt;
&lt;p&gt;Curiosidade.&lt;/p&gt;
&lt;p&gt;Não vou fingir conclusão madura onde só existe botão recém-clicado. Quero ver se
ele realmente entende melhor projetos grandes, se respeita mais o estilo do
código, se testa direito, se não inventa moda quando eu só quero uma alteração
pequena e se consegue trabalhar sem transformar cada tarefa simples numa tese.&lt;/p&gt;
&lt;p&gt;Também quero ver uma coisa que benchmark não mede tão bem: comportamento no
fluxo real.&lt;/p&gt;
&lt;p&gt;Porque um agente pode ser muito inteligente e ainda ser chato. Pode ser capaz e
ansioso. Pode resolver um bug difícil e, cinco minutos depois, mexer onde não
precisava. Quem usa esse tipo de ferramenta todo dia sabe que &amp;quot;mais autonomia&amp;quot;
só é bom quando vem junto com bom senso.&lt;/p&gt;
&lt;p&gt;Então fica a nota: peguei o GPT-5.5 no pulo aqui no Codex.&lt;/p&gt;
&lt;p&gt;Talvez eu volte a falar dele depois de usar mais. Talvez não. Eu não costumo
fazer post sobre cada modelo novo que aparece, mas esse caiu direto na
ferramenta que eu uso pra trabalhar no blog, no código e nas automações.&lt;/p&gt;
&lt;p&gt;Aí ficou difícil ignorar.&lt;/p&gt;
&lt;p&gt;Valeu.&lt;/p&gt;
&lt;h2&gt;Fontes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://openai.com/index/introducing-gpt-5-5/&quot;&gt;OpenAI - Introducing GPT-5.5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://deploymentsafety.openai.com/gpt-5-5&quot;&gt;OpenAI - GPT-5.5 System Card&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://openai.com/api/pricing/&quot;&gt;OpenAI - API Pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://openai.com/index/gpt-5-5-bio-bug-bounty/&quot;&gt;OpenAI - GPT-5.5 Bio Bug Bounty&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Terrarium, Lazarus e benchmarks: 4 alertas sobre segurança e infraestrutura de agentes de IA</title><link>https://otaviomiranda.com.br/2026/terrarium-lazarus-e-benchmarks-4-alertas-sobre-seguranca-e-infra-de-agentes-de-ia/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/terrarium-lazarus-e-benchmarks-4-alertas-sobre-seguranca-e-infra-de-agentes-de-ia/</guid><description>Falha no Terrarium, campanha da Lazarus contra desenvolvedores, ruído em benchmarks de coding agents e a arquitetura do Managed Agents mostram onde estão os riscos reais da IA aplicada ao desenvolvimento.</description><pubDate>Thu, 23 Apr 2026 12:00:50 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Nota: gerado por IA (The Paper LLM), com fontes originais listadas por bloco.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;O lote de hoje gira em torno do mesmo problema: muita gente ainda olha para IA
pensando só no modelo, mas os riscos e os gargalos estão cada vez mais no
runtime, no sandbox, no ambiente de execução e no jeito como essas ferramentas
entram no fluxo de trabalho de quem desenvolve.&lt;/p&gt;
&lt;h2&gt;Terrarium mostra como um sandbox pode virar parte do problema&lt;/h2&gt;
&lt;p&gt;Em 21 de abril de 2026, o CERT/CC publicou a nota VU#414811 sobre uma falha no
Terrarium, plataforma de execução de código em sandbox. O problema está numa
travessia da cadeia de protótipos JavaScript dentro do ambiente Pyodide, o que
abre caminho para alcançar o &lt;code&gt;globalThis&lt;/code&gt;, acessar internals do Node.js e
executar comandos arbitrários com privilégios de root no processo hospedeiro.&lt;/p&gt;
&lt;p&gt;O ponto mais importante aqui não é só a existência da falha. É o tipo de
confiança que ela quebra. Ferramentas como o Terrarium costumam aparecer
justamente no momento em que alguém pensa: &amp;quot;beleza, vou deixar o modelo gerar
código porque isso aqui está isolado&amp;quot;. Quando a camada de isolamento falha, o
sandbox deixa de ser proteção e passa a ser mais uma superfície de ataque.&lt;/p&gt;
&lt;p&gt;O CERT/CC diz que não conseguiu coordenar uma correção com o fornecedor e lista
mitigações mais defensivas do que curativas: segmentação de rede, monitoramento
da atividade do container, controles de acesso e redução de funcionalidades
expostas. Em português claro, se você executa código não confiável, o ideal
continua sendo reduzir o raio de explosão desde o início, não presumir que o
sandbox vai segurar tudo sozinho.&lt;/p&gt;
&lt;p&gt;Fonte: &lt;a href=&quot;https://kb.cert.org/vuls/id/414811&quot;&gt;CERT/CC - VU#414811&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;A campanha descrita pela Expel leva o ataque direto para o workflow do dev&lt;/h2&gt;
&lt;p&gt;Em 22 de abril de 2026, a Expel publicou um relatório sobre um grupo que ela
rastreia com alta confiança como ligado à Coreia do Norte. O alvo principal são
desenvolvedores de Web3, mas a mecânica do ataque interessa a qualquer pessoa
que receba repositório de teste, projeto de entrevista ou prova técnica para
rodar localmente.&lt;/p&gt;
&lt;p&gt;Segundo a Expel, o grupo usa falsas ofertas de emprego e envia take-home
assessments adulterados. Em vários casos, o pacote vem com &lt;code&gt;tasks.json&lt;/code&gt;
malicioso no VS Code, configurado para disparar código assim que a pasta é
aberta. Quando isso não basta, o próprio código do projeto já traz backdoors
pensados para rodar quando a vítima tenta executar a aplicação.&lt;/p&gt;
&lt;p&gt;O detalhe que chama atenção é o uso pesado de ferramentas de IA generativa no
processo. No relatório, a Expel diz que o grupo abusa de ferramentas como Cursor
e ChatGPT para escalar a operação. O resultado não é &amp;quot;malware mágico feito por
IA&amp;quot;. É algo mais pé no chão e, por isso mesmo, mais perigoso: campanhas mais
baratas, melhor escritas e mais fáceis de multiplicar.&lt;/p&gt;
&lt;p&gt;Esse tipo de história é um bom lembrete de que abrir pasta, confiar em automação
do editor e rodar projeto de terceiros já faz parte do modelo de ameaça. Não é
só navegador, e-mail ou anexo estranho. O ambiente de desenvolvimento entrou de
vez nessa conta.&lt;/p&gt;
&lt;p&gt;Fonte:
&lt;a href=&quot;https://expel.com/blog/inside-lazarus-how-north-korea-uses-ai-to-industrialize-attacks-on-developers/&quot;&gt;Expel - Inside Lazarus: How North Korea uses AI to industrialize attacks on developers&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Benchmarks de agentes podem mudar bastante só por causa da infraestrutura&lt;/h2&gt;
&lt;p&gt;Em um texto recente de engenharia, a Anthropic argumenta que benchmarks de
coding agents podem variar vários pontos percentuais sem que o modelo mude. A
mudança vem só da configuração de infraestrutura. No experimento descrito pela
empresa, a diferença entre o setup mais restrito e o menos restrito no
Terminal-Bench 2.0 chegou a 6 pontos percentuais.&lt;/p&gt;
&lt;p&gt;Os números do texto ajudam a entender o tamanho do ruído. Em configuração
estrita, os erros de infraestrutura ficaram em 5,8%. No cenário sem limite,
caíram para 0,5%. Até certo ponto, dar mais folga de CPU e memória corrige
instabilidade que nem deveria contaminar a medição. Depois desse ponto, a
própria prova muda, porque agentes mais pesados passam a conseguir instalar
dependências grandes, abrir subprocessos caros e tentar estratégias que morrem
sob orçamento apertado.&lt;/p&gt;
&lt;p&gt;Essa distinção importa muito. Uma coisa é remover ruído operacional. Outra é
facilitar a prova sem dizer claramente que isso aconteceu. O argumento da
Anthropic é simples: leaderboard de agente não vale muita coisa sem CPU, RAM,
limite rígido, método de enforcement e janela de tempo bem descritos. Diferença
pequena demais, sem metodologia clara, merece desconfiança.&lt;/p&gt;
&lt;p&gt;Fonte:
&lt;a href=&quot;https://www.anthropic.com/engineering/infrastructure-noise&quot;&gt;Anthropic - Quantifying infrastructure noise in agentic coding evals&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Managed Agents reforça uma separação que muita stack ainda ignora&lt;/h2&gt;
&lt;p&gt;Em outro texto de engenharia, a Anthropic descreve a arquitetura do Managed
Agents como uma separação mais dura entre sessão, harness e sandbox. A ideia é
parar de tratar o agente inteiro como um container único, frágil e cheio de
acoplamentos. Em vez disso, o &amp;quot;cérebro&amp;quot; fica desacoplado das &amp;quot;mãos&amp;quot;, e cada
parte pode falhar, reiniciar ou ser trocada sem levar o resto junto.&lt;/p&gt;
&lt;p&gt;O ganho não fica só na elegância da arquitetura. A Anthropic diz que, com essa
separação, a recuperação de falhas ficou mais simples, a integração com VPCs dos
clientes melhorou e a latência até o primeiro token caiu bastante. No texto, a
empresa afirma que o p50 de time-to-first-token caiu cerca de 60%, enquanto o
p95 caiu mais de 90%.&lt;/p&gt;
&lt;p&gt;Tem também um ponto de segurança que vale prestar atenção. Quando código não
confiável roda no mesmo container que guarda credenciais e contexto, uma injeção
bem sucedida precisa atravessar bem menos barreiras para encontrar segredo
valioso. Separar harness, sessão e sandbox não elimina risco, mas corta um
atalho ruim que aparece em muita implementação apressada.&lt;/p&gt;
&lt;p&gt;Fonte:
&lt;a href=&quot;https://www.anthropic.com/engineering/managed-agents&quot;&gt;Anthropic - Scaling Managed Agents: Decoupling the brain from the hands&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Destaques rápidos&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;O texto do Martin Fowler sobre &lt;a href=&quot;https://martinfowler.com/fragments/2026-04-02.html&quot;&gt;technical, cognitive, and intent debt&lt;/a&gt; continua valendo a leitura porque põe nome num problema que muita equipe já está sentindo: não é só o código que degrada quando a automação entra sem cuidado. O entendimento do sistema e a clareza sobre o que o time realmente quis construir também começam a escorregar. Fonte: &lt;a href=&quot;https://martinfowler.com/fragments/2026-04-02.html&quot;&gt;Martin Fowler - Technical, cognitive, and intent debt&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O paper sobre &lt;a href=&quot;https://arxiv.org/abs/2604.11581&quot;&gt;hidden measurement error in LLM pipelines&lt;/a&gt; combina bem com a discussão sobre benchmarks acima. A ideia central é simples: juiz, prompt, temperatura e detalhes do pipeline conseguem distorcer avaliação a ponto de fazer diferença pequena parecer grande. Fonte: &lt;a href=&quot;https://arxiv.org/abs/2604.11581&quot;&gt;arXiv - Hidden Measurement Error in LLM Pipelines Distorts Annotation, Evaluation, and Benchmarking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Em &lt;a href=&quot;https://arxiv.org/abs/2604.19749&quot;&gt;The Tool-Overuse Illusion&lt;/a&gt;, os autores batem numa tecla que aparece com frequência em stacks de agentes: mais ferramenta nem sempre significa mais capacidade. Em bastante caso, excesso de chamada é sintoma de orquestração ruim ou planejamento fraco. Fonte: &lt;a href=&quot;https://arxiv.org/abs/2604.19749&quot;&gt;arXiv - The Tool-Overuse Illusion&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;O trabalho &lt;a href=&quot;https://arxiv.org/abs/2604.20833&quot;&gt;AVISE&lt;/a&gt; chama atenção por um motivo bem prático: segurança de IA precisa de suíte de avaliação repetível, não só demo bonita de lançamento. Ainda está cedo, mas a direção faz sentido. Fonte: &lt;a href=&quot;https://arxiv.org/abs/2604.20833&quot;&gt;arXiv - AVISE: Framework for Evaluating the Security of AI Systems&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Acompanhamento de tendências&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A fronteira de segurança está descendo a pilha. O risco aparece menos no prompt isolado e mais no editor, no sandbox, no runner, no CI e no container onde o agente realmente vive.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A discussão sobre benchmark está ficando menos glamourosa e mais útil. Infraestrutura, metodologia e critério de avaliação estão pesando tanto quanto o modelo em si.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Plataformas de agentes estão ficando com cara de produto de infraestrutura. Memória, identidade, observabilidade, isolamento e governança já não são detalhe de implementação.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Fechando o quadro&lt;/h2&gt;
&lt;p&gt;Juntando as quatro histórias, o padrão fica claro: a discussão séria sobre
agentes de IA está descendo a pilha. O modelo continua importante, claro. Mas o
estrago real e as diferenças práticas estão aparecendo no container, no editor,
no benchmark, no scheduler, no sandbox e na forma como tudo isso conversa com
código e credenciais do mundo real.&lt;/p&gt;
</content:encoded></item><item><title>Paperclip: empresa de IAs que funciona sozinha (será?)</title><link>https://otaviomiranda.com.br/2026/paperclip-empresa-de-ias-que-funciona-sozinha-sera/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/paperclip-empresa-de-ias-que-funciona-sozinha-sera/</guid><description>Subi o Paperclip no VPS com Gemini, Codex e Claude, testei hand off, gastei com heartbeat, falei de budgets e segurança. Entenda como funciona e se é para você.</description><pubDate>Mon, 20 Apr 2026 17:37:45 GMT</pubDate><content:encoded>&lt;p&gt;Subi o Paperclip no VPS com Gemini, Codex e Claude, testei hand off, gastei com
heartbeat, falei de budgets e segurança. Entenda como funciona e se é para você.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/paperclip-ai-ing.jpg&quot; alt=&quot;Paperclip Site&quot;&gt;&lt;/p&gt;
&lt;p&gt;Algo engraçado só pra constar: antes do hype no &lt;strong&gt;Paperclip&lt;/strong&gt; eu torci o nariz
para a ferramenta. E ainda penso que estava com razão. Já apareceu ferramenta
demais prometendo organizar caos, entregando só mais uma camada de caos que gera
ainda mais caos. Sempre é um wrapper em cima do que já existe que é complexo
para entender, cheio de bugs e que vai desaparecer logo.&lt;/p&gt;
&lt;p&gt;Nessa situação: ou você perde tempo com a ferramenta, ou você perde tempo com a
ferramenta (isso não foi um erro). É bem provável que as próprias empresas
(Anthropic, OpenAI, Google e outras...), vão criar ferramentas que fazem
exatamente o que o novo salvador &lt;strong&gt;do hype&lt;/strong&gt; faz.&lt;/p&gt;
&lt;p&gt;Porém, o irônico é que eu sempre penso assim na minha vida pessoal: &amp;quot;&lt;em&gt;você não
sabe o que você não sabe&lt;/em&gt;&amp;quot;. Então, prefiro ficar quieto do que falar mal de algo
que não está na minha bolha de uso.&lt;/p&gt;
&lt;p&gt;O Paperclip veio com uma proposta que eu já bati o olho e pensei: &lt;strong&gt;marketing&lt;/strong&gt;.
E realmente, veja:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Paperclip - The human control plane for AI labor.
Hire AI employees, set goals, automate jobs and
your business runs itself.

Tradução:
Paperclip - o plano de controle humano para o trabalho de IAs.
Contrate funcionários de IA, defina objetivos, automatize
tarefas e deixe sua empresa rodando sozinha.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Qual empresa do planeta vai rodar sozinha? Então você monta a sua empresa e
deixa LLMs tomando conta de tudo? Tem certeza?&lt;/p&gt;
&lt;p&gt;No entanto, bastou eu ter um problema que o &lt;strong&gt;Paperclip&lt;/strong&gt; resolvia pra eu
entender o que ele realmente faz.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Apoio - Hostinger&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Quer testar o Paperclip no seu próprio servidor? Vá de Hostinger.&lt;/p&gt;
&lt;p&gt;Acesse: &lt;a href=&quot;https://www.hostg.xyz/SHJHL&quot;&gt;www.hostg.xyz/SHJHL&lt;/a&gt;&lt;br&gt;Cupom: &lt;code&gt;OTAVIOMIRANDA&lt;/code&gt; (10% de desconto)&lt;/p&gt;
&lt;p&gt;Obrigado Hostinger por acreditar no meu conteúdo 💜.&lt;/p&gt;
&lt;h2&gt;Em vídeo&lt;/h2&gt;
&lt;p&gt;Aqui está tudo o que fiz com o &lt;strong&gt;Paperclip&lt;/strong&gt;, você pode tirar suas conclusões:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://youtu.be/Mcp3fXfFV5A&quot;&gt;&lt;img src=&quot;./images/paperclip-ainda-sou-dev.jpg&quot; alt=&quot;Vídeo de Otávio Miranda no YouTube sobre o Paperclip&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vídeo: &lt;a href=&quot;http://youtu.be/Mcp3fXfFV5A&quot;&gt;youtu.be/Mcp3fXfFV5A&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Arquivo
&lt;a href=&quot;https://gist.github.com/luizomf/502c3d6a6ae8548273aa6cc02411d78c&quot;&gt;Gist&lt;/a&gt; usado
no vídeo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Quer continuar a ler? Bora então!&lt;/p&gt;
&lt;h2&gt;Vibe Coding (pera, deixa eu explicar primeiro)&lt;/h2&gt;
&lt;p&gt;No meu dia a dia eu não fico brincando de &lt;em&gt;vibe coding&lt;/em&gt; o tempo todo. Sou do
tipo que fala: &lt;strong&gt;escreva um teste antes de tocar no código que tenha X
comportamento. Abra o arquivo Y, na linha 80 e edite a função Z. Faça o teste
passar&lt;/strong&gt;. E depois ainda reviso linha a linha o que foi feito.&lt;/p&gt;
&lt;p&gt;Mas é bom acompanhar as tendências e ver para qual rumo o nosso ramo está
migrando.&lt;/p&gt;
&lt;p&gt;Fazia bastante tempo que eu queria criar um agregador de notícias (um feed RSS)
com LLM. Eu já tinha a ideia inteira desenhada na cabeça: buscar as notícias,
injetar dados no modelo de forma dinâmica de acordo com o feedback das próprias
notícias, e gerar interesses bem específicos sobre o que eu gosto e o que eu não
gosto.&lt;/p&gt;
&lt;p&gt;Como eu queria validar a ideia primeiro antes de me comprometer a desenvolver
isso, decidi fazer por &lt;strong&gt;Vibe Coding&lt;/strong&gt;. Usei o &lt;strong&gt;Codex&lt;/strong&gt;, &lt;strong&gt;Claude&lt;/strong&gt; e o
&lt;strong&gt;Gemini&lt;/strong&gt; em várias ocasiões.&lt;/p&gt;
&lt;p&gt;O projeto foi crescendo com um &amp;quot;adiciona isso&amp;quot;, outro &amp;quot;adiciona aquilo&amp;quot;, outro
&amp;quot;e se a gente colocasse isso?&amp;quot;, e assim foi. Hoje ele até lê as notícias em voz
alta e já saiu totalmente do meu controle. O último recurso que usei nele foi o
&amp;quot;Claude Design&amp;quot;. O negócio ficou &lt;strong&gt;MARAVILHOSO&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Enfim, um belo dia eu precisava mexer em várias partes do programa. Ajustar
interface, corrigir um bug no TTS, ajustar o daemon e algumas outras coisas.
Como iniciei este projeto bem estruturado (quer dizer, eu disse para os modelos
trabalharem com testes e boa arquitetura), percebi que nenhuma das tarefas em
que eu precisava mexer tinha overlap. Cada parte do software estava separada na
sua própria camada e ninguém precisaria mexer no mesmo arquivo do coleguinha.&lt;/p&gt;
&lt;p&gt;Abri 5 agentes. Quatro ficaram codando, e um deles ficou comigo só ajudando a
verificar se ninguém estava fazendo nada errado. Claude Opus, Claude Sonnet,
Codex App com GPT 5.4, outro Codex App com GPT 5.4 e o Gemini 3.1 Pro no
Antigravity.&lt;/p&gt;
&lt;p&gt;Se você já fez isso, vai entender perfeitamente o que vou falar agora: às vezes
eu precisava falar com um agente para testar algum recurso. Nesse tempo, os
outros todos terminavam o serviço e eu não via. Isso era perda de tempo, já que
eu mesmo não estava revisando código, só testando a implementação.&lt;/p&gt;
&lt;p&gt;Então lembrei do &lt;strong&gt;Paperclip&lt;/strong&gt;. E a ideia dele começou a fazer mais sentido pra
mim. Como ele mostra todos os agentes em uma única interface, fica bem mais
fácil gerenciar tudo. De fato, você consegue manter todos trabalhando usando
dois recursos que ele tem: &lt;strong&gt;hand off por menção&lt;/strong&gt; e &lt;strong&gt;heartbeat&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;O Hand Off é igual você marcar alguém em uma rede social. Um agente pode fazer
&lt;code&gt;@CTO&lt;/code&gt; (por exemplo) e indicar para o &lt;code&gt;@CTO&lt;/code&gt; que ele terminou o serviço. O
&lt;code&gt;@CTO&lt;/code&gt; acorda e vai conferir sem você colocar a mão em nada.&lt;/p&gt;
&lt;p&gt;Heartbeat é um loop que fica rodando de tempos em tempos (você escolhe). Assim
que chegar o momento do Heartbeat, o agente é obrigado a acordar, verificar o
que tem pra fazer e agir. Essa função é mais útil pra quem realmente não quer
ficar vigiando os agentes. Um exemplo disso foi quando eu queria testar se eles
fariam um site sozinhos, mas já estava pingando de sono. Simplesmente ativei o
Heartbeat do meu &lt;code&gt;@CEO&lt;/code&gt;, deixei instruções para ele sobre o que fazer e fui
dormir.&lt;/p&gt;
&lt;p&gt;No outro dia eu tava com tudo pronto e funcionando.&lt;/p&gt;
&lt;h2&gt;Evite gastar tokens demais&lt;/h2&gt;
&lt;p&gt;Como o tutorial completo está no vídeo, vou focar mais nas partes importantes
que você precisa ter em mente nesse texto.&lt;/p&gt;
&lt;p&gt;Se você montar uma empresa como indicam internet afora, vai gastar tokens como
nunca gastou na vida.&lt;/p&gt;
&lt;p&gt;Eu fiz isso e, sem ter feito absolutamente nada além do &amp;quot;Onboarding&amp;quot;
(contratações e configurações), gastei &lt;strong&gt;85 Milhões&lt;/strong&gt; de tokens. Claro, isso
conta cache, input e output tudo somado.&lt;/p&gt;
&lt;p&gt;Existem várias coisas no &lt;strong&gt;Paperclip&lt;/strong&gt; que você deve ficar de olho. Alguns
exemplos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Usar os CLIs oficiais (como Claude Code, Codex CLI e Gemini CLI) gasta
absurdamente mais tokens. Minha teoria é que os CLIs oficiais carregam pelo
menos 20k tokens a cada wake-up do agente.&lt;/li&gt;
&lt;li&gt;Deixar o Heartbeat ativado sem ter o que fazer faz o agente acordar, gastar
muitos tokens para ter contexto e perceber que não tem nada pra fazer. Só
deixe o Heartbeat ativo se realmente tiver algo para fazer.&lt;/li&gt;
&lt;li&gt;Issues muito longas que se arrastam infinitamente. Em vez de ficar insistindo
na mesma issue, feche-a e abra uma nova para qualquer micro ajuste.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Os agentes do &lt;strong&gt;Paperclip&lt;/strong&gt; funcionam com contexto zerado. Isso significa que
toda vez que eles acordam, é necessário injetar todo o contexto para que o
agente possa fazer o serviço.&lt;/p&gt;
&lt;p&gt;Agora pense:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;20k tokens do CLI oficial&lt;/li&gt;
&lt;li&gt;20k só do skill do Paperclip&lt;/li&gt;
&lt;li&gt;100k tokens da sua issue gigante&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Só aqui, estamos falando de 140k tokens. Se o seu Heartbeat estiver configurado
para 1 hora, em 24 horas você gastaria mais de 3 milhões de tokens sem fazer
absolutamente nada. Só o seu agente acordando e dormindo. Se você fizer a
burrice que eu fiz, colocar 5 agentes, todos com heartbeat de 1 hora,
multiplique o gasto por 5.&lt;/p&gt;
&lt;p&gt;O modo econômico é: use o &lt;code&gt;pi&lt;/code&gt;, deixe o Heartbeat desativado (até entender como
funciona), deixe issues pequenas. Não crie uma hierarquia muito profunda. Se
possível nem crie hierarquia. Vá direto no agente que precisar.&lt;/p&gt;
&lt;h2&gt;Quando vale a pena?&lt;/h2&gt;
&lt;p&gt;Na minha concepção, só vale a pena se você faz vibe coding.&lt;/p&gt;
&lt;p&gt;Isso aqui vai multiplicar sua velocidade em 10x (ou na quantidade de agentes que
seu servidor e seu bolso aguentarem).&lt;/p&gt;
&lt;p&gt;Faça o teste e me diga. Esse é literalmente o caso de você estar dormindo e seus
agentes trabalhando por você.&lt;/p&gt;
&lt;h2&gt;Onde eu não acho que isso vale a pena&lt;/h2&gt;
&lt;p&gt;Se você é dev, não faz sentido. O motivo é simples: um dev é responsável pelo
seu código. Você vai mandar os agentes fazerem o seu código e assinar embaixo?
Se for, então use.&lt;/p&gt;
&lt;p&gt;Estou pensando mais no dev que trabalha para empresas mais sérias. Se você
precisa revisar o código com cuidado, fica bem difícil usar mais de um agente.&lt;/p&gt;
&lt;p&gt;Na prática, a quantidade de código pode ficar inviável de revisar direito.&lt;/p&gt;
&lt;p&gt;Outro ponto que percebi também. Não acho que o &lt;strong&gt;Paperclip&lt;/strong&gt; seja a melhor
ferramenta do mundo pra microajuste fino.&lt;/p&gt;
&lt;p&gt;Se eu quero mexer num espaçamento, trocar uma fonte, arrumar um detalhe visual
ou fazer uma mudança pequena e direta, muitas vezes compensa mais usar um fluxo
simples. Só funciona bem para trabalho em larga escala que pode ser feito por
vários agentes ao mesmo tempo.&lt;/p&gt;
&lt;p&gt;E faz sentido. Se o agente não tem contexto e recebe isso tudo de volta a cada
wake-up, cada vez que você falar pra ele &amp;quot;aumentar a margem&amp;quot; de uma página, você
multiplica a quantidade de tokens que gastou na última rodada. O pior disso é
que você pode ter mais de um agente por issue. Aí é pedir pra torrar dinheiro.&lt;/p&gt;
&lt;h2&gt;O que eu levei dessa experiência&lt;/h2&gt;
&lt;p&gt;Uma coisa que ficou muito clara pra mim foi o modelo de funcionamento do
Paperclip. A parte do Heartbeat já existia no OpenClaw. Porém, o OpenClaw é um
chat normal.&lt;/p&gt;
&lt;p&gt;O Paperclip não tem chat. Você precisa especificar muito bem o que quer na issue
inicial. Isso te força a pensar muito mais antes de enviar um prompt qualquer
para a IA.&lt;/p&gt;
&lt;p&gt;Nada de &amp;quot;Oi, tudo bem?&amp;quot;, nada de &amp;quot;vou te mandar o contexto&amp;quot;. No Paperclip você
precisa injetar tudo o que for necessário para a tarefa na issue inicial. Do
contrário a issue vira chat e você vai pagar bem caro em tokens.&lt;/p&gt;
&lt;p&gt;Hand off também achei genial. A ideia de um agente passar o bastão para o outro
é muito interessante. Exemplo: você especializa um agente em fazer o código,
outro em garantir bons testes. Um pode passar para o outro assim que concluir o
serviço. &amp;quot;Pode começar a testar&amp;quot; ou o contrário (TDD), &amp;quot;Testes prontos, pode
montar o código&amp;quot;.&lt;/p&gt;
&lt;p&gt;Em resumo: estou de olho nos prompts, no Heartbeat, no Hand Off e na interface
maravilhosa que o Paperclip tem. Acho que é isso (no vídeo falo bem mais,
assiste lá).&lt;/p&gt;
&lt;p&gt;Valeu, e até o próximo.&lt;/p&gt;
</content:encoded></item><item><title>Guia de sobrevivência Bash e Shell Script (Sério!)</title><link>https://otaviomiranda.com.br/2026/guia-de-sobrevivencia-bash-e-shell-script-serio-esse-bom-mesmo/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/guia-de-sobrevivencia-bash-e-shell-script-serio-esse-bom-mesmo/</guid><description>Este é um dos posts mais completos sobre Bash e Shell Script que você vai encontrar. Vamos estudar juntos coisas básicas e avançadas para ficarmos ninjas em shell scripting.</description><pubDate>Fri, 10 Apr 2026 19:09:38 GMT</pubDate><content:encoded>&lt;p&gt;Esse vai para devs que já sabem o básico do Shell Script e Bash e querem subir
de nível. Me incluo nisso também. De fato, só estou escrevendo isso porque tenho
estudado muito sobre o assunto.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/bash.jpg&quot; alt=&quot;Logo do Bash&quot;&gt;&lt;/p&gt;
&lt;p&gt;Você vai ler por sua conta e risco. Perceba que o autor desse post não tem
nenhum desses nomes: &lt;strong&gt;Erich Gamma&lt;/strong&gt;, &lt;strong&gt;Richard Helm&lt;/strong&gt;, &lt;strong&gt;Ralph Johnson&lt;/strong&gt; e
&lt;strong&gt;John Vlissides&lt;/strong&gt;. Então você está vivendo perigosamente.&lt;/p&gt;
&lt;p&gt;Tá parei!&lt;/p&gt;
&lt;p&gt;Não sei se foi assim com você também, mas eu tive um caso com Bash e Shell
Script.&lt;/p&gt;
&lt;p&gt;Como tinha acesso ao root do servidor, depois que aprendi &lt;code&gt;#!/bin/bash&lt;/code&gt; e
&lt;code&gt;echo &amp;quot;hello world&amp;quot;&lt;/code&gt;, fui só ladeira abaixo.&lt;/p&gt;
&lt;p&gt;Eu te juro que lembro de pesquisar no Google algo como: &amp;quot;&lt;em&gt;Por que alguns scripts
têm &lt;code&gt;#!/bin/bash&lt;/code&gt; e outros &lt;code&gt;#!/bin/sh&lt;/code&gt;&lt;/em&gt;&amp;quot;. (pode ir pesquisar, te espero)&lt;/p&gt;
&lt;p&gt;Se você está estranhando o acesso ao servidor, antigamente usávamos tudo direto
mesmo (pelo menos EU, usava). Digitava &lt;code&gt;ssh user@host&lt;/code&gt; e usava um programa
chamado &lt;strong&gt;FileZilla&lt;/strong&gt; na porta &lt;code&gt;21&lt;/code&gt; (seco, inseguro, direto). Segurança? 🤬&lt;/p&gt;
&lt;p&gt;Mas estou divagando.&lt;/p&gt;
&lt;p&gt;As coisas mudaram (um pouco). Melhorei meu conhecimento (um pouco, também) e
sigo estudando. Então considere que você está lendo minhas notas de estudo.
Espero que elas sejam úteis pra você, como estão sendo pra mim.&lt;/p&gt;
&lt;p&gt;Do que pretendo falar? (caramba velho, você já está no artigo, só rolar, mas...)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Padrões de tratamento de erro que separam script de brinquedo de script de
produção&lt;/li&gt;
&lt;li&gt;Técnicas de manipulação de variáveis que substituem vários dos seus hábitos
com &lt;code&gt;sed&lt;/code&gt; e &lt;code&gt;awk&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mistérios de controle de processo que causam o famoso bug do &amp;quot;ué, por que
minha variável ficou vazia?&amp;quot;&lt;/li&gt;
&lt;li&gt;Padrões do mundo real pra parsing de argumentos, config, logs e testes&lt;/li&gt;
&lt;li&gt;Armadilhas de segurança e como não ser atropelado por elas&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eu poderia muito bem corrigir as frases acima. Elas não têm meu tom de escrita.
Mas vou manter do jeito que veio da IA por alguns motivos.&lt;/p&gt;
&lt;p&gt;Primeiro, este post está sendo escrito faz um tempo e eu não lembrava o que
tinha escrito. Segundo, eu não sabia falar tecnicamente o que eram cada uma das
coisas que escrevi. Então, fique com uma pitada de IA neste texto.&lt;/p&gt;
&lt;p&gt;Eu já enrolei e brinquei demais. E, não comenta com ninguém, mas faço isso pra
espantar público desqualificado. Aquele povo só queria copiar algo daqui e sair.
Prefiro você, que está disposto a aprender assim como eu.&lt;/p&gt;
&lt;p&gt;Já que eles já foram, então agora temos trabalho.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;Fundamentos e redes de segurança: escrevendo scripts Bash sem susto&lt;/h1&gt;
&lt;p&gt;Essa parte aqui é o kit de sobrevivência. Não é a parte &amp;quot;olha como eu sou
avançado&amp;quot;. É mais como um básico inicial, mas essencial.&lt;/p&gt;
&lt;h2&gt;Tratamento de erro sem autoengano&lt;/h2&gt;
&lt;p&gt;Considero o script abaixo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
cd /tmp/build
rm -rf *
cp -r /src/output/* .
tar czf release.tar.gz *
echo &amp;quot;Pronto!&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enquanto tudo está bonito, ele funciona.&lt;/p&gt;
&lt;p&gt;O problema aparece no dia em que &lt;code&gt;/tmp/build&lt;/code&gt; não existe. Nesse caso, o &lt;code&gt;cd&lt;/code&gt;
falha e o &lt;code&gt;rm -rf *&lt;/code&gt; roda no diretório errado. Parabéns, agora você apagou tudo
do diretório atual. Seu release vai ficar lindo!&lt;/p&gt;
&lt;p&gt;O primeiro freio de mão costuma ser este:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;set -euo pipefail
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ajuda bastante. Mas eu acabei descobrindo que isso não resolve tudo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;set -e&lt;/code&gt; (errexit)&lt;/strong&gt; manda o script parar quando um comando falha. Show! Só
que ele tem alguns comportamentos estranhos.&lt;/p&gt;
&lt;p&gt;Um dos meus favoritos é este aqui:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Isso engole erros silenciosamente:
local result=$(might_fail)

# Isso captura o erro direito:
local result
result=$(might_fail)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Até ontem, as duas linhas acima não teriam diferença na minha cabeça.&lt;/p&gt;
&lt;p&gt;No primeiro caso, o &lt;code&gt;local&lt;/code&gt; devolve &lt;code&gt;0&lt;/code&gt; e esconde a falha do comando. Você segue
com uma variável vazia e ainda acha que está tudo certo.&lt;/p&gt;
&lt;p&gt;Outra pegadinha estranha: &lt;code&gt;set -e&lt;/code&gt; fica meio &amp;quot;desligado&amp;quot; em alguns contextos,
como comandos usados em &lt;code&gt;if&lt;/code&gt;. Então não confia cegamente nele. Entende onde ele
ajuda e onde ele faz pose.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;set -u&lt;/code&gt; (nounset)&lt;/strong&gt; transforma variável não definida em erro. Sem isso,
&lt;code&gt;$UNSET_VAR&lt;/code&gt; vira string vazia e a festa continua. Em Bash antigo, arrays vazios
podem dar umas respostas mais esquisitas, então vale testar se você ainda
precisa suportar versão jurássica. A maioria dos servidores modernos suporta
versões novas do Bash (e para de ficar mantendo script legado, a IA resolve eles
pra você em segundos).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;set -o pipefail&lt;/code&gt;&lt;/strong&gt; corrige outro clássico: pipeline que &amp;quot;passa&amp;quot; porque só o
último comando foi bem. Sem ele, um &lt;code&gt;curl&lt;/code&gt; pode falhar e o &lt;code&gt;jq&lt;/code&gt; ainda sair com
cara de inocente. Só lembra que &lt;code&gt;head -n1&lt;/code&gt; e amigos podem encerrar o pipe cedo e
gerar &lt;code&gt;SIGPIPE&lt;/code&gt; no processo anterior. Quando isso for esperado, trate o caso de
forma explícita.&lt;/p&gt;
&lt;h3&gt;O modo estrito moderno&lt;/h3&gt;
&lt;p&gt;Se eu pudesse escolher, prefiro já começar assim (mas não olhe meus dotfiles, só
confia no que falo 🤣):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bash
set -Euo pipefail
shopt -s inherit_errexit  # Bash 4.4+
trap &amp;#39;s=$?; echo &amp;quot;$0: Erro na linha $LINENO: $BASH_COMMAND&amp;quot; &amp;gt;&amp;amp;2; exit $s&amp;#39; ERR
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Os extras fazem diferença.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;set -E&lt;/code&gt; ajuda o &lt;code&gt;trap ERR&lt;/code&gt; a alcançar função e subshell.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inherit_errexit&lt;/code&gt; evita outra daquelas &amp;quot;gentilezas&amp;quot; do Bash com &lt;code&gt;$(...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;trap&lt;/code&gt; pelo menos te diz onde deu pepino, em vez de deixar só um silêncio
constrangedor e &amp;quot;&lt;em&gt;indebugável&lt;/em&gt;&amp;quot; (não copia, acabei de inventar a palavra).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Olha o contraste:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
DATA=$(curl -s https://api.example.com/data)
echo &amp;quot;$DATA&amp;quot; | jq &amp;#39;.users[] | .email&amp;#39; &amp;gt; emails.txt
wc -l emails.txt
echo &amp;quot;Exportacao concluida&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se a API estiver fora, esse script ainda consegue terminar sorrindo e dizendo
&amp;quot;exportação concluída&amp;quot;. Maravilhoso. Assim você esperava...&lt;/p&gt;
&lt;p&gt;Talvez assim fique melhor. Faça o teste:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bash
set -Euo pipefail
shopt -s inherit_errexit
trap &amp;#39;s=$?; echo &amp;quot;FALHOU na linha $LINENO: $BASH_COMMAND (saida $s)&amp;quot; &amp;gt;&amp;amp;2; exit $s&amp;#39; ERR

DATA=$(curl -sf https://api.example.com/data)
echo &amp;quot;$DATA&amp;quot; | jq -e &amp;#39;.users[] | .email&amp;#39; &amp;gt; emails.txt
LINES=$(wc -l &amp;lt; emails.txt)
echo &amp;quot;Exportacao concluida: $LINES emails&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;curl -f&lt;/code&gt; para de fingir que HTTP quebrado é sucesso. &lt;code&gt;jq -e&lt;/code&gt; também ajuda a
falhar quando o resultado vem vazio ou &lt;code&gt;null&lt;/code&gt;. E o &lt;code&gt;trap&lt;/code&gt; te devolve um erro que
dá pra investigar sem precisar abrir uma sessão espírita.&lt;/p&gt;
&lt;h2&gt;POSIX vs recursos específicos do Bash (ou zsh)&lt;/h2&gt;
&lt;p&gt;Antes de usar array, &lt;code&gt;[[ ]]&lt;/code&gt;, &lt;code&gt;mapfile&lt;/code&gt; e companhia, decide uma coisa: seu
script é &lt;code&gt;sh&lt;/code&gt; ou é Bash? Quando não temos experiencia com isso, achamos que
shell é tudo igual.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;#!/bin/sh&lt;/code&gt; não significa &amp;quot;shell genérico mágico&amp;quot;. No macOS, costuma cair num
Bash 3.2 antigo. Em Debian e Ubuntu, normalmente é &lt;code&gt;dash&lt;/code&gt;. Em Alpine, geralmente
é &lt;code&gt;ash&lt;/code&gt;. Cada um tem suas manias, e nenhum deles jura compatibilidade com os
bashismos que a gente usa sem pensar.&lt;/p&gt;
&lt;p&gt;O bug clássico é este: você testa localmente com Bash, sobe a mesma coisa num
container Alpine e toma um &lt;code&gt;Syntax error: &amp;quot;(&amp;quot; unexpected&lt;/code&gt; na cara.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
# Parece inocente. Quebra em praticamente qualquer lugar que nao seja bash.

NAMES=(&amp;quot;Alice&amp;quot; &amp;quot;Bob&amp;quot; &amp;quot;Charlie&amp;quot;)
for name in &amp;quot;${NAMES[@]}&amp;quot;; do
    if [[ &amp;quot;$name&amp;quot; == &amp;quot;Bob&amp;quot; ]]; then
        echo &amp;quot;Bob encontrado!&amp;quot;
    fi
done

MSG=&amp;quot;Hello\tWorld&amp;quot;
echo -e &amp;quot;$MSG&amp;quot;

curl -s https://example.com &amp;amp;&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aí já tem array, &lt;code&gt;[[ ]]&lt;/code&gt;, &lt;code&gt;echo -e&lt;/code&gt; e &lt;code&gt;&amp;amp;&amp;gt;/dev/null&lt;/code&gt;. Tudo coisa que pode
explodir ou se comportar diferente fora do Bash.&lt;/p&gt;
&lt;p&gt;Se a ideia for ficar em POSIX, algo assim já é bem mais honesto:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/sh
set -- &amp;quot;Alice&amp;quot; &amp;quot;Bob&amp;quot; &amp;quot;Charlie&amp;quot;
for name in &amp;quot;$@&amp;quot;; do
    case &amp;quot;$name&amp;quot; in
        Bob) echo &amp;quot;Bob encontrado!&amp;quot; ;;
    esac
done

printf &amp;#39;Hello\tWorld\n&amp;#39;

curl -s https://example.com &amp;gt;/dev/null 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vou falar dele mais adiante, mas o &lt;code&gt;shellcheck&lt;/code&gt; pode te ajudar a escrever
scripts que funciona de acordo com o
&lt;a href=&quot;https://pt.wikipedia.org/wiki/Shebang&quot;&gt;shebang&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;O jeito que eu tenho usado é simples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Se o shebang é &lt;code&gt;#!/bin/sh&lt;/code&gt;, escreve POSIX sem jeitinho.&lt;/li&gt;
&lt;li&gt;Se você quer recurso do Bash, assume isso logo e usa &lt;code&gt;#!/usr/bin/env bash&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Também tenho me aventurado no Mac com &lt;code&gt;#!/usr/bin/env zsh&lt;/code&gt; (e já munda muito
do Bash para o zsh).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pra testar, &lt;code&gt;shellcheck -s sh&lt;/code&gt; e &lt;code&gt;dash script.sh&lt;/code&gt; já evitam bastante dor de
cabeça.&lt;/p&gt;
&lt;h2&gt;ShellCheck ajuda bastante&lt;/h2&gt;
&lt;p&gt;Se eu tivesse que te recomendar uma ferramenta só pra escrever shell script
menos torto, seria o &lt;a href=&quot;https://www.shellcheck.net/&quot;&gt;ShellCheck&lt;/a&gt;. Ele não pega só
estilo. Ele pega bug que morde.&lt;/p&gt;
&lt;p&gt;Um aviso famoso é o &lt;code&gt;SC2115&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# ShellCheck avisa: SC2115
# Use &amp;quot;${var:?}&amp;quot; para garantir que isso nunca expanda para /*
rm -rf &amp;quot;$STEAMROOT/&amp;quot;*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se &lt;code&gt;$STEAMROOT&lt;/code&gt; vier vazio, isso pode virar &lt;code&gt;rm -rf /*&lt;/code&gt;. Não é paranoia. Já
aconteceu com o cliente Linux da Steam. A forma segura é exigir que a variável
exista de verdade com &lt;code&gt;${STEAMROOT:?}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Cinco avisos que vivem aparecendo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SC2086&lt;/strong&gt;: coloca aspas nas variáveis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SC2155&lt;/strong&gt;: separa declaração de atribuição.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SC2164&lt;/strong&gt;: não trata &lt;code&gt;cd&lt;/code&gt; como decoração.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SC2046&lt;/strong&gt;: coloca aspas em substituição de comando também.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SC2162&lt;/strong&gt;: usa &lt;code&gt;read -r&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Se quiser integrar no projeto:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# .shellcheckrc
shell=bash
enable=all
severity=style
source-path=SCRIPTDIR
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Às vezes também vem warning chato, aí eu só desativo mesmo. Às vezes ele não
enxerga o arquivo que você está usando para &lt;code&gt;source&lt;/code&gt; e eu também não estou muito
animado a configurar (nem sei se dá pra configurar).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# shellcheck disable=SC1091
. &amp;quot;${HOME}/dotfiles/zsh/config/functions&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No CI, eu faria algo assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: ShellCheck
  run: find . -name &amp;#39;*.sh&amp;#39; -print0 | xargs -0 shellcheck --severity=warning
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E no &lt;code&gt;pre-commit&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# .pre-commit-config.yaml
repos:
  - repo: https://github.com/koalaman/shellcheck-precommit
    rev: v0.10.0
    hooks:
      - id: shellcheck
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Quando NÃO usar Bash&lt;/h2&gt;
&lt;p&gt;Bash é ótimo como cola. Como linguagem de propósito geral, pode ser mais
trabalhoso do que o necessário.&lt;/p&gt;
&lt;p&gt;Eu gosto de Bash quando a tarefa é:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ligar comando com comando&lt;/li&gt;
&lt;li&gt;mover arquivo, chamar processo, organizar pipeline&lt;/li&gt;
&lt;li&gt;fazer automação curta&lt;/li&gt;
&lt;li&gt;ser entrypoint, script de deploy ou etapa de CI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eu já começo a desconfiar quando aparece isso aqui:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;estrutura de dados mais séria&lt;/li&gt;
&lt;li&gt;parsing de JSON, YAML ou XML virando rotina&lt;/li&gt;
&lt;li&gt;HTTP com retry, autenticação e tratamento de erro mais chato&lt;/li&gt;
&lt;li&gt;muito &lt;code&gt;awk&lt;/code&gt; e &lt;code&gt;sed&lt;/code&gt; segurando a arquitetura nas costas&lt;/li&gt;
&lt;li&gt;regra de negócio crescendo dentro do shell&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Não existe número mágico, mas quando um script começa a crescer e a lógica vai
ficando mais cheia de desvio, vale parar e perguntar: &amp;quot;isso aqui ainda é Bash ou
eu só estou teimando?&amp;quot;.&lt;/p&gt;
&lt;p&gt;Às vezes 200 linhas em Bash ficam ótimas. Às vezes 50 já passaram da conta. O
critério, pra mim, é menos tamanho e mais sofrimento.&lt;/p&gt;
&lt;p&gt;Hoje, eu tendo a gostar mais do script Bash que sabe seu lugar: orquestra bem,
falha cedo e passa o trabalho pesado pra ferramenta certa. Python é um ótimo
substituto do Bash para lógicas mais complexas, mas aí já envolve subir o
interpretador. Você escolhe.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;Variáveis e expansões avançadas: além de &lt;code&gt;$var&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;Muita gente aprende &lt;code&gt;name=&amp;quot;world&amp;quot;&lt;/code&gt; e &lt;code&gt;echo &amp;quot;$name&amp;quot;&lt;/code&gt; e para ali. Eu também fiquei
um bom tempo nessa. Só que Bash começa a ficar menos sofrido quando você usa as
expansões direito e para de abrir subprocesso por qualquer coisinha.&lt;/p&gt;
&lt;h2&gt;Arrays: pare de fingir com string separada por espaço&lt;/h2&gt;
&lt;p&gt;Isso aqui é a minha cara:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;files=&amp;quot;file1.txt file2.txt file3.txt&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;... mas em minha defesa, eu nem sabia que o Bash tinha arrays. Olha só:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Declaracao
files=(&amp;quot;report Q1.csv&amp;quot; &amp;quot;data 2024.json&amp;quot; &amp;quot;notes.txt&amp;quot;)

# Iteracao - o jeito mais seguro
for f in &amp;quot;${files[@]}&amp;quot;; do
    echo &amp;quot;Processando: $f&amp;quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse &lt;code&gt;&amp;quot;${files[@]}&amp;quot;&lt;/code&gt; com aspas não é frescura. É o que impede &lt;code&gt;report Q1.csv&lt;/code&gt; de
virar dois argumentos.&lt;/p&gt;
&lt;p&gt;Se precisar de mapa, tem array associativo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;declare -A http_codes=(
    [200]=&amp;quot;OK&amp;quot;
    [404]=&amp;quot;Not Found&amp;quot;
    [500]=&amp;quot;Internal Server Error&amp;quot;
)

echo &amp;quot;${http_codes[404]}&amp;quot;  # Not Found
echo &amp;quot;${!http_codes[@]}&amp;quot;   # Chaves: 200 404 500
echo &amp;quot;${#http_codes[@]}&amp;quot;   # Quantidade: 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sem &lt;code&gt;declare -A&lt;/code&gt;, o Bash faz o favor de interpretar chave string como expressão
aritmética. Adivinha o tipo de bug bonito que sai daí.&lt;/p&gt;
&lt;p&gt;Exemplo mais útil:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;declare -A status_counts

while IFS= read -r line; do
    # Extrai o status HTTP (quinto campo do common log format)
    status=&amp;quot;${line##* \&amp;quot; }&amp;quot;
    status=&amp;quot;${status%% *}&amp;quot;
    (( status_counts[$status]++ ))
done &amp;lt; access.log

# Relatorio
for code in &amp;quot;${!status_counts[@]}&amp;quot;; do
    printf &amp;quot;%-6s %d\n&amp;quot; &amp;quot;$code&amp;quot; &amp;quot;${status_counts[$code]}&amp;quot;
done | sort -t&amp;#39; &amp;#39; -k2 -rn
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quando o dado vai continuar vivo dentro do script, isso costuma ser melhor do
que transformar tudo em texto e depois parsear de novo.&lt;/p&gt;
&lt;p&gt;Pra carregar arquivo em array:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Seguro: lida com espacos e nao faz glob
mapfile -t lines &amp;lt; input.txt

# Tambem funciona (alternativa para bash 3.x)
IFS=$&amp;#39;\n&amp;#39; read -r -d &amp;#39;&amp;#39; -a lines &amp;lt; input.txt || true

# Evite isto: globbing + word splitting destroem seus dados
lines=( $(cat input.txt) )    # NAO FACA ISSO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mapfile&lt;/code&gt; resolve isso. O &lt;code&gt;$(cat file)&lt;/code&gt; é o tipo de coisa que parece funcionar
até o dia em que a entrada vem com espaço, &lt;code&gt;*&lt;/code&gt; ou qualquer outra surpresinha
simpática.&lt;/p&gt;
&lt;h2&gt;Expansão de parâmetros&lt;/h2&gt;
&lt;p&gt;Aqui começa a parte divertida do Bash. Não divertida no sentido &amp;quot;uau, que
linguagem elegante&amp;quot;. Divertida no sentido &amp;quot;pera, eu realmente não preciso abrir
um processo pra isso?&amp;quot;.&lt;/p&gt;
&lt;p&gt;Só pra constar, eu não sei isso de cor. Toda vez que preciso de algo assim:
Google. O problema é você não conhecer e nem saber o que pesquisar. Olha que
massa:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;filepath=&amp;quot;/home/deploy/app/config.tar.gz&amp;quot;

echo &amp;quot;${filepath##*/}&amp;quot;    # config.tar.gz  (basename, remove prefixo de forma gulosa)
echo &amp;quot;${filepath%/*}&amp;quot;     # /home/deploy/app  (dirname, remove sufixo de forma curta)
echo &amp;quot;${filepath%%.*}&amp;quot;    # /home/deploy/app/config  (remove TODAS as extensoes)
echo &amp;quot;${filepath%.gz}&amp;quot;    # /home/deploy/app/config.tar  (remove so a ultima extensao)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sua colinha de bolso:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#&lt;/code&gt; corta da esquerda&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%&lt;/code&gt; corta da direita&lt;/li&gt;
&lt;li&gt;uma vez = menor correspondência&lt;/li&gt;
&lt;li&gt;dobrado = maior correspondência&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Renomear extensão fica simples. E eu estava precisando disso agora mesmo pra
fazer isso:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Agora suas imagens .png com fundo transparente tem um fundo preto.
# De nada 🥸 (a pasta mess aí é pra me lembrar de limpar minha própria bagunça)
for f in &amp;quot;${HOME}&amp;quot;/Desktop/mess/fotos/*.png; do
    ########################## só isso pra trocar a extensão   ↓     ↓
    magick &amp;quot;$f&amp;quot; -background black -alpha remove -alpha off &amp;quot;${f%.png}.jpg&amp;quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Busca e substituição também:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;msg=&amp;quot;ERROR: connection failed: timeout: host=db.prod&amp;quot;

echo &amp;quot;${msg//:/ -}&amp;quot;          # Substitui todos os : por -
echo &amp;quot;${msg//:/}&amp;quot;            # Remove todos os :
echo &amp;quot;${msg/#ERROR/WARN}&amp;quot;    # Substitui so se estiver no inicio
echo &amp;quot;${msg/%prod/staging}&amp;quot;  # Substitui so se estiver no fim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E tem as expansões de default, que eu uso bastante. Você seria obrigado a usar
isso se fosse fazer um script direito, como vimos anteriormente. Se uma variável
não tiver valor, dá erro e ele nem roda. Isso resolve:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# ${var:-default} - usa o default se var estiver vazia OU nao definida
db_host=&amp;quot;${DB_HOST:-localhost}&amp;quot;

# ${var:=default} - mesma coisa, mas TAMBEM atribui o valor a var
: &amp;quot;${CACHE_TTL:=3600}&amp;quot;  # Define CACHE_TTL se ainda nao existir

# ${var:+alternate} - usa alternate so se var EXISTIR e nao estiver vazia
auth_header=&amp;quot;${API_TOKEN:+Authorization: Bearer $API_TOKEN}&amp;quot;

# ${var:?message} - sai com erro se var estiver vazia ou nao definida
: &amp;quot;${DATABASE_URL:?DATABASE_URL precisa estar definida}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;:=&lt;/code&gt; é útil pra inicializar. &lt;code&gt;:?&lt;/code&gt; é ótimo pra falhar cedo sem escrever aquele
bloco &lt;code&gt;if [ -z ... ]&lt;/code&gt; pela milésima vez.&lt;/p&gt;
&lt;p&gt;Se você estiver num Bash 4+, ainda ganha conversão de caixa:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;name=&amp;quot;john DOE&amp;quot;
echo &amp;quot;${name^^}&amp;quot;   # JOHN DOE  (maiusculas)
echo &amp;quot;${name,,}&amp;quot;   # john doe  (minusculas)
echo &amp;quot;${name^}&amp;quot;    # John DOE  (capitaliza o primeiro caractere)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;É pouca coisa? Sim. Mas já corta subprocesso besta com &lt;code&gt;tr&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;O inferno das aspas&lt;/h2&gt;
&lt;p&gt;Se eu tivesse que escolher uma coisa pra martelar em shell script, seria aspas.
Metade dos bugs estranhos de Bash parece ter sido criada num laboratório só pra
te lembrar disso. Sabe aquele tipo de regra cheio de &lt;code&gt;ifs&lt;/code&gt;? Sãs as aspas no Bash
(principalmente as duplas).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
cleanup() {
    local dir=$1
    for file in $(ls &amp;quot;$dir&amp;quot;); do
        rm &amp;quot;$dir/$file&amp;quot;
    done
}

cleanup &amp;quot;/tmp/my app/cache&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse script é uma fábrica de problema. &lt;code&gt;$(ls &amp;quot;$dir&amp;quot;)&lt;/code&gt; faz word splitting. Aí o
arquivo com espaço vira dois. E você só percebe quando apagou a coisa errada ou
quando nada funciona no servidor &amp;quot;sem motivo&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro tip&lt;/strong&gt;: Se você está começando agora, já saiba disso: &lt;code&gt;rm&lt;/code&gt; não tem volta.
Começa com &lt;code&gt;echo rm algumacoisa&lt;/code&gt;. E veja o comando que vai ser executado
primeiro. Eu faço isso toda hora. Aqui o histórico do meu &lt;code&gt;zsh&lt;/code&gt; que não me deixa
mentir:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;: 1776691649:0;for f in ~/Desktop/mess/fotos/*.*; do echo magick &amp;quot;$f&amp;quot; -background black -alpha remove -alpha off &amp;quot;${f%(.png|.jpg)}.jpg&amp;quot; ; done
: 1776691666:0;for f in ~/Desktop/mess/fotos/*.*; do magick &amp;quot;$f&amp;quot; -background black -alpha remove -alpha off &amp;quot;${f%(.png|.jpg)}.jpg&amp;quot; ; done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O que? Achou que era mentira, né? Te peguei!&lt;/p&gt;
&lt;p&gt;O mínimo aceitável fica assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cleanup() {
    local dir=$1
    for file in &amp;quot;$dir&amp;quot;/*; do
        [[ -f &amp;quot;$file&amp;quot; ]] &amp;amp;&amp;amp; rm -- &amp;quot;$file&amp;quot;
    done
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Três regras que ajudam muito:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Coloca aspas em expansão de variável.&lt;/li&gt;
&lt;li&gt;Usa &lt;code&gt;&amp;quot;$@&amp;quot;&lt;/code&gt; pra repassar argumentos.&lt;/li&gt;
&lt;li&gt;Prefere &lt;code&gt;[[ ]]&lt;/code&gt; a &lt;code&gt;[ ]&lt;/code&gt; quando estiver em Bash.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Sobre &lt;code&gt;&amp;quot;$@&amp;quot;&lt;/code&gt; vs &lt;code&gt;$*&lt;/code&gt;, olha isso:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Suponha que o script foi chamado assim: ./script &amp;quot;hello world&amp;quot; &amp;quot;foo bar&amp;quot;

echo &amp;quot;--- \$* ---&amp;quot;
for arg in $*; do echo &amp;quot;  [$arg]&amp;quot;; done
# [hello] [world] [foo] [bar]  - 4 args. Quebrado.

echo &amp;quot;--- \&amp;quot;\$*\&amp;quot; ---&amp;quot;
for arg in &amp;quot;$*&amp;quot;; do echo &amp;quot;  [$arg]&amp;quot;; done
# [hello world foo bar]  - 1 arg. Tambem quebrado.

echo &amp;quot;--- \&amp;quot;\$@\&amp;quot; ---&amp;quot;
for arg in &amp;quot;$@&amp;quot;; do echo &amp;quot;  [$arg]&amp;quot;; done
# [hello world] [foo bar]  - 2 args. Correto.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;quot;$@&amp;quot;&lt;/code&gt; preserva os argumentos do jeito que eles vieram. &lt;code&gt;&amp;quot;$*&amp;quot;&lt;/code&gt; tem seus usos,
mas quase nunca é o que você quer.&lt;/p&gt;
&lt;p&gt;E sobre &lt;code&gt;[[ ]]&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Fragil - quebra com valores vazios ou com espacos
[ &amp;quot;$var&amp;quot; = &amp;quot;&amp;quot; ] &amp;amp;&amp;amp; echo &amp;quot;vazia&amp;quot;

# Robusto - sem ginastica com aspas
[[ -z $var ]] &amp;amp;&amp;amp; echo &amp;quot;vazia&amp;quot;

# Bonus: regex (so em [[ ]])
[[ &amp;quot;$email&amp;quot; =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]] &amp;amp;&amp;amp; echo &amp;quot;valido&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Variáveis indiretas e nameref: dinamismo sem &lt;code&gt;eval&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Quando você precisa usar o nome de uma variável guardado dentro de outra, &lt;code&gt;eval&lt;/code&gt;
costuma ser a primeira ideia ruim que aparece.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# PERIGOSO - nunca faca isso com entrada nao confiavel
key=&amp;quot;PATH&amp;quot;
eval &amp;quot;echo \$$key&amp;quot;    # Funciona, mas eval interpreta codigo arbitrario
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Funciona? Funciona. E também abre a porta pra você executar coisa que não devia.&lt;/p&gt;
&lt;p&gt;Pra ler o valor de outra variável, usa expansão indireta:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;key=&amp;quot;HOME&amp;quot;
echo &amp;quot;${!key}&amp;quot;    # /home/deploy - expande a variavel cujo nome esta em $key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se precisar mesmo de referência, aí entra &lt;code&gt;declare -n&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;declare -n ref=&amp;quot;HOME&amp;quot;
echo &amp;quot;$ref&amp;quot;           # /home/deploy
ref=&amp;quot;/opt/app&amp;quot;        # Isso ALTERA $HOME (cuidado!)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Caso real:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;load_config() {
    local config_file=&amp;quot;$1&amp;quot;
    while IFS=&amp;#39;=&amp;#39; read -r key value; do
        # Ignora comentarios e linhas em branco
        [[ &amp;quot;$key&amp;quot; =~ ^[[:space:]]*# ]] &amp;amp;&amp;amp; continue
        [[ -z &amp;quot;$key&amp;quot; ]] &amp;amp;&amp;amp; continue
        # Remove espacos
        key=&amp;quot;${key## }&amp;quot; ; key=&amp;quot;${key%% }&amp;quot;
        value=&amp;quot;${value## }&amp;quot; ; value=&amp;quot;${value%% }&amp;quot;
        # Exporta como variavel de ambiente em maiusculas
        declare -g &amp;quot;${key^^}=$value&amp;quot;
    done &amp;lt; &amp;quot;$config_file&amp;quot;
}

# config.env:
# db_host = postgres.prod
# db_port = 5432
# cache_ttl = 3600

load_config &amp;quot;config.env&amp;quot;
echo &amp;quot;$DB_HOST&amp;quot;    # postgres.prod
echo &amp;quot;$CACHE_TTL&amp;quot;  # 3600
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sem &lt;code&gt;eval&lt;/code&gt;, sem &lt;code&gt;source&lt;/code&gt;, sem transformar config em código executável.&lt;/p&gt;
&lt;p&gt;Outra situação boa pra &lt;code&gt;nameref&lt;/code&gt; é &amp;quot;retorno múltiplo&amp;quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Uma funcao que devolve o valor em variaveis escolhidas pelo chamador
parse_semver() {
    local version=&amp;quot;$1&amp;quot;
    declare -n major_ref=&amp;quot;$2&amp;quot;
    declare -n minor_ref=&amp;quot;$3&amp;quot;
    declare -n patch_ref=&amp;quot;$4&amp;quot;

    major_ref=&amp;quot;${version%%.*}&amp;quot;
    local rest=&amp;quot;${version#*.}&amp;quot;
    minor_ref=&amp;quot;${rest%%.*}&amp;quot;
    patch_ref=&amp;quot;${rest#*.}&amp;quot;
}

parse_semver &amp;quot;3.14.2&amp;quot; maj min pat
echo &amp;quot;$maj.$min.$pat&amp;quot;  # 3.14.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;É um jeito limpo de fazer a função escrever no que o chamador escolheu, sem
arquivo temporário e sem gambiarra global.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota de versão:&lt;/strong&gt; arrays associativos exigem Bash 4.0+, conversão de caixa
(&lt;code&gt;^^&lt;/code&gt;, &lt;code&gt;,,&lt;/code&gt;) exige 4.0+, &lt;code&gt;mapfile&lt;/code&gt; exige 4.0+, e &lt;code&gt;declare -n&lt;/code&gt; exige 4.3+. O
macOS ainda vem com Bash 3.2 por causa da novela da GPLv3. Então, se você
precisa suportar macOS puro, ou instala Bash novo com &lt;code&gt;brew install bash&lt;/code&gt;, ou
segura a onda com recursos mais antigos.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h1&gt;Controle de processos e sinais: aqui o shell começa a aprontar&lt;/h1&gt;
&lt;p&gt;Quando Bash fica estranho de verdade, geralmente não é por causa de variável. É
por causa de processo. A variável sumiu? Talvez ela tenha nascido num subshell.
O &lt;code&gt;Ctrl-C&lt;/code&gt; não limpou nada? Talvez você não amarrou os sinais. O &lt;code&gt;nohup&lt;/code&gt; não
virou daemon? Porque ele nunca prometeu isso.&lt;/p&gt;
&lt;h2&gt;Substituição de processo: quando pipe não resolve&lt;/h2&gt;
&lt;p&gt;Pipe é ótimo quando o próximo comando quer &lt;code&gt;stdin&lt;/code&gt;. Nem sempre ele quer.&lt;/p&gt;
&lt;p&gt;Nesses casos, a substituição de processo quebra um galho enorme:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&amp;lt;(list)   # um pseudo-arquivo legivel com o stdout de list
&amp;gt;(list)   # um pseudo-arquivo gravavel alimentando o stdin de list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O Bash te entrega algo via &lt;code&gt;/dev/fd&lt;/code&gt; ou FIFO pra você plugar em comando que
espera caminho de arquivo.&lt;/p&gt;
&lt;p&gt;Exemplo clássico:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;comm -3 &amp;lt;(sort prod-packages.txt) &amp;lt;(sort staging-packages.txt)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ou com &lt;code&gt;diff&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;diff -u &amp;lt;(sort before.txt) &amp;lt;(sort after.txt)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sem arquivo temporário perdido em &lt;code&gt;/tmp&lt;/code&gt;, sem cleanup extra só pra comparar duas
coisas.&lt;/p&gt;
&lt;p&gt;O &lt;code&gt;&amp;gt;(...)&lt;/code&gt; também é útil:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tar -cf - project \
  | tee &amp;gt;(sha256sum &amp;gt; project.tar.sha256) \
  &amp;gt; project.tar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Você gera o tar uma vez só e ainda calcula o hash no caminho.&lt;/p&gt;
&lt;p&gt;Agora, o bug clássico:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;last=&amp;quot;&amp;quot;
printf &amp;#39;%s\n&amp;#39; alpha beta | while IFS= read -r line; do
  last=$line
done
printf &amp;#39;last=%s\n&amp;#39; &amp;quot;$last&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se isso te devolveu variável vazia, bem-vindo ao clube. O loop rodou num
subshell por causa do pipeline. O shell pai não ficou sabendo de nada.&lt;/p&gt;
&lt;p&gt;Quando o estado precisa sobreviver, eu prefiro assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;last=&amp;quot;&amp;quot;
while IFS= read -r line; do
  last=$line
done &amp;lt; &amp;lt;(printf &amp;#39;%s\n&amp;#39; alpha beta)
printf &amp;#39;last=%s\n&amp;#39; &amp;quot;$last&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ou, se já for arquivo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;while IFS= read -r line; do
  last=$line
done &amp;lt; input.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tem &lt;code&gt;lastpipe&lt;/code&gt;? Tem. Eu basearia convenção de time nisso? Nem a pau.&lt;/p&gt;
&lt;p&gt;Se a lógica mexe em estado que você ainda vai usar, eu evitaria pipear pra
dentro dela (mais uma palavra inventada neste post).&lt;/p&gt;
&lt;h2&gt;Traps: cleanup de verdade&lt;/h2&gt;
&lt;p&gt;Se o script cria temporário, lock, socket ou processo em background, cleanup
deixa de ser detalhe bem rápido. Se você deixa pra pensar nisso no fim,
normalmente já deu tempo de se arrepender.&lt;/p&gt;
&lt;p&gt;Um padrão que costuma me servir:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bash
set -euo pipefail

tmpdir=$(mktemp -d)
worker_pid=&amp;quot;&amp;quot;

cleanup() {
  [[ -n &amp;quot;$worker_pid&amp;quot; ]] &amp;amp;&amp;amp; kill &amp;quot;$worker_pid&amp;quot; 2&amp;gt;/dev/null || true
  rm -rf &amp;quot;$tmpdir&amp;quot;
}

trap cleanup EXIT
trap &amp;#39;exit 130&amp;#39; INT
trap &amp;#39;exit 143&amp;#39; TERM

(
  while :; do
    date +%s &amp;gt;&amp;gt;&amp;quot;$tmpdir/heartbeat.log&amp;quot;
    sleep 2
  done
) &amp;amp;
worker_pid=$!

printf &amp;#39;worker=%s temp=%s\n&amp;#39; &amp;quot;$worker_pid&amp;quot; &amp;quot;$tmpdir&amp;quot;
wait &amp;quot;$worker_pid&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se o script só limpa no caminho feliz, ele não limpa. Só teve sorte.&lt;/p&gt;
&lt;p&gt;Coisas que eu tento não esquecer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;EXIT&lt;/code&gt; é onde eu quase sempre penduro teardown.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INT&lt;/code&gt; e &lt;code&gt;TERM&lt;/code&gt; merecem atenção.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ERR&lt;/code&gt; ajuda no diagnóstico, mas não substitui fluxo pensado.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SIGKILL&lt;/code&gt; não negocia com ninguém.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;E não, o Bash não vira supervisor só porque você colocou um &lt;code&gt;&amp;amp;&lt;/code&gt; no final.
Guardou PID? Sabe quem precisa morrer? Sabe o que acontece se o shell ignorar um
sinal? Isso tudo importa.&lt;/p&gt;
&lt;h2&gt;Job control: bom no terminal, outra história em script&lt;/h2&gt;
&lt;p&gt;No shell interativo, job control é uma mão na roda:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ rsync -av --info=progress2 big-tree/ backup:/srv/backup/
^Z
[1]+  Stopped                 rsync -av --info=progress2 big-tree/ backup:/srv/backup/
$ bg %1
[1]+ rsync -av --info=progress2 big-tree/ backup:/srv/backup/ &amp;amp;
$ jobs
[1]+  Running                 rsync -av --info=progress2 big-tree/ backup:/srv/backup/ &amp;amp;
$ fg %1
rsync -av --info=progress2 big-tree/ backup:/srv/backup/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pra esse tipo de uso, ele quebra um galho bonito. Você pausa, manda pro
background, traz de volta. Beleza.&lt;/p&gt;
&lt;p&gt;O tropeço vem quando a gente começa a tratar isso como gerenciamento de serviço.
Não é.&lt;/p&gt;
&lt;p&gt;Se quiser manter o job vivo depois do logout:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bg %1
disown -h %1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ou já inicia com &lt;code&gt;nohup&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nohup rsync -av --info=progress2 big-tree/ backup:/srv/backup/ \
  &amp;gt;rsync.log 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lembra do detalhe: &lt;code&gt;nohup&lt;/code&gt; não manda pro background sozinho. O &lt;code&gt;&amp;amp;&lt;/code&gt; continua
necessário.&lt;/p&gt;
&lt;p&gt;Se o processo realmente importa, eu tenderia a jogar isso pra &lt;code&gt;systemd&lt;/code&gt;, &lt;code&gt;tmux&lt;/code&gt;,
&lt;code&gt;screen&lt;/code&gt;, &lt;code&gt;systemd-run&lt;/code&gt; ou outro supervisor de verdade. &lt;code&gt;disown&lt;/code&gt; e &lt;code&gt;nohup&lt;/code&gt;
resolvem um problema bem mais simples.&lt;/p&gt;
&lt;h2&gt;Subshells vs grupos de comando&lt;/h2&gt;
&lt;p&gt;Essa diferença parece boba até o dia em que você toma uma rasteira:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;(commands)      # roda em um subshell
{ commands; }   # roda no shell atual
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Com parênteses, mudança de diretório, variável e opções do shell morrem ali
dentro. Com chaves, elas continuam valendo.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;(cd /tmp; printf &amp;#39;dentro de (): %s\n&amp;#39; &amp;quot;$PWD&amp;quot;)
printf &amp;#39;depois de (): %s\n&amp;#39; &amp;quot;$PWD&amp;quot;

{ cd /tmp; printf &amp;#39;dentro de {}: %s\n&amp;#39; &amp;quot;$PWD&amp;quot;; }
printf &amp;#39;depois de {}: %s\n&amp;#39; &amp;quot;$PWD&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E é a mesma ideia por trás do contador que fica em &lt;code&gt;0&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;count=0
printf &amp;#39;%s\n&amp;#39; a b c | while IFS= read -r _; do
  ((count++))
done
echo &amp;quot;$count&amp;quot;   # 0

count=0
while IFS= read -r _; do
  ((count++))
done &amp;lt; &amp;lt;(printf &amp;#39;%s\n&amp;#39; a b c)
echo &amp;quot;$count&amp;quot;   # 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O resumo que eu tento guardar é este:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;usa &lt;code&gt;(...)&lt;/code&gt; quando quiser isolamento&lt;/li&gt;
&lt;li&gt;usa &lt;code&gt;{ ...; }&lt;/code&gt; quando quiser manter efeito colateral&lt;/li&gt;
&lt;li&gt;evita pipeline quando a lógica precisa alterar estado do shell atual&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Parece detalhe. Não é. É o tipo de detalhe que decide se o script é previsível
ou se vai ter comportamento de poltergeist.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;Padrões do mundo real: Bash de produção&lt;/h1&gt;
&lt;p&gt;Quando eu penso em Bash de produção, eu não penso em &amp;quot;script elegante&amp;quot;. Eu penso
em script previsível. Aquele troço chato, explícito e um pouco paranoico, mas
que não inventa surpresa ruim.&lt;/p&gt;
&lt;h2&gt;Parsing de argumentos sem gambiarra&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;getopts&lt;/code&gt; já resolve muita coisa. Não é framework de CLI, não vai te dar
subcomando bonitinho, mas pra muito script real ele basta.&lt;/p&gt;
&lt;p&gt;O problema é que muita gente acha que parsing acaba quando você conseguiu ler
&lt;code&gt;-v&lt;/code&gt; e &lt;code&gt;-o&lt;/code&gt;. Não acaba. Parsing também é validar aridade, formato e tudo aquilo
de que o resto do script depende.&lt;/p&gt;
&lt;p&gt;Esse é o tipo de padrão que eu gosto de ter por perto:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat &amp;lt;&amp;lt;&amp;#39;USAGE&amp;#39;
Usage: report-sync [-v] [-o file] [-c config] job_name source_dir
USAGE
}

die() {
  printf &amp;#39;ERROR: %s\n&amp;#39; &amp;quot;$*&amp;quot; &amp;gt;&amp;amp;2
  exit 1
}

verbose=0
output_file=
config_file=
job_name=
source_dir=

normalize_args() {
  local normalized=()

  while (($#)); do
    case &amp;quot;$1&amp;quot; in
      --verbose) normalized+=(-v) ;;
      --output)
        (($# &amp;gt;= 2)) || die &amp;quot;--output requires a file path&amp;quot;
        normalized+=(-o &amp;quot;$2&amp;quot;)
        shift
        ;;
      --config)
        (($# &amp;gt;= 2)) || die &amp;quot;--config requires a path&amp;quot;
        normalized+=(-c &amp;quot;$2&amp;quot;)
        shift
        ;;
      --help) normalized+=(-h) ;;
      --)
        shift
        while (($#)); do
          normalized+=(&amp;quot;$1&amp;quot;)
          shift
        done
        break
        ;;
      *) normalized+=(&amp;quot;$1&amp;quot;) ;;
    esac
    shift
  done

  printf &amp;#39;%s\0&amp;#39; &amp;quot;${normalized[@]}&amp;quot;
}

parse_args() {
  local -a argv=()

  while IFS= read -r -d &amp;#39;&amp;#39; token; do
    argv+=(&amp;quot;$token&amp;quot;)
  done &amp;lt; &amp;lt;(normalize_args &amp;quot;$@&amp;quot;)

  set -- &amp;quot;${argv[@]}&amp;quot;
  OPTIND=1

  while getopts &amp;#39;:vo:c:h&amp;#39; opt; do
    case &amp;quot;$opt&amp;quot; in
      v) verbose=1 ;;
      o) output_file=$OPTARG ;;
      c) config_file=$OPTARG ;;
      h)
        usage
        exit 0
        ;;
      :)
        die &amp;quot;Option -$OPTARG requires an argument&amp;quot;
        ;;
      \?)
        usage &amp;gt;&amp;amp;2
        die &amp;quot;Unknown option: -$OPTARG&amp;quot;
        ;;
    esac
  done

  shift $((OPTIND - 1))
  (($# == 2)) || die &amp;#39;Expected job_name and source_dir&amp;#39;

  job_name=$1
  source_dir=$2

  [[ $job_name =~ ^[a-zA-Z0-9._-]+$ ]] || die &amp;quot;Invalid job name: $job_name&amp;quot;
  [[ -d $source_dir ]] || die &amp;quot;Source directory not found: $source_dir&amp;quot;

  if [[ -n ${output_file:-} ]]; then
    local parent_dir
    parent_dir=$(dirname -- &amp;quot;$output_file&amp;quot;)
    [[ -d $parent_dir ]] || die &amp;quot;Output directory does not exist: $parent_dir&amp;quot;
  fi
}

parse_args &amp;quot;$@&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Algumas escolhas aí eu manteria, pelo menos hoje:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;normalizar opção longa na mão em vez de depender de &lt;code&gt;getopt&lt;/code&gt; externo&lt;/li&gt;
&lt;li&gt;validar logo depois do parsing&lt;/li&gt;
&lt;li&gt;falhar cedo quando caminho, nome ou formato vierem errados&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Se o script começa a pedir subcomando, config aninhada e um monte de modo
mutuamente exclusivo, é um bom sinal de que talvez já tenha passado da hora de
sair do Bash.&lt;/p&gt;
&lt;h2&gt;Arquivos de configuração: dados, não código surpresa&lt;/h2&gt;
&lt;p&gt;Eu evito &lt;code&gt;source&lt;/code&gt; pra config sempre que posso. &lt;code&gt;source&lt;/code&gt; não lê &amp;quot;dados&amp;quot;. Ele
executa shell dentro do shell atual. Se isso é o que você quer, beleza. Se não
é, você acabou de transformar configuração em código executável sem nem
perceber.&lt;/p&gt;
&lt;p&gt;Pra config simples, &lt;code&gt;KEY=VALUE&lt;/code&gt; ainda é um formato honesto. Fácil de versionar,
de sobrepor com variável de ambiente e de entender sem sofrimento.&lt;/p&gt;
&lt;p&gt;Este loader aqui segue essa ideia e já recusa sintaxe shell no valor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bash
set -euo pipefail

readonly DEFAULT_CONFIG=&amp;quot;$HOME/.myapprc&amp;quot;
readonly SYSTEM_CONFIG=&amp;quot;/etc/myapp.conf&amp;quot;
config_file_for_current_run=

find_config_file() {
  local candidate

  for candidate in &amp;quot;${MYAPP_CONFIG:-}&amp;quot; &amp;quot;$DEFAULT_CONFIG&amp;quot; &amp;quot;$SYSTEM_CONFIG&amp;quot;; do
    [[ -n ${candidate:-} &amp;amp;&amp;amp; -f $candidate ]] || continue
    config_file_for_current_run=$candidate
    return 0
  done

  return 1
}

die() {
  printf &amp;#39;ERROR: %s\n&amp;#39; &amp;quot;$*&amp;quot; &amp;gt;&amp;amp;2
  exit 1
}

load_config_file() {
  local file=$1 line lineno=0 key value

  [[ -f $file ]] || die &amp;quot;Config is not a regular file: $file&amp;quot;

  while IFS= read -r line || [[ -n $line ]]; do
    ((lineno++))
    [[ $line =~ ^[[:space:]]*($|#) ]] &amp;amp;&amp;amp; continue

    [[ $line =~ ^[[:space:]]*([A-Z_][A-Z0-9_]*)=(.*)$ ]] \
      || die &amp;quot;Sintaxe de config nao suportada em $file:$lineno&amp;quot;

    key=${BASH_REMATCH[1]}
    value=${BASH_REMATCH[2]}

    [[ ! $value =~ [\`\$\(\)\;\&amp;amp;\|\&amp;lt;\&amp;gt;] ]] \
      || die &amp;quot;Recusando sintaxe shell em $file:$lineno&amp;quot;

    value=${value##[[:space:]]}
    value=${value%$&amp;#39;\r&amp;#39;}

    if [[ $value =~ ^&amp;quot;(.*)&amp;quot;$ ]]; then
      value=${BASH_REMATCH[1]}
    elif [[ $value =~ ^\&amp;#39;(.*)\&amp;#39;$ ]]; then
      value=${BASH_REMATCH[1]}
    fi

    printf -v &amp;quot;$key&amp;quot; &amp;#39;%s&amp;#39; &amp;quot;$value&amp;quot;
    export &amp;quot;$key&amp;quot;
  done &amp;lt; &amp;quot;$file&amp;quot;
}

if find_config_file; then
  load_config_file &amp;quot;$config_file_for_current_run&amp;quot;
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eu deixei ele rígido de propósito. Se alguém quiser array, objeto aninhado e
meia dúzia de regra de escape, melhor trocar de formato logo em vez de inventar
uma mini linguagem no shell.&lt;/p&gt;
&lt;p&gt;Pra JSON, usa &lt;code&gt;jq&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;api_base_url=$(jq -er &amp;#39;.api_base_url&amp;#39; &amp;quot;$config_json&amp;quot;)
concurrency=$(jq -er &amp;#39;.concurrency // 4&amp;#39; &amp;quot;$config_json&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pra mim, costuma funcionar bem assim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;arquivo de config pros defaults estáveis&lt;/li&gt;
&lt;li&gt;variável de ambiente pros overrides do deploy e pros segredos&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Logging e debug sem virar bagunça&lt;/h2&gt;
&lt;p&gt;Log bom em shell, pra mim, tem duas regras:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;humano lê em &lt;code&gt;stderr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;dado da pipeline vai limpo em &lt;code&gt;stdout&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Misturou os dois, você transformou um script reutilizável num bicho irritante.&lt;/p&gt;
&lt;p&gt;Uma camada simples já ajuda bastante:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;LOG_LEVEL=${LOG_LEVEL:-info}
LOG_FILE=${LOG_FILE:-}

level_number() {
  case &amp;quot;$1&amp;quot; in
    debug) printf &amp;#39;10\n&amp;#39; ;;
    info)  printf &amp;#39;20\n&amp;#39; ;;
    warn)  printf &amp;#39;30\n&amp;#39; ;;
    error) printf &amp;#39;40\n&amp;#39; ;;
    *) return 1 ;;
  esac
}

log() {
  local level=${1:?missing log level}
  shift

  local current threshold ts line
  current=$(level_number &amp;quot;${level,,}&amp;quot;) || return 1
  threshold=$(level_number &amp;quot;${LOG_LEVEL,,}&amp;quot;) || threshold=20

  (( current &amp;lt; threshold )) &amp;amp;&amp;amp; return 0

  printf -v ts &amp;#39;%(%Y-%m-%dT%H:%M:%S%z)T&amp;#39; -1
  printf -v line &amp;#39;%s %-5s %s&amp;#39; &amp;quot;$ts&amp;quot; &amp;quot;${level^^}&amp;quot; &amp;quot;$*&amp;quot;

  printf &amp;#39;%s\n&amp;#39; &amp;quot;$line&amp;quot; &amp;gt;&amp;amp;2
  [[ -n ${LOG_FILE:-} ]] &amp;amp;&amp;amp; printf &amp;#39;%s\n&amp;#39; &amp;quot;$line&amp;quot; &amp;gt;&amp;gt; &amp;quot;$LOG_FILE&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Com isso, você ganha nível, timestamp e arquivo de log sem sujar &lt;code&gt;stdout&lt;/code&gt;.
Quando eu quero espelhar stream, ainda gosto de &lt;code&gt;tee&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;exec 2&amp;gt; &amp;gt;(tee -a &amp;quot;$LOG_FILE&amp;quot; &amp;gt;&amp;amp;2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;set -x&lt;/code&gt; ajuda, mas a saída crua costuma ser barulhenta demais. O truque abaixo
deixa o trace num descritor separado e com contexto que presta:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;exec 9&amp;gt;&amp;gt;&amp;quot;${TRACE_FILE:-/tmp/report-sync.trace}&amp;quot;
export BASH_XTRACEFD=9
export PS4=&amp;#39;+ ${BASH_SOURCE##*/}:${LINENO}:${FUNCNAME[0]:-main}: &amp;#39;

set -x
# bloco suspeito aqui
set +x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;É o tipo de coisa que vale guardar porque economiza um bom tempo quando o script
começa a se comportar como se estivesse possuído.&lt;/p&gt;
&lt;h2&gt;Testando script Bash sem sofrimento&lt;/h2&gt;
&lt;p&gt;Dá pra testar shell script, sim. E eu acho que vale bastante quando o script
começa a lidar com flag, config, arquivo ou efeito colateral.&lt;/p&gt;
&lt;p&gt;Se for um scriptinho de 15 linhas em volta de um comando, talvez nem precise.
Agora, se ele apaga coisa, faz parsing e conversa com produção, eu prefiro ter
algum teste.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bats-core.readthedocs.io/en/latest/writing-tests.html&quot;&gt;&lt;code&gt;bats&lt;/code&gt;&lt;/a&gt; me
atende bem porque mantém tudo no ecossistema do shell.&lt;/p&gt;
&lt;p&gt;Um teste útil, pro loader de config acima, seria algo assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bats

load &amp;#39;../lib/config.sh&amp;#39;

@test &amp;quot;load_config_file rejeita sintaxe shell em valores&amp;quot; {
  config=&amp;quot;$BATS_TEST_TMPDIR/bad.conf&amp;quot;
  printf &amp;#39;OUTPUT_FILE=$(touch /tmp/nope)\n&amp;#39; &amp;gt; &amp;quot;$config&amp;quot;

  run load_config_file &amp;quot;$config&amp;quot;

  [ &amp;quot;$status&amp;quot; -eq 1 ]
  [[ &amp;quot;$output&amp;quot; == *&amp;quot;Recusando sintaxe shell&amp;quot;* ]]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse teste protege uma fronteira que, pra mim, importa bastante: config tem que
continuar sendo dado.&lt;/p&gt;
&lt;p&gt;Pra mockar comando externo, eu gosto do jeito simples: diretório temporário no
começo do &lt;code&gt;PATH&lt;/code&gt; e executável falso lá dentro. Menos frágil do que tentar
inventar mágica com alias.&lt;/p&gt;
&lt;p&gt;Pra mim, testabilidade em Bash costuma andar junto com design menos bagunçado.
Quando parsing, config e efeito colateral ficam separados em funções pequenas, o
teste deixa de ser castigo.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;Segurança e produção: onde Bash para de ser brincadeira&lt;/h1&gt;
&lt;p&gt;Quando o script começa a rodar em servidor, CI, container ou qualquer lugar com
entrada menos controlada, a história muda rápido. O que no notebook era só
&amp;quot;script útil&amp;quot; vira superfície de ataque sem pedir licença.&lt;/p&gt;
&lt;h2&gt;As armadilhas de segurança&lt;/h2&gt;
&lt;h3&gt;Injeção de comando: o botão vermelho do &lt;code&gt;eval&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Se você monta comando a partir de entrada de usuário, está perigosamente perto
de entregar um shell de presente.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;O script vulnerável:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# &amp;quot;Visualizador simples&amp;quot; de logs - NAO USE ISSO
echo &amp;quot;Digite o nome do arquivo:&amp;quot;
read -r filename
eval &amp;quot;cat logs/$filename&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O atacante pode meter um &lt;code&gt;;&lt;/code&gt;, um &lt;code&gt;$(...)&lt;/code&gt; ou qualquer outra gracinha. E, às
vezes, nem precisa ser algo espalhafatoso pra causar estrago.&lt;/p&gt;
&lt;p&gt;Pra mim, aqui não adianta tentar &amp;quot;sanitizar melhor&amp;quot;. O que ajuda de verdade é
parar de tratar dado como código:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
read -r filename

# Valida: somente letras, numeros, traco, ponto e underscore
if [[ ! &amp;quot;$filename&amp;quot; =~ ^[a-zA-Z0-9._-]+$ ]]; then
    echo &amp;quot;Nome de arquivo invalido&amp;quot; &amp;gt;&amp;amp;2
    exit 1
fi

# Usa a variavel como DADO, nao como CODIGO
# Sem eval, sem expansao desprotegida, sem montar comando na mao
cat -- &amp;quot;logs/$filename&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O &lt;code&gt;--&lt;/code&gt; evita interpretar nome como opção. O regex barra &lt;code&gt;../../etc/passwd&lt;/code&gt; e
coisa parecida. Aqui a ideia é simples: cada camada corta um pedaço do problema.&lt;/p&gt;
&lt;p&gt;Quando bate vontade de usar &lt;code&gt;eval&lt;/code&gt;, eu paro e desconfio. Na maior parte das
vezes dá pra fazer melhor. Quando eu realmente preciso montar comando, costumo
ir de array:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cmd=(find &amp;quot;$directory&amp;quot; -name &amp;quot;*.log&amp;quot; -mtime +&amp;quot;$days&amp;quot;)
&amp;quot;${cmd[@]}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Corrida com arquivo temporário: &lt;code&gt;mktemp&lt;/code&gt; ou arrependimento&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# VULNERAVEL: nome previsivel, condicao de corrida
tmpfile=&amp;quot;/tmp/myapp-$$&amp;quot;
echo &amp;quot;$data&amp;quot; &amp;gt; &amp;quot;$tmpfile&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse padrão com PID no nome parece &amp;quot;único o bastante&amp;quot; até o dia em que alguém
cria um symlink ali antes de você. Aí o seu script grava dado onde não devia e a
brincadeira acaba.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Um padrão bem melhor:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
set -euo pipefail

tmpdir=$(mktemp -d &amp;quot;${TMPDIR:-/tmp}/myapp.XXXXXXXXXX&amp;quot;)
trap &amp;#39;rm -rf -- &amp;quot;$tmpdir&amp;quot;&amp;#39; EXIT

# Agora use $tmpdir com seguranca - aleatorio, dono voce, modo 0700
echo &amp;quot;$data&amp;quot; &amp;gt; &amp;quot;$tmpdir/output.txt&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mktemp&lt;/code&gt; cria de forma atômica e com permissão restritiva. O &lt;code&gt;trap&lt;/code&gt; limpa
depois. E diretório temporário costuma ser mais prático do que arquivo
temporário solto.&lt;/p&gt;
&lt;h3&gt;Segredos: o que NÃO fazer&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Tudo isso aqui esta errado:
PASSWORD=&amp;quot;hunter2&amp;quot;                      # Hardcoded no codigo
curl -u &amp;quot;admin:$PASSWORD&amp;quot; &amp;quot;$url&amp;quot;        # Visivel em /proc/$pid/cmdline
echo &amp;quot;$SECRET&amp;quot; | docker login --stdin   # Melhor, mas cuidado com historico do shell
export DB_PASS=&amp;quot;secret&amp;quot;                 # Herdado por TODO processo filho
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Em shell, segredo espalha fácil demais. Vai pro histórico, vai pro &lt;code&gt;ps&lt;/code&gt;, vai pro
ambiente do processo filho, vai parar onde você não queria.&lt;/p&gt;
&lt;p&gt;Quando der, eu prefiro ler de arquivo/FD ou deixar a plataforma injetar só no
momento do uso:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Le segredo de file descriptor, sem tocar disco nem aparecer em ps
db_password=$(&amp;lt; /run/secrets/db_password)
PGPASSWORD=&amp;quot;$db_password&amp;quot; psql -h &amp;quot;$host&amp;quot; -U &amp;quot;$user&amp;quot; &amp;quot;$dbname&amp;quot; &amp;lt;&amp;lt;&amp;lt; &amp;quot;$query&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Quando o Bash fica lento (e quando isso não importa)&lt;/h2&gt;
&lt;p&gt;Bash costuma ir melhor abrindo processo do que processando dado em loop
apertado. Quando o script fica lento, muitas vezes o problema é um festival de
fork.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A versão lenta&lt;/strong&gt;: 3 forks externos por iteração.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Processando 10.000 linhas: ~45 segundos
while IFS= read -r line; do
    name=$(echo &amp;quot;$line&amp;quot; | cut -d&amp;#39;,&amp;#39; -f1)
    value=$(echo &amp;quot;$line&amp;quot; | cut -d&amp;#39;,&amp;#39; -f2)
    echo &amp;quot;$name: $value&amp;quot;
done &amp;lt; data.csv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;A versão rápida&lt;/strong&gt;: zero fork.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Processando 10.000 linhas: ~0.3 segundos
while IFS=, read -r name value _rest; do
    printf &amp;#39;%s: %s\n&amp;#39; &amp;quot;$name&amp;quot; &amp;quot;$value&amp;quot;
done &amp;lt; data.csv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A diferença está aí: o primeiro exemplo cria processo demais. O segundo deixa o
shell fazer o que ele consegue fazer sem sair abrindo ferramenta externa em cada
linha.&lt;/p&gt;
&lt;p&gt;Pra paralelismo de verdade, muitas vezes é melhor sair do loop manual:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Processa arquivos em paralelo, 8 por vez
find . -name &amp;#39;*.log&amp;#39; -print0 | xargs -0 -P 8 -I{} gzip {}

# GNU parallel com barra de progresso
find . -name &amp;#39;*.csv&amp;#39; | parallel --bar -j+0 &amp;#39;process_file {}&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A regra prática que eu tento lembrar é: shell lento quase sempre tem loop com
fork demais.&lt;/p&gt;
&lt;h2&gt;CI/CD: códigos de saída são sua API&lt;/h2&gt;
&lt;p&gt;CI é binário: saiu &lt;code&gt;0&lt;/code&gt;, passou. Qualquer outra coisa, falhou. Então o script que
roda ali precisa ser bem honesto com código de saída:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
set -euo pipefail
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sem &lt;code&gt;pipefail&lt;/code&gt;, o pipeline pode mascarar erro feio e o build passar com cara de
vencedor.&lt;/p&gt;
&lt;h3&gt;ShellCheck no CI&lt;/h3&gt;
&lt;p&gt;ShellCheck no CI é um daqueles freios baratos que compensam muito:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# .github/workflows/lint.yml
name: Shell Lint
on: [push, pull_request]
jobs:
  shellcheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run ShellCheck
        run: |
          find . -name &amp;#39;*.sh&amp;#39; -print0 | xargs -0 shellcheck --severity=warning
      - name: Run BATS tests
        run: |
          sudo apt-get install -y bats
          bats tests/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Com ShellCheck e algum teste em Bats, eu já fico bem mais tranquilo com o
script.&lt;/p&gt;
&lt;h2&gt;Docker: o problema do PID 1&lt;/h2&gt;
&lt;p&gt;Docker com shell tem uma pegadinha bem comum: PID 1. Se a sua app vira filha do
Bash e o Bash não repassa sinal direito, o container morre de forma feia.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Um entrypoint que eu usaria com mais tranquilidade em produção:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
set -euo pipefail

# --- Configuracao via ambiente ---
: &amp;quot;${APP_PORT:=8080}&amp;quot;
: &amp;quot;${APP_ENV:=production}&amp;quot;
: &amp;quot;${GRACEFUL_SHUTDOWN_TIMEOUT:=30}&amp;quot;

# --- Espera dependencias ---
wait_for_db() {
    local retries=30
    while ! pg_isready -h &amp;quot;$DB_HOST&amp;quot; -p &amp;quot;${DB_PORT:-5432}&amp;quot; -q; do
        retries=$((retries - 1))
        if [[ $retries -le 0 ]]; then
            echo &amp;quot;ERROR: Banco de dados indisponivel apos 30 tentativas&amp;quot; &amp;gt;&amp;amp;2
            exit 1
        fi
        sleep 1
    done
}

# --- Tratamento de sinais ---
shutdown_handler() {
    echo &amp;quot;Sinal de encerramento recebido, drenando conexoes...&amp;quot;
    # Da tempo pro load balancer remover a instancia
    sleep 2
    kill -TERM &amp;quot;$child_pid&amp;quot; 2&amp;gt;/dev/null || true
    wait &amp;quot;$child_pid&amp;quot;
    exit_code=$?
    echo &amp;quot;Aplicacao saiu com codigo $exit_code&amp;quot;
    exit &amp;quot;$exit_code&amp;quot;
}

trap shutdown_handler SIGTERM SIGINT

# --- Init ---
wait_for_db
echo &amp;quot;Iniciando app em :${APP_PORT} (env=${APP_ENV})&amp;quot;

# --- Launch: exec substitui o shell SE nao houver pos-processamento ---
# Quando voce precisa encaminhar sinais (como no tratador acima), NAO use exec.
# Em vez disso, jogue o processo para background e espere.
/usr/local/bin/myapp --port &amp;quot;$APP_PORT&amp;quot; &amp;amp;
child_pid=$!
wait &amp;quot;$child_pid&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Daqui, eu guardo basicamente isto:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;exec&lt;/code&gt; substitui o shell pelo processo final quando você não precisa de lógica
extra.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;background + wait&lt;/code&gt; entra quando você realmente precisa interceptar sinal.&lt;/li&gt;
&lt;li&gt;Alpine não te dá Bash por padrão.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--init&lt;/code&gt; com &lt;code&gt;tini&lt;/code&gt; às vezes já resolve sem precisar reinventar nada.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Padrões de deploy&lt;/h2&gt;
&lt;h3&gt;Publicando sem surpresa&lt;/h3&gt;
&lt;p&gt;Pra script standalone, eu gosto de falhar cedo se a versão do Bash não for a
necessária:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# Checagem de versao no topo - falha rapido, nao no meio do caminho
if [[ &amp;quot;${BASH_VERSINFO[0]}&amp;quot; -lt 4 ]]; then
    echo &amp;quot;ERROR: Bash 4+ obrigatorio (encontrado ${BASH_VERSION})&amp;quot; &amp;gt;&amp;amp;2
    exit 1
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Idempotência ajuda bastante&lt;/h3&gt;
&lt;p&gt;Script de produção roda de novo. Retry, cron, recuperação, operador apertando
seta pra cima sem querer pensar muito. Então idempotência ajuda bastante:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Idempotente: cria so se nao existir
mkdir -p /opt/myapp/data
id -u appuser &amp;amp;&amp;gt;/dev/null || useradd -r -s /bin/false appuser

# Idempotente: move so se a origem existir
[[ -f &amp;quot;$src&amp;quot; ]] &amp;amp;&amp;amp; mv -- &amp;quot;$src&amp;quot; &amp;quot;$dst&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Quando parar de usar Bash&lt;/h3&gt;
&lt;p&gt;Bash é bom pra colar programa, mover arquivo, orquestrar etapa. Começa a ficar
ruim quando:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;você está parseando JSON/YAML o tempo todo&lt;/li&gt;
&lt;li&gt;você precisa de estrutura de dados de verdade&lt;/li&gt;
&lt;li&gt;o script cresce e ninguém mais quer encostar&lt;/li&gt;
&lt;li&gt;recovery de erro e concorrência viram parte do problema&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nessa hora, chamar Python ou Go não é derrota. Muitas vezes é só bom senso.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;Conclusão: script chato costuma ser o melhor script&lt;/h1&gt;
&lt;p&gt;Como eu falei no começo, isso aqui continua sendo nota de estudo. Só que é o
tipo de nota de estudo que já me poupou de algumas burradas, então resolvi
juntar tudo num lugar só.&lt;/p&gt;
&lt;p&gt;Pra mim, Bash fica melhor quando a gente para de procurar truque esperto e
começa a desconfiar das coisas certas. Do &lt;code&gt;set -e&lt;/code&gt;, do pipeline inocente, do
&lt;code&gt;eval&lt;/code&gt;, do temporário malfeito, do loop que abre processo demais e jura que está
tudo sob controle.&lt;/p&gt;
&lt;p&gt;No fim, o tipo de script Bash de que eu mais gosto é aquele que você roda daqui
a três meses sem sentir vontade de xingar o seu eu do passado. Se ele falha
cedo, fala onde doeu, não mistura dado com código e não sai fazendo gracinha em
silêncio, eu já fico feliz.&lt;/p&gt;
&lt;p&gt;Se ele funcionar no notebook, no CI e dentro do container sem virar um caso de
polícia, melhor ainda. E se em algum momento você perceber que está forçando a
barra e tentando transformar Bash em linguagem de aplicação, passa o bastão. Eu
mesmo prefiro isso a insistir por teimosia.&lt;/p&gt;
&lt;p&gt;Valeu. Até o próximo (esse aqui foi sofrido pra escrever 🙏)&lt;/p&gt;
</content:encoded></item><item><title>Um dos maiores ataques de supply chain de 2026 (na prática)</title><link>https://otaviomiranda.com.br/2026/um-dos-maiores-ataques-de-supply-chain-de-2026-na-pratica/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/um-dos-maiores-ataques-de-supply-chain-de-2026-na-pratica/</guid><description>O ataque ao Trivy e LiteLLM pelo TeamPCP nos ensinou muitas coisas novas. Caso tenha perdido, vou replicar o ataque na prática. Bora aprender juntos!</description><pubDate>Thu, 02 Apr 2026 20:07:56 GMT</pubDate><content:encoded>&lt;p&gt;Obs.: o tom desse artigo está extremamente diferente de como eu escrevo
realmente. O motivo é que se eu falar realmente como eu escrevo, o artigo não
vai ser entregue ou vai ser removido por questões de segurança. O assunto é
sério e precisa ser falado, mas de forma que o algoritmo entenda e não remova o
conteúdo.&lt;/p&gt;
&lt;p&gt;Pessoal, preciso falar de um assunto muito sério hoje. Existe um ataque de
supply chain bem engenhoso rolando na internet, e se você usa GitHub Actions ou
pip install em qualquer pacote Python, você precisa me ouvir. Entre fevereiro e
março de 2026 a coisa ficou bem feia na nossa área.&lt;/p&gt;
&lt;p&gt;Alguns responsáveis por esse problema conseguiram comprometer o Trivy (sim, um
grande scanner de segurança do mercado) e o LiteLLM (que bate quase 95 milhões
de downloads mensais no PyPI). Tem indícios de que sobrou até para o Checkmarx.&lt;/p&gt;
&lt;p&gt;Eu montei a timeline desses eventos, reproduzi partes do incidente no meu
laboratório privado e agora vou te mandar a real dos acontecimentos por baixo
dos panos e como você protege o seu próprio código de dor de cabeça igual a
essa.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Apoio&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Se estiver precisando de servidores (VPS) com bom custo-benefício, dá uma olhada
na Hostinger. Usando meu link e cupom, você ganha até 2 anos de desconto.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.hostg.xyz/SHJC8&quot;&gt;hostinger.com/otaviomiranda&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cupom: &lt;code&gt;OTAVIOMIRANDA&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Em vídeo&lt;/h2&gt;
&lt;p&gt;Fiz um vídeo completo sobre o assunto, caso queira assistir:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/oFrcf_17gRg&quot;&gt;https://youtu.be/oFrcf_17gRg&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Então agora é texto, bora...&lt;/p&gt;
&lt;h2&gt;A fresta de entrada e o bot&lt;/h2&gt;
&lt;p&gt;Tudo começou lá pelo final de fevereiro, quando surgiu uma conta no GitHub
chamada &amp;quot;hackerbot-claw&amp;quot;. Esse bot ficava o dia todo escaneando a rede pública
atrás de projetos com aquela falha simples na área de CI/CD. Cedo ou tarde ele
iria encontrar algo.&lt;/p&gt;
&lt;p&gt;O bot focava em projetos usando o famigerado trigger &lt;code&gt;pull_request_target&lt;/code&gt;. Só
para nivelar a questão: o trigger normal &lt;code&gt;pull_request&lt;/code&gt; no Actions roda tudo num
ambiente bem isolado blindando os secrets do repositório. O perigo mora lá na
sintaxe de &amp;quot;target&amp;quot;, que ao contrário da normal inicia tudo mirando com
permissões da &amp;quot;main&amp;quot;, com a faca e o queijo na mão (no caso todas as chaves e
senhas configuradas no sistema).&lt;/p&gt;
&lt;p&gt;O ponto de quebra que abriu a porta foi as equipes combinarem o trigger exposto
com o checkout direto das linhas dos contribuintes na PR. Isso significa pegar
código totalmente não testado rodando nos runners com variáveis da sua carteira
de trabalho da nuvem liberados.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;on:
  pull_request_target:
    types: [opened, synchronize]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # O erro: Fazer checkout de um PR feito por desconhecidos
      # dentro de um job na retaguarda com secrets destravados!
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}

      - run: make build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eles conseguiram furar e rodar o script no repositório gigante do Trivy
explorando exatamente esse passo fraco.&lt;/p&gt;
&lt;h2&gt;A captura silenciosa do sistema&lt;/h2&gt;
&lt;p&gt;Na surdina, o script configurado por eles largou um comando de rede simples,
entrando nos parâmetros ativos da máquina sem gritaria e sem levantar suspeitas.
Eles extraíram os dados do processo lendo a memória RAM de forma direta mapeando
os volumes de execução. Lá nesse bloco constava o token principal de acesso ao
projeto.&lt;/p&gt;
&lt;p&gt;Assim que a senha de edição foi parar no servidor deles, efetuaram um envio
camuflado no formato do Conventional Commits e fingiram assinar com a autoria de
antigos mantenedores oficiais do Trivy. Tudo continuou parecendo muito normal
para a equipe original de manutenção do pacote.&lt;/p&gt;
&lt;p&gt;Mas a artimanha inserida trocou com primor um apontamento lá da rotina de
lançamento para um endereço alvo falso com o nome (removido por questões de
segurança). Esse código alterado ficou um par de semanas dormente, esperando
pacientemente o fluxo rodar.&lt;/p&gt;
&lt;h2&gt;Farsa das tags e a queda do LiteLLM&lt;/h2&gt;
&lt;p&gt;Então botaram a cereja principal da situação e cometeram o force push no
repositório brutalmente, rebaixando e trocando muito mais do que setenta tags do
Trivy de volta à fase comprometida. Agora mesmo quem só declarava o clássico
valor (REMOVIDO POR QUESTÕES DE SEGURANÇA) nas automações passava a usar esse
código contendo as extrações escondidas.&lt;/p&gt;
&lt;p&gt;Para o grupo do LiteLLM o estrago nas tags causou muito dano. Todo o fluxo
interno de Actions que a equipe geria estava perfeitamente configurado sem erros
no caso de checkouts e PRs de estranhos. Só que os devs haviam importado o Trivy
na esteira para auditar falhas do mesmo modo padrão utilizado pela grande
comunidade global.&lt;/p&gt;
&lt;p&gt;A action modificada desceu rodando no servidor LiteLLM engolido pelos scanners
deles e extraiu na hora as credenciais sensíveis de enviar releases lá de dentro
da automação (chaves das atualizações locais). Pegaram tudo, codificaram os
textos e jogaram no próprio servidor remoto que hospedavam. Usando a chave do
pacote que eles conseguiram puxar do projeto oficial, o grupo subiu na loja do
Python de forma validada o conteúdo do LiteLLM alterado, e para enganar geral a
conta primária de código continuava livre desse incidente. O buraco havia sido
feito só lá no entregável do servidor e a comunidade de uso instalava pacotes
não oficiais rotineiramente sem nem notar.&lt;/p&gt;
&lt;h2&gt;O truque do .pth e a bomba de processos&lt;/h2&gt;
&lt;p&gt;Qual foi o passo sorrateiro no pacote pra sobrecarregar a máquina dos
desenvolvedores que instalavam isso no seu uso trivial daquele dia? Eles
espalharam um pequeno arquivo de sistema usando o formato &lt;code&gt;.pth&lt;/code&gt; guardado com
discrição nos diretórios de ambiente (pasta padrão originada na execução das
instalações locais).&lt;/p&gt;
&lt;p&gt;Muitos desconhecem, mas quando a gente sobe uma rotina do código, o motor
processa este script &lt;code&gt;.pth&lt;/code&gt; para registrar os percursos extras em diretórios que
vão constar na variável base. O detalhe não divulgado é que iniciar frases
contendo a ordem &amp;quot;import&amp;quot; executa na mesma hora toda alocação contida logo à
frente. Sem cerimônia e sem exigências adicionais.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os; exec(&amp;quot;REMOVIDO POR QUESTÕES DE SEGURANÇA&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Só de levantar o prompt REPL, pedir um log simples ou debugar a aplicação, a
ferramenta já ativava rotinas secundárias no fundo, mapeando variáveis de
ambiente e transferindo resumos pra longe. E sabe por que a conta não fechou e
fomos notificados prontamente pela comunidade? Eles erraram bizarramente os
loops na inicialização, o que ocasionou chamadas retroativas num
congestionamento de tarefas conhecido no mercado por fork bomb. Cerca de onze
mil comandos simultâneos rodando até engasgar os computadores alertaram a
supervisão em vários setores de que isso era um gargalo gravíssimo rolando de
forma anômala.&lt;/p&gt;
&lt;h2&gt;Ajustes necessários pra você e sua equipe&lt;/h2&gt;
&lt;p&gt;Esse alerta vem para mostrar onde devemos nos ligar ao construir e publicar
sistemas inteiros pelo Github sem copiar do escopo global como meros
digitadores. Segue o que funciona tranquilamente no isolamento básico reduzindo
bastante os perigos em pipelines.&lt;/p&gt;
&lt;p&gt;Use sempre o valor de Hash e não coloque mais a variável nominal do pacote via
tag. Todo commit original do projeto traz uma representação com letras que você
joga fixada de forma absoluta na frente de tudo que puxar nos runners que nunca
será fraudada ou mudada a rodo. Acima disso você tranca no Docker os pacotes
referenciando a imagem por meio de digest da versão original ao invés da sintaxe
última do release na linha.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Inseguro
uses: exemplo/seguro@v1

# Correto fixado via hash de origem do projeto
uses: exemplo/seguro@a1b2c3d4e5f6...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Outro conselho de peso pra amarrar bem a porta nos workflows lida com a
verificação de código puxado. Só traga checkouts desconhecidos validando por
&lt;code&gt;pull_request&lt;/code&gt; fechado impedindo rodagem nas permissões chaves da branch mãe de
ponta. Reserve &lt;code&gt;pull_request_target&lt;/code&gt; no limite seguro pra aprovar flags e regras
nos projetos, contanto que jamais em nenhuma circunstância puxe ou aplique &lt;code&gt;ref&lt;/code&gt;
externa junto com isso nas ações de build. Tranque a sua internet configurando
firewalls travando saídas agressivas de envio direto a domínios ocultos da rede
no executor Github se não usar isso nas chamadas oficiais ou pacotes internos
pra evitar vazamentos descarados do token local na tela dele.&lt;/p&gt;
&lt;p&gt;E façam revisões atentas nas dependências do backend renovando credenciais em
bloco, nunca deixando chaves antigas ativas. Analisem também aquele formato
&lt;code&gt;.pth&lt;/code&gt;, sempre validando se algo conta com chamadas automatizadas antes que isso
crie brechas indesejáveis em aplicações rodando de forma corporativa ou caseira.&lt;/p&gt;
&lt;p&gt;No blog e aqui na aba de comunidade falo das paradas baseadas na raiz, focando
para melhorarmos nossas skills avançadas na engenharia ao criarmos ferramentas
pra toda galera dev na comunidade com as nossas proteções sólidas montadinhas do
que o mercado expõe como norma nas vulnerabilidades em andamento diário no
ecosistema por ai afora da net de desenvolvimento livre onde tem o perigo atual
operante do supply chain escondidinho onde menos se acredita. Assine a
Newsletter ali pra ganhar essas pegadas que chegam rápidas por lá também sem
burocracia dos banners irritantes nas navegações usuais na plataforma oficial. É
eu trocando a parte técnica limpa a seu dispor direto pro coração dos estudos em
tecnologia.&lt;/p&gt;
&lt;p&gt;Até a próxima.&lt;/p&gt;
</content:encoded></item><item><title>Hackers roubaram senhas com litellm (Pacote de IA do Python)</title><link>https://otaviomiranda.com.br/2026/litellm-python-supply-chain-attack-check/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/litellm-python-supply-chain-attack-check/</guid><description>Supply chain attack no litellm: o pacote foi comprometido para roubar credenciais. Entenda o que houve e como verificar se você foi afetado.</description><pubDate>Thu, 26 Mar 2026 11:31:44 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;./images/litellm-python-attack.jpg&quot; alt=&quot;Hackers roubaram senhas com litellm (Pacote de IA do Python)&quot;&gt;&lt;/p&gt;
&lt;p&gt;O pacote &lt;code&gt;litellm&lt;/code&gt; versões &lt;strong&gt;1.82.7 e 1.82.8&lt;/strong&gt; foram comprometidos com um
credential stealer sofisticado. Basicamente, foi um script escondido atrás de
&lt;code&gt;base64&lt;/code&gt; que rouba qualquer chave secreta no seu computador. Senhas, &lt;code&gt;.env&lt;/code&gt;,
credenciais em geral, &lt;code&gt;authentication tokens&lt;/code&gt;, chaves SSH e qualquer outra coisa
que você nem imaginaria que estivesse em arquivo de texto no seu computador
(como as próprias credenciais dos modelos de LLM que você usa).&lt;/p&gt;
&lt;p&gt;Se você trabalha com IA em Python, ou usa qualquer ferramenta que depende de
litellm por baixo dos panos, esse texto é pra você.&lt;/p&gt;
&lt;p&gt;Referência da issue inicial:
&lt;a href=&quot;https://github.com/BerriAI/litellm/issues/24512&quot;&gt;https://github.com/BerriAI/litellm/issues/24512&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Apoio Hostinger&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Se estiver precisando de servidores (VPS) com bom custo-benefício, dá uma olhada
na Hostinger. Usando meu link e cupom, você ganha até 2 anos de desconto.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://hostinger.com/otaviomiranda&quot;&gt;hostinger.com/otaviomiranda&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cupom: &lt;code&gt;OTAVIOMIRANDA&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;O que aconteceu?&lt;/h2&gt;
&lt;p&gt;No dia 24 de março de 2026, hackers publicaram duas versões maliciosas do
&lt;code&gt;litellm&lt;/code&gt; diretamente no PyPI. Não foi invasão ao repositório do GitHub. Não foi
PR malicioso que passou despercebido. Eles simplesmente... &lt;strong&gt;publicaram o pacote
no lugar do time oficial&lt;/strong&gt;, usando credenciais roubadas. 🤔&lt;/p&gt;
&lt;p&gt;O &lt;code&gt;litellm&lt;/code&gt; não é uma biblioteca qualquer. Eu não o usava diretamente, mas estou
descobrindo como funciona enquanto escrevo este texto.&lt;/p&gt;
&lt;p&gt;Ele é a camada que traduz e roteia chamadas pra mais de 100 provedores de LLM
(OpenAI, Anthropic, Google, AWS Bedrock, etc.) numa interface única.&lt;/p&gt;
&lt;p&gt;Uma comparação mais familiar para mim seria com o LangChain. Porém, enquanto
LangChain é o framework que orquestra os agentes de IA, o &lt;code&gt;litellm&lt;/code&gt; é a camada
que fica embaixo dele traduzindo essas chamadas pra cada provedor. Inclusive,
parece que o LangChain usa o &lt;code&gt;litellm&lt;/code&gt; por baixo dos panos em alguns fluxos.&lt;/p&gt;
&lt;p&gt;Aquela busca rápida no Google já me entregou o código abaixo sem eu olhar nenhum
site. É aquele tipo de amigo que te empurra na piscina, quando você não sabe
nadar, pra te ajudar a perder o medo (tô brincando, viu? haha).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Example Usage
from langchain_litellm import ChatLiteLLM

llm = ChatLiteLLM(model=&amp;quot;gpt-4.1-nano&amp;quot;, temperature=0.1)
# or using other providers
# llm = ChatLiteLLM(model=&amp;quot;bedrock/amazon.titan-embed-text-v1&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sério agora, é o tipo de biblioteca invisível que você nem sabe que está usando,
até que ela é comprometida.&lt;/p&gt;
&lt;p&gt;O pacote tem algo em torno de 95 a 97 milhões de downloads por mês. É o motor
invisível de boa parte do ecossistema de IA em Python.&lt;/p&gt;
&lt;p&gt;Quando um pacote desse tamanho é comprometido, o impacto não é local apenas.&lt;/p&gt;
&lt;p&gt;Para você ter uma ideia, as versões maliciosas ficaram no ar por poucas horas,
mas isso foi suficiente pra ~47.000 downloads acontecerem. E não, nem todo mundo
que foi afetado instalou o &lt;code&gt;litellm&lt;/code&gt; diretamente. Mas a gente chega nisso.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Como os hackers conseguiram publicar no PyPI?&lt;/h2&gt;
&lt;p&gt;A doideira deste ataque é super interessante, mas assustadora ao mesmo tempo.&lt;/p&gt;
&lt;p&gt;Não começou no &lt;code&gt;litellm&lt;/code&gt;. Começou no &lt;strong&gt;&lt;a href=&quot;https://trivy.dev/&quot;&gt;Trivy&lt;/a&gt;&lt;/strong&gt;. Ele é um
scanner de vulnerabilidades open-source mantido pela &lt;strong&gt;Aqua Security&lt;/strong&gt;. É
exatamente a ferramenta que deveria estar te protegendo.&lt;/p&gt;
&lt;p&gt;No final de fevereiro, um bot automatizado chamado &lt;code&gt;hackerbot-claw&lt;/code&gt; explorou um
&lt;a href=&quot;https://docs.github.com/pt/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target&quot;&gt;workflow &lt;code&gt;pull_request_target&lt;/code&gt; no GitHub Actions&lt;/a&gt;
do Trivy e roubou um Personal Access Token (PAT). Já usamos esse tipo de token
em vídeos do canal.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://youtu.be/yxxEk68EDgo?t=1140&amp;si=i6cBZGyNZupEGYDt&quot;&gt;&lt;img src=&quot;./images/pat.jpg&quot; alt=&quot;Personal Access Token do GitHub&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A Aqua Security percebeu e tentou revogar... mas a rotação de credenciais foi
incompleta. Justo uma empresa de segurança, trancou todas as portas, ligou o
alarme, mas deixou a janela aberta. Não me pergunte 😅.&lt;/p&gt;
&lt;p&gt;No dia 19 de março, o grupo (conhecido como &lt;strong&gt;TeamPCP&lt;/strong&gt;) voltou com as
credenciais que sobraram e comprometeu o Trivy de vez. Publicaram um binário
infectado e forçaram commits maliciosos em 75 das 76 tags do &lt;code&gt;trivy-action&lt;/code&gt; no
GitHub. Ou seja: qualquer pipeline que rodasse com
&lt;code&gt;uses: aquasecurity/trivy-action&lt;/code&gt; sem travar numa versão específica passou a
executar código malicioso.&lt;/p&gt;
&lt;p&gt;Lembrando que a maioria das pessoas usa &amp;quot;tags&amp;quot; para travar a versão, mas tags
não são imutáveis, apenas os hashes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nota para o eu do futuro: Deixe de preguiça e use o SHA do commit ao invés de
tag para garantir imutabilidade.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;O que esse código malicioso fazia? Vasculhava as variáveis de ambiente do runner
em busca dos secrets. E o CI/CD do &lt;code&gt;litellm&lt;/code&gt; rodava o Trivy pra fazer scan de
segurança sem travar a versão. O malware encontrou o &lt;code&gt;PYPI_PUBLISH&lt;/code&gt; token e
mandou pro servidor dos atacantes.&lt;/p&gt;
&lt;p&gt;Cinco dias depois, com o token em mãos, o &lt;strong&gt;TeamPCP&lt;/strong&gt; publicou as versões
&lt;code&gt;1.82.7&lt;/code&gt; e &lt;code&gt;1.82.8&lt;/code&gt; direto no &lt;a href=&quot;https://pypi.org/&quot;&gt;PyPI&lt;/a&gt;. Sem tocar no GitHub.
Sem PR. Sem nenhum rastro no repositório público.&lt;/p&gt;
&lt;p&gt;Esse é o tipo de coisa que faz você parar um segundo: será que toda dependência
que você adiciona vale a exposição? No caso do &lt;code&gt;litellm&lt;/code&gt;, você realmente vai
usar os mais de 100 provedores? Ou vai só chamar a &lt;strong&gt;OpenAI&lt;/strong&gt; mesmo?&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;O truque do &lt;code&gt;.pth&lt;/code&gt;: por que a versão 1.82.8 é diferente&lt;/h2&gt;
&lt;p&gt;A versão 1.82.7 já fazia algum estrago. Ela tinha código malicioso embutido em
&lt;code&gt;litellm/proxy/proxy_server.py&lt;/code&gt;, mas só executava se você importasse o módulo
proxy no seu código. Dava pra ter sorte se você não usasse isso.&lt;/p&gt;
&lt;p&gt;A versão &lt;strong&gt;1.82.8&lt;/strong&gt;, publicada &lt;strong&gt;13 minutos depois&lt;/strong&gt;, não te perdoaria.&lt;/p&gt;
&lt;p&gt;Ela incluiu um arquivo chamado &lt;code&gt;litellm_init.pth&lt;/code&gt; dentro do wheel. E aqui entra
um mecanismo do Python que muita gente não conhece.&lt;/p&gt;
&lt;p&gt;Toda vez que o Python inicia (qualquer invocação, qualquer contexto) ele passa
pela pasta &lt;code&gt;site-packages&lt;/code&gt; e lê todos os arquivos &lt;code&gt;.pth&lt;/code&gt; que encontrar.&lt;/p&gt;
&lt;p&gt;Normalmente esses arquivos só adicionam caminhos ao &lt;code&gt;sys.path&lt;/code&gt;. Mas &lt;strong&gt;se uma
linha começar com &lt;code&gt;import&lt;/code&gt;, o Python executa ela como código nativo&lt;/strong&gt;. Sem pedir
permissão. Sem precisar que você escreva &lt;code&gt;import litellm&lt;/code&gt; em lugar nenhum.&lt;/p&gt;
&lt;p&gt;O resultado disso é que o payload do &lt;strong&gt;TeamPCP&lt;/strong&gt; rodava em vários cenários:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quando você abria um Jupyter notebook&lt;/li&gt;
&lt;li&gt;Quando o &lt;code&gt;pytest&lt;/code&gt; iniciava&lt;/li&gt;
&lt;li&gt;Quando o Language Server da sua IDE ligava&lt;/li&gt;
&lt;li&gt;Quando você rodava qualquer script Python no ambiente&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Durante o próprio &lt;code&gt;pip install&lt;/code&gt;&lt;/strong&gt;, porque o pip invoca Python&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;E o arquivo &lt;code&gt;litellm_init.pth&lt;/code&gt; estava corretamente declarado no manifesto do
wheel com hash &lt;code&gt;SHA-256&lt;/code&gt; válido. Ferramentas tradicionais de supply chain
scanning não sinalizaram nada.&lt;/p&gt;
&lt;p&gt;O que salvou a situação foi um bug dos próprios hackers. O &lt;code&gt;.pth&lt;/code&gt; spawna um
processo Python pra executar o payload.&lt;/p&gt;
&lt;p&gt;Esse processo filho também inicia o Python... que lê o &lt;code&gt;.pth&lt;/code&gt;... que spawna
outro filho... que lê o &lt;code&gt;.pth&lt;/code&gt;... Já viu, né? Loop, fork bomb e a memória RAM da
turma começou a ser jantada pelos processos.&lt;/p&gt;
&lt;p&gt;A pessoa vai verificar... Puxa um fio daqui, outro dali... E abre uma
&lt;a href=&quot;https://github.com/BerriAI/litellm/issues/24512&quot;&gt;issue dessas&lt;/a&gt;. Cá entre nós,
já pensou receber uma dessas?&lt;/p&gt;
&lt;p&gt;Se eles não tivessem cometido esse erro, o malware poderia ter ficado rodando em
silêncio por dias.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;O que o malware coletava&lt;/h2&gt;
&lt;p&gt;Uma vez rodando, o script vasculhava tudo que encontrava:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Variáveis de ambiente&lt;/strong&gt;: onde ficam &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;, &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt;,
tokens de CI/CD e afins&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chaves SSH&lt;/strong&gt; (&lt;code&gt;~/.ssh/&lt;/code&gt;): par de chaves inteiro, &lt;code&gt;known_hosts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credenciais de cloud&lt;/strong&gt;: &lt;code&gt;~/.aws/credentials&lt;/code&gt;, &lt;code&gt;~/.azure/&lt;/code&gt;, arquivos GCP;
também consultava o IMDS da AWS pra pegar tokens temporários de alta permissão&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;: &lt;code&gt;~/.kube/config&lt;/code&gt; e todos os segredos de todos os namespaces do
cluster&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker&lt;/strong&gt;: &lt;code&gt;~/.docker/config.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Carteiras de criptomoedas&lt;/strong&gt;: Bitcoin, Ethereum, Solana e mais sete&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Histórico do terminal&lt;/strong&gt;: bash e zsh; sim, porque tem gente que digita senha
por acidente no prompt e o histórico guarda&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Banco de dados&lt;/strong&gt;: strings de conexão pra PostgreSQL, MySQL, Redis, MongoDB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arquivos &lt;code&gt;.env&lt;/code&gt;&lt;/strong&gt; recursivamente até 6 diretórios de profundidade&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;E olha só. Os atacantes se preocuparam com a sua privacidade. Eles
criptografavam seus dados roubados com &lt;strong&gt;AES-256-CBC&lt;/strong&gt; e empacotavam com uma
chave &lt;code&gt;RSA-4096&lt;/code&gt; embutida no código (pra que ninguém interceptasse a chave AES
no meio do caminho).&lt;/p&gt;
&lt;p&gt;O pacote &lt;code&gt;tpcp.tar.gz&lt;/code&gt; ia via &lt;code&gt;POST&lt;/code&gt; pra &lt;code&gt;https://models.litellm.cloud/&lt;/code&gt;,
domínio falso, registrado horas antes, feito pra parecer tráfego legítimo da
biblioteca nos logs.&lt;/p&gt;
&lt;p&gt;E se detectasse um cluster &lt;strong&gt;Kubernetes&lt;/strong&gt; acessível, iam além: criavam pods
privilegiados em cada nó do cluster, montavam o sistema de arquivos do host e
instalavam um backdoor persistente. Mesmo que você deletasse o &lt;code&gt;litellm&lt;/code&gt;, a
porta dos fundos continuava aberta.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Por que CrewAI, DSPy, MLflow e outros foram afetados&lt;/h2&gt;
&lt;p&gt;Você pode nunca ter escrito &lt;code&gt;pip install litellm&lt;/code&gt; na sua vida e ainda assim ter
sido afetado. O que foi o meu caso. Mas, já te mostro os comandos que rodei
aqui.&lt;/p&gt;
&lt;p&gt;Se você instala o &lt;code&gt;CrewAI&lt;/code&gt;, e o &lt;code&gt;CrewAI&lt;/code&gt; depende do &lt;code&gt;litellm&lt;/code&gt;. Se o arquivo de
dependências do &lt;code&gt;CrewAI&lt;/code&gt; dizia &lt;code&gt;litellm&amp;gt;=1.80&lt;/code&gt;, sem travar numa versão
específica, o pip simplesmente pega a versão mais nova disponível no momento.
Que, no momento, era a &lt;code&gt;1.82.8&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Análise dos dados do PyPI mostrou que &lt;strong&gt;88% dos ~2.337 pacotes que dependem do
&lt;code&gt;litellm&lt;/code&gt;&lt;/strong&gt; usavam especificações soltas assim.&lt;/p&gt;
&lt;p&gt;Ao fazer uma instalação limpa do &lt;code&gt;CrewAI&lt;/code&gt;, &lt;code&gt;DSPy&lt;/code&gt;, &lt;code&gt;MLflow&lt;/code&gt;, &lt;code&gt;GraphRAG&lt;/code&gt; ou
outros que usam &lt;code&gt;litellm&lt;/code&gt; na manhã do dia 24 de março de 2026, baixava o malware
automaticamente, sem nenhum aviso.&lt;/p&gt;
&lt;p&gt;É o que a galera de segurança chama de &lt;strong&gt;dependência transitiva&lt;/strong&gt;. Você não
instalou o problema. Mas o problema veio junto com o que você instalou.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Como verificar se você foi afetado&lt;/h2&gt;
&lt;p&gt;Aqui vai o passo a passo. Se todos os comandos voltarem vazios ou com erro de
import, você está limpo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IMPORTANTE&lt;/strong&gt;: rode os comandos em uma pasta acima de onde estão seus pacotes
(ou na pasta onde eles estão).&lt;/p&gt;
&lt;p&gt;Aqui vai um exemplo de um dos comandos sendo executado, mas buscando por
&lt;code&gt;llms.py&lt;/code&gt; (só pra encontrar algo):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/termexample1.gif&quot; alt=&quot;Exemplo de comando em gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;Se o seu aparecer algo similar a isso, verifique qual a versão do &lt;code&gt;litellm&lt;/code&gt; está
usando.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verificar instalação global via pip&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip list 2&amp;gt;/dev/null | grep -i litellm
pip3 list 2&amp;gt;/dev/null | grep -i litellm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Verificar via import direto&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 -c &amp;quot;import litellm; print(litellm.__version__, litellm.__file__)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Verificar via uv (se usar)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv pip list 2&amp;gt;/dev/null | grep -i litellm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Varrer todos os ambientes virtuais em &lt;code&gt;.venv&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;find . -path &amp;quot;*/.venv/*/site-packages/litellm*&amp;quot; 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Buscar em arquivos de dependência&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep -ri &amp;quot;litellm&amp;quot; --include=&amp;quot;requirements*.txt&amp;quot; \
                   --include=&amp;quot;pyproject.toml&amp;quot; \
                   --include=&amp;quot;uv.lock&amp;quot; \
                   --include=&amp;quot;poetry.lock&amp;quot; \
                   --include=&amp;quot;Pipfile*&amp;quot; \
                   --include=&amp;quot;setup.py&amp;quot; \
                   --include=&amp;quot;setup.cfg&amp;quot; .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Buscar imports no código-fonte&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep -rE --include=&amp;quot;*.py&amp;quot; -l &amp;quot;import litellm|from litellm&amp;quot; . \
  | grep -v &amp;quot;.venv&amp;quot; | grep -v &amp;quot;__pycache__&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Buscar o arquivo &lt;code&gt;.pth&lt;/code&gt; malicioso&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Esse é o mais importante. Se existir, a máquina está comprometida.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;find / -name &amp;quot;litellm_init.pth&amp;quot; 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pra varrer só dentro dos seus &lt;code&gt;.venv&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;find . -type d -name &amp;quot;.venv&amp;quot; -exec find {} -name &amp;quot;litellm_init.pth&amp;quot; \;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se usar &lt;code&gt;uv&lt;/code&gt;, o cache dele também precisa ser checado:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;find ~/.cache/uv -name &amp;quot;litellm_init.pth&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;O que fazer se encontrou algo&lt;/h2&gt;
&lt;p&gt;Encontrou o &lt;code&gt;.pth&lt;/code&gt; ou a versão &lt;code&gt;1.82.7&lt;/code&gt; ou &lt;code&gt;1.82.8&lt;/code&gt; instalada? A postura correta
agora é assumir que tudo que estava no ambiente foi vazado. Não é pessimismo, é
o protocolo segurança de verdade.&lt;/p&gt;
&lt;p&gt;Vamos ter que rotacionar tudo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Limpe o ambiente e o cache&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Deletar o pacote não é suficiente. O pip (e o &lt;code&gt;uv&lt;/code&gt;) têm cache em disco, e na
próxima instalação podem reaproveitar o wheel contaminado:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# pip
pip uninstall litellm -y
pip cache purge

# uv
uv cache clean
rm -rf ~/.cache/uv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Verifique persistência no sistema&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Se o script rodou no seu ambiente, pode ter instalado um backdoor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ls ~/.config/sysmon/sysmon.py
ls ~/.config/systemd/user/sysmon.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se encontrar qualquer um dos dois: delete e reinicie o serviço do &lt;code&gt;systemd&lt;/code&gt; do
usuário.&lt;/p&gt;
&lt;p&gt;Pra quem usa &lt;code&gt;Kubernetes&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get pods -n kube-system | grep &amp;quot;node-setup&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Qualquer pod com esse nome usando imagem Alpine precisa ser removido
imediatamente.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Observação:&lt;/strong&gt; pedi ajuda para LLMs (3 diferentes) para pontos que não
conheço muito bem, como &lt;strong&gt;Azure&lt;/strong&gt;, &lt;strong&gt;ClaudTrail&lt;/strong&gt;, etc.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rotacione credenciais (por ordem de prioridade)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tokens de CI/CD primeiro:&lt;/strong&gt; (GitHub Actions, GitLab CI, CircleCI, PyPI,
Docker Hub). Se vazar um token de publicação, o atacante passa a ser você&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud (AWS/GCP/Azure):&lt;/strong&gt; access keys, service accounts, roles; consulte os
logs de auditoria (CloudTrail na AWS) pra ver se já usaram as credenciais&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chaves SSH:&lt;/strong&gt; gere um novo par (preferencialmente Ed25519), remova as chaves
antigas do GitHub/GitLab/Bitbucket&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API keys de LLM:&lt;/strong&gt; OpenAI, Anthropic, Google, qualquer provedor que estava
em variável de ambiente&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tudo que estava em &lt;code&gt;.env&lt;/code&gt;:&lt;/strong&gt; DB passwords, webhooks, tokens de integração&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Como se proteger daqui pra frente&lt;/h2&gt;
&lt;p&gt;O que protegeu quem não foi afetado? Basicamente uma coisa: &lt;strong&gt;lockfile&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Projetos usando &lt;code&gt;poetry.lock&lt;/code&gt; ou &lt;code&gt;uv.lock&lt;/code&gt; com versões travadas ficaram
completamente imunes.&lt;/p&gt;
&lt;p&gt;A versão maliciosa nunca foi resolvida porque a versão exata já estava definida
no &lt;code&gt;lockfile&lt;/code&gt;. Este foi um caso de estudos interessante.&lt;/p&gt;
&lt;p&gt;Se eu não atualizei nada, nem instalei nada do zero, o que será instalado será o
que está no &lt;code&gt;lockfile&lt;/code&gt;. Claro que temos alguns hábitos ruins: talvez pudéssemos
travar a versão de forma mais específica nos nossos &lt;code&gt;pyproject.toml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Você não quer a versão mais nova possível, você quer a versão que está
disponível agora.&lt;/p&gt;
&lt;p&gt;Falando em &amp;quot;agora&amp;quot;, vou deixar algumas práticas que já sigo e outras que vou
passar a seguir a partir de agora (eu acho 😅).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trave versões das suas dependências&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Em vez de &lt;code&gt;litellm&amp;gt;=1.80&lt;/code&gt;, porque não usar &lt;code&gt;litellm==1.82.6&lt;/code&gt;? Vale pra qualquer
dependência, não só o &lt;code&gt;litellm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;lockfile&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uv lock&lt;/code&gt;, &lt;code&gt;poetry lock&lt;/code&gt;, &lt;code&gt;pip-compile&lt;/code&gt;. Você commita o &lt;code&gt;lockfile&lt;/code&gt; junto com o
código, e qualquer instalação futura (local, CI, Docker) usa exatamente as
mesmas versões.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Escopo de segredos no CI/CD&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;O token &lt;code&gt;PYPI_PUBLISH&lt;/code&gt; que foi roubado estava disponível pra toda a pipeline, o
tempo todo.&lt;/p&gt;
&lt;p&gt;Para nós, a lição é deixá-las disponíveis apenas nas etapas que precisam
usá-las. Não como variável de ambiente global disponível pra qualquer step,
incluindo steps de terceiros como scanners.&lt;/p&gt;
&lt;p&gt;Também use o
&lt;a href=&quot;https://www.paloaltonetworks.com/cyberpedia/what-is-the-principle-of-least-privilege&quot;&gt;&amp;quot;princípio do menor privilégio&amp;quot;&lt;/a&gt;
com chaves e segredos. Se algo precisa ler, não precisa da permissão de
escrever.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trave ferramentas externas por commit SHA&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No GitHub Actions, &lt;code&gt;uses: aquasecurity/trivy-action@v0.20.0&lt;/code&gt; não é seguro. O
correto seria &lt;code&gt;uses: aquasecurity/trivy-action@COMMIT_SHA_AQUI&lt;/code&gt;. Tag pode ser
reescrita (foi exatamente o que o &lt;strong&gt;TeamPCP&lt;/strong&gt; fez). &lt;code&gt;SHA&lt;/code&gt; não.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Fim&lt;/h2&gt;
&lt;p&gt;Esse incidente é uma prova de que &amp;quot;o repositório está limpo no GitHub&amp;quot; não é
garantia de nada se as credenciais de publicação forem roubadas. O vetor de
ataque foi o CI/CD, não o código.&lt;/p&gt;
&lt;p&gt;A versão segura é qualquer uma &lt;strong&gt;até a 1.82.6&lt;/strong&gt;. Vi algumas pessoas falando de
versões abaixo, mas não consegui confirmar isso. Se você atualizou o &lt;code&gt;litellm&lt;/code&gt;
(direto ou indiretamente) no dia 24 de março de 2026, vale rodar os comandos
acima pra checar.&lt;/p&gt;
&lt;p&gt;Qualquer dúvida, comenta aí. Valeu. Até o próximo.&lt;/p&gt;
</content:encoded></item><item><title>Tools - Ferramentas para desenvolvedores</title><link>https://otaviomiranda.com.br/2026/tools-ferramentas-para-desenvolvedores/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/tools-ferramentas-para-desenvolvedores/</guid><description>Várias ferramentas para desenvolvedores que venho tirando do papel atualmente. Tools é onde vou manter todas no mesmo lugar.</description><pubDate>Wed, 25 Mar 2026 21:07:00 GMT</pubDate><content:encoded>&lt;p&gt;Com o advento da IA &lt;em&gt;(&amp;quot;Inteligência&amp;quot; Artificial)&lt;/em&gt;, venho tirando muitas
ferramentas para desenvolvedores que tinha vontade de criar do papel.&lt;/p&gt;
&lt;p&gt;Tools &lt;em&gt;(esta página)&lt;/em&gt; é onde vou agregar todas essas ferramentas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Links rápidos para as ferramentas&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;/editor/&quot;&gt;Markdown Editor&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://wireguard.otaviomiranda.com.br/&quot;&gt;WireGuard Config Generator&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://sshtoolkit.otaviomiranda.com.br/&quot;&gt;SSH Toolkit&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/luizomf/loudterm&quot;&gt;Loudterm TTS&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/luizomf/dotfiles&quot;&gt;dotfiles&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=omthemes.omthemes&quot;&gt;Om Theme - VS Code&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/luizomf/bgremover&quot;&gt;BGRemover&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Markdown Editor&lt;/h2&gt;
&lt;p&gt;Criei este &lt;a href=&quot;/editor/&quot;&gt;Markdown Editor&lt;/a&gt; na esperança de escrever em qualquer
lugar, mesmo sem o meu editor favorito (&lt;code&gt;nvim&lt;/code&gt;). Na verdade, ele acabou sendo
muito mais do que isso. Por rodar no navegador, sempre estou com ele aberto para
anotações em geral.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Link: &lt;strong&gt;&lt;a href=&quot;/editor/&quot;&gt;Markdown Editor&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;WireGuard Config Generator&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;WireGuard Config Generator&lt;/strong&gt; é um gerador de configurações para o WireGuard.
Isso é uma mão na roda para quem não tem uma rede grande o suficiente para um
gerenciador, mas grande para se perder nos arquivos de configuração. Testei com
10 máquinas, funcionou perfeitamente. Ele gerenciada o processo de criação das
configurações e evita erros bobos, como IP ou chaves no peer incorreto.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Link:
&lt;strong&gt;&lt;a href=&quot;https://wireguard.otaviomiranda.com.br/&quot;&gt;WireGuard Config Generator&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;SSH Toolkit&lt;/h2&gt;
&lt;p&gt;Seguindo a mesma lógica do WireGuard Config Generator, também criei o &lt;strong&gt;SSH
Toolkit&lt;/strong&gt;. Um monte de ferramentas que te ajudam a configurar o SSH de ponta a
ponta. Inclusive, explicando o que é cada uma das configurações. Também gera os
comandos que precisa executar.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Link: &lt;strong&gt;&lt;a href=&quot;https://sshtoolkit.otaviomiranda.com.br/&quot;&gt;SSH Toolkit&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Loudterm TTS&lt;/h2&gt;
&lt;p&gt;Loudterm TTS é um aplicativo que usa o modelo Kokoro-82M para converter texto em
fala. Funciona com 8 idiomas, incluindo PT-BR. Também tem uma qualidade
impressionante para algo gratuito. Eu ouço artigos em inglês e português
enquanto faço outras coisas. Tudo gerado por ele.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Link: &lt;strong&gt;&lt;a href=&quot;https://github.com/luizomf/loudterm&quot;&gt;Loudterm TTS&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;dotfiles&lt;/h2&gt;
&lt;p&gt;Todo o meu ambiente Dev está aqui.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Link: &lt;strong&gt;&lt;a href=&quot;https://github.com/luizomf/dotfiles&quot;&gt;dotfiles&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;OM Theme - Tema para VS Code&lt;/h2&gt;
&lt;p&gt;Tema que criei enquanto usava o VS Code. As pessoas gostam bastante dele.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Link:
&lt;strong&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=omthemes.omthemes&quot;&gt;Om Theme - VS Code&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;BGRemover - Remove o fundo de imagens&lt;/h2&gt;
&lt;p&gt;Remove o fundo de imagens mantendo o personagem principal e fundo transparente.
Garanto que você já deve ter pagado algum software para fazer isso.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Link: &lt;strong&gt;&lt;a href=&quot;https://github.com/luizomf/bgremover&quot;&gt;BGRemover&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>SSH Tunnels: o canivete suíço que você não está usando</title><link>https://otaviomiranda.com.br/2026/ssh-tunnels-o-canivete-suico-que-voce-nao-esta-usando/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/ssh-tunnels-o-canivete-suico-que-voce-nao-esta-usando/</guid><description>Aprenda SSH Tunnels na prática: Local -L, Remote -R e Dynamic -D. Acesse serviços remotos, exponha portas locais e crie proxies SOCKS com um comando.</description><pubDate>Wed, 25 Mar 2026 21:06:00 GMT</pubDate><content:encoded>&lt;p&gt;Aproveitando o embalo do meu último conteúdo sobre
&lt;a href=&quot;/2026/vpn-com-wireguard-o-guia-definitivo/&quot;&gt;VPN com o WireGuard&lt;/a&gt;, hoje trago o
seu primo minimalista: SSH Tunnels na prática. Vamos ver um túnel local com -L,
remoto com -R e dinâmico com -D.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/ssh-tunnels-1.jpg&quot; alt=&quot;SSH Tunnel Cover&quot;&gt;&lt;/p&gt;
&lt;p&gt;Depois dessa, você vai conseguir expor um serviço rodando na sua máquina para
fora (mesmo com NAT ou firewall no meio do caminho), acessar um serviço remoto
como se estivesse sentado de frente para o próprio servidor e, de quebra, ainda
criar um proxy SOCKS. Tudo isso via SSH.&lt;/p&gt;
&lt;p&gt;Nesse texto, vou assumir que você já tem uma conexão SSH com um servidor
qualquer. Ele nem precisa ter IP público para os exemplos fazerem sentido, mas
vai ser muito mais legal se tiver 😈.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Falando em servidores&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Se você estiver precisando de um servidor para hospedar seu site, aplicação ou
projeto, dá uma olhada na &lt;strong&gt;Hostinger&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Estou deixando meu link com desconto abaixo. Isso pode te garantir até 2 anos de
benefícios.&lt;/p&gt;
&lt;p&gt;Acesse: &lt;a href=&quot;https://hostinger.com/otaviomiranda&quot;&gt;Hostinger + Otávio Miranda&lt;/a&gt;&lt;br&gt;Cupom: &lt;code&gt;OTAVIOMIRANDA&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Em vídeo:&lt;/h2&gt;
&lt;p&gt;Se quiser assistir ao invés de ler (mas aqui tem muito mais informação):&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://youtu.be/s-B2A8A4hHc&quot;&gt;&lt;img src=&quot;./images/ssh-tunnels-thumb.jpg&quot; alt=&quot;Acesse QUALQUER Coisa de Qualquer Lugar: SSH Tunnels&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/s-B2A8A4hHc&quot;&gt;https://youtu.be/s-B2A8A4hHc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;O que é um SSH Tunnel?&lt;/h2&gt;
&lt;p&gt;Todo mundo já sabe que SSH serve para abrir terminal em outra máquina. O que
pouca gente te ensina é que aquele mesmo canal criptografado pode carregar
&lt;strong&gt;outro tipo de tráfego&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;E se você parar para pensar, tudo começa a fazer mais sentido. O túnel já está
aberto e seguro, que tal aproveitar a oportunidade para encaixar uma conexão com
banco de dados, uma requisição HTTP ou qualquer coisa que use TCP?&lt;/p&gt;
&lt;p&gt;O SSH suporta três tipos clássicos de &lt;em&gt;tunnel&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Local&lt;/strong&gt; &lt;code&gt;-L&lt;/code&gt; - Abre uma porta na sua máquina e encaminha para um destino visto
do lado do servidor SSH. Com ele você poderia dizer algo como: &lt;em&gt;&amp;quot;Quando alguém
acessar a porta 4321 na minha máquina, conecte em &lt;code&gt;localhost:5432&lt;/code&gt; no servidor e
me entregue isso aqui&amp;quot;&lt;/em&gt;. E, magicamente, ao usar &lt;code&gt;localhost:4321&lt;/code&gt;, você recebe
um PostgreSQL como se estivesse lá no servidor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remote&lt;/strong&gt; &lt;code&gt;-R&lt;/code&gt; - Faz o caminho contrário: abre uma porta no servidor e manda o
que cair nela para um destino visto do lado da sua máquina. É a carta na manga
quando você quer mostrar algo local para fora usando a conexão SSH que já saiu
da sua rede. Alguém acessa a porta 8000 no servidor e o SSH chama a sua máquina
na porta 3000.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dynamic&lt;/strong&gt; &lt;code&gt;-D&lt;/code&gt; - Cria um proxy SOCKS na máquina onde você executou o SSH. Tudo
que você apontar para esse proxy passa a sair pelo servidor SSH. Se você
configurar isso no sistema, os apps que respeitam o proxy do sistema entram no
mesmo barco também.&lt;/p&gt;
&lt;p&gt;Se quiser usar uma ferramenta automatizada, fiz o
&lt;a href=&quot;https://sshtoolkit.otaviomiranda.com.br/&quot;&gt;SSH Toolkit&lt;/a&gt;, é uma ferramenta de
código aberto que te ajuda a gerar esses túneis de forma visual. Faz mais do que
isso, mas vou te falando ao longo do texto.&lt;/p&gt;
&lt;p&gt;Bora destrinchar cada um.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;sshd_config&lt;/h2&gt;
&lt;p&gt;Estou escrevendo isso depois de terminar o artigo inteiro. Eu tinha espalhado
essas configurações pelo texto, mas faz mais sentido deixar tudo junto antes de
você sair abrindo túnel e depois culpar o SSH (client) por algo que foi o &lt;code&gt;sshd&lt;/code&gt;
(server).&lt;/p&gt;
&lt;p&gt;Abra o arquivo de configuração do &lt;strong&gt;servidor SSH&lt;/strong&gt;. Em geral ele fica aqui:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Linux
/etc/ssh/sshd_config

# Algumas distros também usam includes aqui
/etc/ssh/sshd_config.d/*.conf

# macOS
/etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No OpenSSH, &lt;code&gt;AllowTcpForwarding&lt;/code&gt;, &lt;code&gt;PermitOpen&lt;/code&gt; e &lt;code&gt;PermitListen&lt;/code&gt; já costumam vir
liberados por padrão. O que costuma pegar a galera de surpresa é o
&lt;code&gt;GatewayPorts&lt;/code&gt;, porque ele vem de fábrica como &lt;code&gt;no&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Se você endureceu a config do &lt;code&gt;sshd&lt;/code&gt;, garanta pelo menos isso:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ssh-config&quot;&gt;# Necessário para -L, -R e -D
AllowTcpForwarding yes

# Só se você restringiu destinos ou portas remotas
# No final, deixo um aviso sobre isso.
PermitOpen any
PermitListen any

# Necessário para expor um -R para outras máquinas
GatewayPorts clientspecified
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Resumo rápido do que interessa:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AllowTcpForwarding yes&lt;/code&gt; libera os forwards TCP.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PermitOpen any&lt;/code&gt; só é necessário se você travou os destinos permitidos para
&lt;code&gt;-L&lt;/code&gt;/&lt;code&gt;-D&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PermitListen any&lt;/code&gt; só é necessário se você travou as portas/endereços
permitidos para &lt;code&gt;-R&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GatewayPorts clientspecified&lt;/code&gt; deixa o cliente escolher se o &lt;code&gt;-R&lt;/code&gt; vai escutar
só em &lt;code&gt;localhost&lt;/code&gt; ou em &lt;code&gt;0.0.0.0&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depois de alterar isso, reinicie o serviço SSH.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Linux
sudo systemctl restart sshd # ou ssh

# macOS
sudo launchctl kickstart -k system/com.openssh.sshd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O &lt;a href=&quot;https://sshtoolkit.otaviomiranda.com.br/&quot;&gt;SSH Toolkit&lt;/a&gt; também te ajuda nisso.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Local Forward (&lt;code&gt;-L&lt;/code&gt;): &amp;quot;trago a porta de lá pra cá&amp;quot;&lt;/h2&gt;
&lt;p&gt;Esse é o mais simples. Você abre uma porta na &lt;strong&gt;sua máquina&lt;/strong&gt; e tudo que chegar
nela é encaminhado, pelo túnel SSH, para um destino do outro lado. O resultado é
que, ao acessar essa porta, quem responde é o servidor.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/diagram-l.jpg&quot; alt=&quot;Local Forward -L trago a porta de lá pra cá&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Todos os diagramas estão
&lt;a href=&quot;https://excalidraw.com/#json=pZCWitiR0Byu9xrH4Ct3D,yQJRrX-QjT4rDzTaAs5mBA&quot;&gt;aqui (via excalidraw)&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;A sintaxe&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Sua porta local encaminha para &amp;lt;host:porta&amp;gt; visto do lado do servidor SSH
ssh -L &amp;lt;porta_local&amp;gt;:&amp;lt;host_remoto&amp;gt;:&amp;lt;porta_remota&amp;gt; &amp;lt;user@servidor&amp;gt;

# Obs.: na maioria das vezes eu uso -N antes de -L para ele só focar no túnel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Fluxo&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sua_máquina:porta_local -&amp;gt; túnel SSH -&amp;gt; servidor -&amp;gt; host_remoto:porta_remota
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Na prática&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Você tem um VPS e subiu um servidorzinho HTTP nele. Esse servidor escuta em
&lt;code&gt;localhost:8080&lt;/code&gt;. Você não liberou a porta no firewall, não configurou NGINX,
não fez nada. Exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# caminho_qualquer tem um index.html
python3 -m http.server 8080 -d caminho_qualquer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Na sua máquina você pode encaminhar a porta local &lt;code&gt;8080&lt;/code&gt; para &lt;code&gt;localhost:8080&lt;/code&gt;
do servidor assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# ---&amp;gt; você:vps ---&amp;gt;
ssh -L 8080:localhost:8080 user@seu-vps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora, se você abrir &lt;code&gt;http://localhost:8080&lt;/code&gt; no navegador, vai ver o conteúdo do
VPS como se estivesse lá. Sem liberar porta, sem configurar nada. Tipo um
&lt;em&gt;teletransporte&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;O &lt;code&gt;localhost&lt;/code&gt; ali no meio da sintaxe se refere ao ponto de vista do
&lt;strong&gt;servidor&lt;/strong&gt;. Ou seja, &amp;quot;conecte em &lt;code&gt;localhost:8080&lt;/code&gt; do servidor e traga pra
minha porta &lt;code&gt;8080&lt;/code&gt;&amp;quot;.&lt;/p&gt;
&lt;h3&gt;Outro exemplo: banco de dados&lt;/h3&gt;
&lt;p&gt;Um PostgreSQL rodando no servidor, porta &lt;code&gt;5432&lt;/code&gt;, escutando só em &lt;code&gt;localhost&lt;/code&gt;.
Sem acesso externo.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Isso é o mais comum, mesma porta
ssh -L 5432:localhost:5432 deploy@servidor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Conecte seu cliente local em &lt;code&gt;localhost:5432&lt;/code&gt;. Pronto, você está no banco
remoto.&lt;/p&gt;
&lt;p&gt;Se a porta &lt;code&gt;5432&lt;/code&gt; já estiver ocupada na sua máquina, sem pânico. É só mudar a
local:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Você controla qual a porta quer usar no seu lado
ssh -L 15432:localhost:5432 deploy@servidor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora use &lt;code&gt;localhost:15432&lt;/code&gt;. Isso continuará chamando &amp;quot;5432&amp;quot; no lado do
servidor.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Remote Forward (&lt;code&gt;-R&lt;/code&gt;): &amp;quot;mando minha porta pra lá&amp;quot;&lt;/h2&gt;
&lt;p&gt;Esse é o inverso. Você abre uma porta &lt;strong&gt;no servidor&lt;/strong&gt; e tudo que chegar lá volta
pelo túnel até a sua máquina. O resultado é que, ao acessar essa porta no
servidor, quem responde é sua máquina local.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/diagram-r.jpg&quot; alt=&quot;Remote Forward -R mando minha porta pra lá&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A sintaxe&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# A porta no servidor encaminha para &amp;lt;host:porta&amp;gt; visto do lado da sua máquina
ssh -R &amp;lt;porta_remota&amp;gt;:&amp;lt;destino_local&amp;gt;:&amp;lt;porta_destino&amp;gt; &amp;lt;user@servidor&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;O fluxo&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;servidor:porta_remota → túnel SSH → sua_máquina → destino:porta_destino
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Na prática&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Agora vira o jogo. Você tem um servidorzinho rodando &lt;strong&gt;na sua máquina&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Mesmo exemplo, só que... 😏&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# caminho_qualquer tem index.html
python3 -m http.server 8080 -d caminho_qualquer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quer que alguém acesse isso pelo seu VPS? Faz esse túnel com -R:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Cria a porta 8080 no VPS apontando para a sua máquina local
ssh -R 8080:localhost:8080 user@seu-vps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse comando cria uma porta &lt;code&gt;8080&lt;/code&gt; &lt;strong&gt;no VPS&lt;/strong&gt; apontando para &lt;code&gt;localhost:8080&lt;/code&gt;
&lt;strong&gt;da sua máquina&lt;/strong&gt;. O &lt;code&gt;localhost&lt;/code&gt; aqui é visto do lado do cliente SSH, ou seja,
do computador onde você executou o comando.&lt;/p&gt;
&lt;h3&gt;Wait, what? Não funcionou?&lt;/h3&gt;
&lt;p&gt;Isso acontece. Por padrão, o &lt;code&gt;-R&lt;/code&gt; escuta só em &lt;code&gt;127.0.0.1&lt;/code&gt; no servidor. Ou seja,
um &lt;code&gt;curl http://localhost:8080&lt;/code&gt; rodando no próprio VPS funciona de boa, mas o
tráfego externo (da Internet) bate de frente numa porta fechada.&lt;/p&gt;
&lt;p&gt;Para liberar acesso externo, o servidor SSH precisa ter isso no
&lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ssh-config&quot;&gt;GatewayPorts clientspecified
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eu prefiro &lt;code&gt;clientspecified&lt;/code&gt; porque você escolhe no comando se quer deixar isso
privado ou público. Aí sim você especifica o bind:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -R 0.0.0.0:8080:localhost:8080 user@seu-vps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se você usar &lt;code&gt;GatewayPorts yes&lt;/code&gt;, o &lt;code&gt;sshd&lt;/code&gt; força bind no wildcard e você perde um
pouco desse controle fino.&lt;/p&gt;
&lt;p&gt;Depois de alterar o &lt;code&gt;sshd_config&lt;/code&gt;, reinicie o serviço SSH:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Linux
sudo systemctl restart sshd

# macOS
sudo launchctl kickstart -k system/com.openssh.sshd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E não esqueça do firewall. Se a porta &lt;code&gt;8080&lt;/code&gt; está bloqueada no firewall do VPS,
o &lt;em&gt;tunnel&lt;/em&gt; funciona, mas ninguém de fora chega lá.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Dynamic Forward (&lt;code&gt;-D&lt;/code&gt;): proxy SOCKS&lt;/h2&gt;
&lt;p&gt;Os dois anteriores conectam portas específicas. O &lt;code&gt;-D&lt;/code&gt; é diferente: ele cria um
&lt;strong&gt;proxy SOCKS&lt;/strong&gt; na sua máquina. Qualquer aplicação que suporte SOCKS pode mandar
tráfego por ele, e o servidor SSH conecta no destino final.&lt;/p&gt;
&lt;p&gt;Você não precisa definir o destino antes. O proxy decide na hora.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/diagram-d.jpg&quot; alt=&quot;Dynamic Forward -D proxy SOCKS&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A sintaxe&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -D &amp;lt;porta_local&amp;gt; &amp;lt;user@servidor&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;O fluxo&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;seu app -&amp;gt; SOCKS proxy (localhost:porta) -&amp;gt; túnel SSH -&amp;gt; servidor -&amp;gt; destino final
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Na prática&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Você quer navegar como se estivesse no seu VPS. Não é uma VPN completa do
sistema inteiro, mas para os apps que usam o proxy a sensação é bem parecida.&lt;/p&gt;
&lt;p&gt;Talvez para testar um bloqueio de IP, talvez porque você está naquele Wi-Fi
público super duvidoso de aeroporto e não quer ninguém &amp;quot;cheirando&amp;quot; (sniffing)
seus pacotes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -D 1080 user@seu-vps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora configure o proxy SOCKS no sistema ou no navegador.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No macOS:&lt;/strong&gt; vá em Ajustes do Sistema -&amp;gt; Rede -&amp;gt; a interface que está usando
(Wi-Fi, por exemplo) -&amp;gt; Detalhes -&amp;gt; Proxies -&amp;gt; ative &lt;strong&gt;Proxy SOCKS&lt;/strong&gt; → coloque
&lt;code&gt;127.0.0.1&lt;/code&gt; e porta &lt;code&gt;1080&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Abra o navegador e acesse &lt;code&gt;ifconfig.me&lt;/code&gt; ou &lt;code&gt;ip.me&lt;/code&gt;. O IP que aparece deve ser o
do seu VPS, não o seu.&lt;/p&gt;
&lt;p&gt;Só não mistura as coisas: isso vale para o que estiver usando o proxy, não para
todo e qualquer pacote do sistema.&lt;/p&gt;
&lt;h3&gt;Um detalhe sobre DNS&lt;/h3&gt;
&lt;p&gt;Dependendo da aplicação, a resolução DNS pode acontecer &lt;strong&gt;antes&lt;/strong&gt; de ir para o
proxy. Isso meio que entrega o jogo.&lt;/p&gt;
&lt;p&gt;No Firefox, por exemplo, vá em &lt;code&gt;about:config&lt;/code&gt; e mude
&lt;code&gt;network.proxy.socks_remote_dns&lt;/code&gt; para &lt;code&gt;true&lt;/code&gt;. Assim até as consultas DNS passam
pelo tunnel.&lt;/p&gt;
&lt;p&gt;Pra variar, o &lt;a href=&quot;https://sshtoolkit.otaviomiranda.com.br/&quot;&gt;SSH Toolkit&lt;/a&gt; também faz
isso.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Flags úteis&lt;/h2&gt;
&lt;p&gt;Na maioria das vezes, você não quer um shell remoto. Quer só o &lt;em&gt;tunnel&lt;/em&gt;. Essas
flags resolvem:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-N&lt;/code&gt; - sem comando remoto&lt;/p&gt;
&lt;p&gt;Não executa nenhum comando no servidor. Só mantém o tunnel aberto.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -N -L 8080:localhost:8080 user@servidor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-f&lt;/code&gt; - manda para o background&lt;/p&gt;
&lt;p&gt;Joga o processo SSH pro background depois de autenticar.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -f -N -L 8080:localhost:8080 user@servidor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Combinar &lt;code&gt;-f&lt;/code&gt; com &lt;code&gt;-N&lt;/code&gt; é o padrão para &lt;em&gt;tunnels&lt;/em&gt; em segundo plano. Eu gosto de
somar &lt;code&gt;-o ExitOnForwardFailure=yes&lt;/code&gt; para o SSH não fingir que deu tudo certo
quando a porta já estava ocupada.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -f -N -o ExitOnForwardFailure=yes -L 8080:localhost:8080 user@servidor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quando cansar da brincadeira, é só caçar e matar o processo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# encontre o PID
ps aux | grep ssh

# ou, se souber a porta
lsof -i :8080

# mate o processo
kill &amp;lt;PID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Keepalive&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Tunnels&lt;/em&gt; podem morrer se ficarem tempo demais sem tráfego ou se algum NAT no
meio resolver te abandonar. Para reduzir isso:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -L 8080:localhost:8080 user@servidor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se o servidor ficar sem responder, o SSH detecta isso e encerra a conexão sem te
deixar adivinhando o que aconteceu.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Múltiplos tunnels de uma vez&lt;/h2&gt;
&lt;p&gt;Pode empilhar quantos quiser:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -L 5432:localhost:5432 -L 8080:localhost:80 -L 3000:localhost:3000 user@servidor
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Coloca isso no SSH config&lt;/h2&gt;
&lt;p&gt;Se você levanta um tunnel com frequência, pelo amor de Deus, pare de digitar
esse textão toda vez. Coloca lá no seu &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Host vps-tunnel
    HostName seu-vps.com
    User deploy
    LocalForward 5432 localhost:5432
    ExitOnForwardFailure yes
    ServerAliveInterval 60
    ServerAliveCountMax 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora basta:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -N vps-tunnel
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Tunnels persistentes com &lt;code&gt;autossh&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;O SSH não reconecta sozinho. Se a conexão cair, o &lt;em&gt;tunnel&lt;/em&gt; morre junto. O
&lt;code&gt;autossh&lt;/code&gt; resolve isso: ele monitora a conexão e reinicia se precisar.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;autossh -M 0 -f -N -o &amp;quot;ServerAliveInterval=60&amp;quot; -o &amp;quot;ServerAliveCountMax=3&amp;quot; \
  -L 5432:localhost:5432 deploy@servidor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O &lt;code&gt;-M 0&lt;/code&gt; desativa a porta de monitoramento do &lt;code&gt;autossh&lt;/code&gt; e usa o
&lt;code&gt;ServerAliveInterval&lt;/code&gt; do próprio SSH, que funciona melhor.&lt;/p&gt;
&lt;p&gt;Se quiser algo ainda mais robusto, crie um serviço no systemd e ele reinicia
automaticamente em caso de falha.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Coisas que pegam gente desprevenida&lt;/h2&gt;
&lt;h3&gt;Só funciona com TCP&lt;/h3&gt;
&lt;p&gt;SSH Tunnels encaminham &lt;strong&gt;TCP&lt;/strong&gt;. Se você precisa de UDP de verdade, vá de
WireGuard. Se precisa de um redirecionamento pontual fora do SSH, &lt;code&gt;socat&lt;/code&gt; pode
ajudar. Se quer algo mais para o lado de &amp;quot;quase uma VPN por SSH&amp;quot; para TCP/DNS,
dá uma olhada no &lt;code&gt;sshuttle&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Portas privilegiadas&lt;/h3&gt;
&lt;p&gt;Portas abaixo de 1024 precisam de root no lado que vai abrir a escuta. Se você
tentar isso com &lt;code&gt;-L&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -L 80:localhost:80 user@servidor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vai falhar sem &lt;code&gt;sudo&lt;/code&gt;. Use uma porta alta:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -L 8080:localhost:80 user@servidor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No &lt;code&gt;-R&lt;/code&gt;, a ideia é a mesma, mas do lado do servidor.&lt;/p&gt;
&lt;h3&gt;Firewall&lt;/h3&gt;
&lt;p&gt;O tunnel funciona entre as máquinas, mas se o firewall do servidor bloqueia a
porta do &lt;code&gt;-R&lt;/code&gt;, ninguém de fora chega. Libere a porta:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# UFW
sudo ufw allow 8080

# firewalld
sudo firewall-cmd --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Segurança&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Tunnels&lt;/em&gt; podem furar políticas de rede. Use com responsabilidade.&lt;/p&gt;
&lt;p&gt;Se você mandar o bind para &lt;code&gt;0.0.0.0&lt;/code&gt; no &lt;code&gt;-L&lt;/code&gt;, &lt;code&gt;-D&lt;/code&gt; ou &lt;code&gt;-R&lt;/code&gt;, qualquer máquina que
consiga alcançar essa porta pode usar o forward. Por isso o SSH costuma ficar em
&lt;code&gt;localhost&lt;/code&gt; por padrão. Se for abrir para fora, saiba exatamente o que está
fazendo.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Referência rápida&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Acessar serviço remoto localmente - &lt;code&gt;ssh -L 8080:localhost:8080 user@servidor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Expor serviço local pelo servidor -
&lt;code&gt;ssh -R 0.0.0.0:8080:localhost:3000 user@vps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Proxy SOCKS para navegação - &lt;code&gt;ssh -D 1080 user@servidor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Tunnel em background - &lt;code&gt;ssh -f -N -L 8080:localhost:8080 user@servidor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Tunnel persistente - &lt;code&gt;autossh -M 0 -f -N -L 8080:db:5432 user@vps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Múltiplos forwards - &lt;code&gt;ssh -L 5432:db:5432 -L 8080:web:80 user@srv&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Quando usar cada tipo?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Precisa acessar algo que está numa rede remota - Local (&lt;code&gt;-L&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Precisa expor algo local para o mundo - Remote (&lt;code&gt;-R&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Precisa acessar vários serviços sem criar um &lt;em&gt;tunnel&lt;/em&gt; para cada um - Dynamic
(&lt;code&gt;-D&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Quer navegar com o IP de outra máquina - Dynamic (&lt;code&gt;-D&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;SSH Tunnel é uma daquelas ferramentas que resolvem o problema em uma linha e,
mesmo assim, a maioria das pessoas não usa.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-L&lt;/code&gt; traz, &lt;code&gt;-R&lt;/code&gt; manda, &lt;code&gt;-D&lt;/code&gt; faz proxy. Mais do que isso, é saber que você não
precisa liberar porta no firewall, configurar reverse proxy ou instalar nada
extra toda vez que quer acessar algo de outra máquina.&lt;/p&gt;
&lt;p&gt;Isso já está instalado no seu sistema. Usa.&lt;/p&gt;
&lt;p&gt;PS.: essa é a minha configuração do ssh ao terminar de escrever isso. Tem um
aviso enorme só para me lembrar.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ssh-config&quot;&gt;PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
PermitRootLogin no
PermitEmptyPasswords no
UsePAM yes
AuthenticationMethods publickey
PermitUserEnvironment no
PermitUserRC no
X11Forwarding no
AllowStreamLocalForwarding no
AllowAgentForwarding no
PermitTunnel no
MaxAuthTries 4
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
PrintMotd no
UseDNS no

# ===================================================================
# 🚨 ATENÇÃO: ZONA DE PERIGO (TÚNEIS ESCANCARADOS) 🚨
# ===================================================================
# O bloco abaixo permite que qualquer túnel (-R) exponha portas
# diretamente para a INTERNET PÚBLICA (0.0.0.0).
# Excelente para o nosso laboratório e testes, mas se for rodar em
# PRODUÇÃO real, mude o GatewayPorts para &amp;#39;no&amp;#39; ou &amp;#39;clientspecified&amp;#39;
# para evitar expor serviços internos acidentalmente.
# ===================================================================
AllowTcpForwarding yes
PermitOpen any
PermitListen any
GatewayPorts yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fui.&lt;/p&gt;
&lt;p&gt;Ah, o &lt;a href=&quot;https://sshtoolkit.otaviomiranda.com.br/&quot;&gt;SSH Toolkit&lt;/a&gt; faz ssh hardening
também.&lt;/p&gt;
</content:encoded></item><item><title>OpenAI compra Astral (uv, ruff e ty vão junto)</title><link>https://otaviomiranda.com.br/2026/openai-compra-astral-uv-ruff-e-ty-vao-junto/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/openai-compra-astral-uv-ruff-e-ty-vao-junto/</guid><description>A OpenAI acabou de anunciar a compra da Astral. Se esse nome não te diz nada: Astral é a empresa por trás do Ruff, do uv e do ty. </description><pubDate>Thu, 19 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Eu não sou o cara das news, como você já deve ter notado pelo meu conteúdo. Mas
aqui não deu pra pular.&lt;/p&gt;
&lt;p&gt;Em vídeo:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://youtu.be/kHePnM4NPWo&quot;&gt;&lt;img src=&quot;./images/openai-astral.jpg&quot; alt=&quot;OpenAI compra Astral - uv add astral&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Link: &lt;a href=&quot;https://youtu.be/kHePnM4NPWo&quot;&gt;youtu.be/kHePnM4NPWo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A
&lt;a href=&quot;https://openai.com/index/openai-to-acquire-astral/&quot;&gt;OpenAI acabou de anunciar a compra da Astral&lt;/a&gt;.
Só pra te lembrar: Astral é a empresa por trás do &lt;code&gt;Ruff&lt;/code&gt;, do &lt;code&gt;uv&lt;/code&gt; e do &lt;code&gt;ty&lt;/code&gt;. Ou
seja, o linter mais rápido do ecossistema Python, o gerenciador de pacotes que
tá substituindo pip, poetry, pyenv e companhia, e o type checker novinho que
acabou de sair.&lt;/p&gt;
&lt;p&gt;Se você acompanha o canal, sabe que eu uso e recomendo
&lt;a href=&quot;https://youtu.be/HuAc85cLRx0?si=E_68NaPogXqY_NE4&quot;&gt;bastante o &lt;code&gt;uv&lt;/code&gt;&lt;/a&gt; e o &lt;code&gt;ruff&lt;/code&gt;.
Falei disso até no
&lt;a href=&quot;https://www.udemy.com/course/python-3-do-zero-ao-avancado/?referralCode=5DDCAD01311E2A9599B2&quot;&gt;meu curso de Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Fiz aquele
&lt;a href=&quot;https://youtu.be/IeyO3TnHcaw?si=e1g7YlcquGc8mxby&quot;&gt;vídeo de Docker com uv e multistage build&lt;/a&gt;
que a galera gostou bastante. Então sim, eu tenho skin in the game aqui. Essas
ferramentas fazem parte do meu dia a dia e do que eu ensino. Bora entender o que
tá acontecendo.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;A compra&lt;/h2&gt;
&lt;p&gt;Dia 19 de março de 2026 (também conhecido como poucas horas atrás), a OpenAI
anuncia que vai adquirir a Astral. Ambas as empresas já estão anunciando em
&lt;a href=&quot;https://astral.sh/blog/openai&quot;&gt;seus blogs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;O time inteiro vai ser absorvido pela divisão do Codex, aquele produto de coding
com IA da OpenAI que já tem mais de 2 milhões de usuários semanais e que uso
constantemente.&lt;/p&gt;
&lt;p&gt;O Charlie Marsh, fundador da Astral, disse que vai continuar construindo
abertamente, junto com a comunidade.&lt;/p&gt;
&lt;p&gt;A OpenAI prometeu manter os projetos open source.&lt;/p&gt;
&lt;p&gt;Valor da aquisição? Não divulgaram. A Astral tinha levantado uns
&lt;a href=&quot;https://astral.sh/blog/announcing-astral-the-company-behind-ruff&quot;&gt;4 milhões em seed com Accel e Caffeinated Capital&lt;/a&gt;,
mais uma Series A e uma Series B, nada absurdo comparado com as outras loucuras
que a OpenAI anda comprando.&lt;/p&gt;
&lt;p&gt;A OpenAI já tinha comprado a
&lt;a href=&quot;https://exame.com/inteligencia-artificial/openai-compra-startup-do-criador-do-iphone-por-us-64-bilhoes-e-mira-levar-ia-ao-mundo-fisico/&quot;&gt;empresa do Jony Ive por 6,4 bilhões de dólares&lt;/a&gt;,
tentou comprar a Windsurf por 3 bilhões, mas não deu certo e contratou o
&lt;a href=&quot;https://steipete.me/posts/2026/openclaw&quot;&gt;Dev do OpenClaw&lt;/a&gt; (não sei dizer se
compraram o OpenClaw por que realmente não verifiquei). Mas, o ponto é que a
Astral deve ter saído barata para eles.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Motivações da OpenAI&lt;/h2&gt;
&lt;p&gt;E aí a primeira pergunta que vem na cabeça: por que a OpenAI, uma empresa de IA,
quer comprar ferramentas de linting e gerenciamento de pacotes Python?&lt;/p&gt;
&lt;p&gt;Três motivos que fazem total sentido pra mim.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Primeiro: velocidade.&lt;/strong&gt; As ferramentas da Astral são escritas em Rust. O Ruff
processa um arquivo em menos de 10 milissegundos. O &lt;code&gt;uv&lt;/code&gt; resolve dependências
numa fração de segundo. Quando você tem um agente de IA iterando num problema
centenas de vezes, a diferença entre 10 milissegundos e 2 segundos por
verificação é a diferença entre funcionar e não funcionar. Pra agente autônomo,
Python tooling tradicional é lento demais.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Segundo:&lt;/strong&gt; o ciclo completo. O Codex hoje gera código. Mas a OpenAI quer que
ele faça tudo: montar o ambiente com uv, lintar com Ruff, checar tipos com ty,
rodar testes. Um workflow ponta a ponta, autônomo. As peças encaixam
perfeitamente.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Terceiro:&lt;/strong&gt; distribuição. O &lt;code&gt;uv&lt;/code&gt; já tem centenas de milhões de downloads por
mês. FastAPI, Django, Pandas, Spark, tudo já usa ou tá migrando. A OpenAI compra
acesso direto a milhões de desenvolvedores Python. É um canal de distribuição
absurdo por provavelmente uma fração do preço das outras aquisições deles. Do
ponto de vista de negócio? Brilhante.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Comunidade Dev e Open Source&lt;/h2&gt;
&lt;p&gt;Agora... a comunidade? Não posso falar por todos e também não tenho nada contra
a OpenAI. Mas, de cara, o que me assustou foi que o &lt;code&gt;uv&lt;/code&gt; e o &lt;code&gt;ruff&lt;/code&gt; são
ferramentas que estão no coração de boa parte do que faço com Python
(atualmente, tudo). O problema é que a OpenAI queima dinheiro num ritmo absurdo
e ainda não gera lucro. Então, o meu sentimento é medo. Será que vou ter que
sair mudando base de código antiga? Preciso regravar vídeos? Não sei...&lt;/p&gt;
&lt;p&gt;Busquei pela Internet afora e encontrei algumas coisas. O sentimento não está
muito distante do meu.&lt;/p&gt;
&lt;p&gt;No Hacker News, as threads explodiram. Chutando por baixo, diria que uns 70 a
75% dos comentários são negativos ou céticos.&lt;/p&gt;
&lt;p&gt;A preocupação número um é a mesma que tive: &lt;strong&gt;a saúde financeira da OpenAI&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;E olha, não sou eu falando. A própria
&lt;a href=&quot;https://finance.yahoo.com/news/openais-own-forecast-predicts-14-150445813.html&quot;&gt;OpenAI projeta 14 bilhões de dólares em prejuízo pra 2026&lt;/a&gt;.
Eles gastam dois dólares e cinquenta pra cada dólar que faturam. Não esperam
fluxo de caixa positivo antes de 2029, 2030. Então você pega infraestrutura
Python crítica e coloca debaixo do guarda-chuva de uma empresa que depende de
injeção constante de VC pra sobreviver. Se a bolha de IA esfriar ou se alguém
espirrar na direção errada... JÁ ERA!&lt;/p&gt;
&lt;p&gt;Um cara no &lt;a href=&quot;https://news.ycombinator.com/item?id=47438723&quot;&gt;HN resumiu bem&lt;/a&gt;:
&amp;quot;mais funcionalidades essenciais das quais desenvolvedores dependem passam a
depender de um fluxo contínuo de bilhões em financiamento VC. O que poderia dar
errado?&amp;quot;&lt;/p&gt;
&lt;p&gt;A segunda preocupação é &lt;strong&gt;enshittification&lt;/strong&gt;. Todo mundo já está imaginando:
daqui a pouco você roda &lt;code&gt;uv add&lt;/code&gt; e o negócio quer te oferecer uma sugestão do
Codex.&lt;/p&gt;
&lt;p&gt;Ou o &lt;code&gt;Ruff&lt;/code&gt; começa a ter features premium integradas. Alguém brincou com um
cenário de &lt;code&gt;UV_DISABLE_AGENT=1 UV_DISABLE_AI_HINTS=1 uv add&lt;/code&gt;. Engraçado, mas é o
tipo de humor nervoso, né?&lt;/p&gt;
&lt;p&gt;E faz sentido. Imagina que você é uma empresa e precisa urgentemente de capital!
A ideia é tentar arrancar grana de onde quer que ela venha.&lt;/p&gt;
&lt;p&gt;A terceira é mais filosófica mas importa: &lt;strong&gt;assimetria informacional&lt;/strong&gt;. Se os
autores das ferramentas agora são funcionários da OpenAI, o que impede versões
internas de evoluírem mais rápido que as públicas? O Codex teria uma vantagem
sobre Claude Code e Copilot que não é justa.&lt;/p&gt;
&lt;p&gt;Ah, e teve o
&lt;a href=&quot;https://news.ycombinator.com/item?id=47438723&quot;&gt;comentário que eu achei genial&lt;/a&gt;:
&amp;quot;Empresa que vive dizendo que desenvolvedores de software vão ser substituídos
por IA... compra mais desenvolvedores em vez de usar a própria IA pra fazer
software.&amp;quot; Né?&lt;/p&gt;
&lt;p&gt;Pelo jeito, ainda estamos precisando bastante de Devs.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;E eu? O que penso?&lt;/h2&gt;
&lt;p&gt;Beleza, eu já falei algumas vezes no texto, mas deixo minha opinião sincera como
alguém que ensina Python e usa essas ferramentas todo dia?&lt;/p&gt;
&lt;p&gt;Olha, eu sou bem pragmático. Vou continuar usando &lt;code&gt;uv&lt;/code&gt; e &lt;code&gt;Ruff&lt;/code&gt; amanhã do mesmo
jeito que usei ontem. Nada muda na prática agora. As ferramentas são MIT e
Apache 2.0. Se a OpenAI fizer besteira, dá pra forkar.&lt;/p&gt;
&lt;p&gt;Mas tenho que ser honesto: eu não tô confortável. O motivo é simples: não é
sobre a OpenAI especificamente, é sobre o &lt;strong&gt;padrão&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A Anthropic comprou a Bun em dezembro de 2025. A OpenAI compra a Astral agora. O
Google contratou os fundadores da Windsurf por 2,4 bilhões. A Microsoft já tem o
GitHub e o Copilot. Toda empresa de IA tá engolindo infraestrutura de
desenvolvedor.&lt;/p&gt;
&lt;p&gt;OpenAI captura Python. Anthropic captura JavaScript. E os devs ficam onde nessa
história?&lt;/p&gt;
&lt;p&gt;O risco real não é agora. É daqui a 2, 3 anos. Quando a pressão por monetização
bater, quando o investidor cobrar retorno, é aí que as promessas de &amp;quot;vamos
manter tudo aberto&amp;quot; começam a ficar... flexíveis.&lt;/p&gt;
&lt;p&gt;A história da tecnologia nos ensina isso. Promessa corporativa sobre open source
tem prazo de validade.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;O que fazer?&lt;/h2&gt;
&lt;p&gt;Pra finalizar: o que eu recomendo?&lt;/p&gt;
&lt;p&gt;NADA! &amp;quot;Apenas sorria e acene, rapazes! Sorria e acene!&amp;quot; 🐧 👋&lt;/p&gt;
&lt;p&gt;Continue usando &lt;code&gt;uv&lt;/code&gt; e &lt;code&gt;Ruff&lt;/code&gt;. Não faz sentido trocar agora. São as melhores
ferramentas que o ecossistema Python já teve, e isso não mudou. Pelo menos para
mim não muda nada ainda.&lt;/p&gt;
&lt;p&gt;Mas fica de olho. Presta atenção nos releases. Se começar a aparecer integração
forçada com Codex, funcionalidade que só funciona com conta da OpenAI, qualquer
coisa desse tipo, aí a gente reavalia. Talvez troque!&lt;/p&gt;
&lt;p&gt;E a comunidade Python tem um dever de casa: garantir que a possibilidade de fork
seja real e não só teórica. Porque um dia a gente pode precisar.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Fim&lt;/h2&gt;
&lt;p&gt;É isso. Se você trabalha com Python, essa notícia te afeta diretamente. Cola nos
comentários me dizendo o que você acha. Esse tipo de conteúdo é novo pra mim.&lt;/p&gt;
&lt;p&gt;Valeu. Até o próximo.&lt;/p&gt;
&lt;hr&gt;
</content:encoded></item><item><title>VPN com WireGuard: O Guia Definitivo</title><link>https://otaviomiranda.com.br/2026/vpn-com-wireguard-o-guia-definitivo/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/vpn-com-wireguard-o-guia-definitivo/</guid><description>Veja como montar uma VPN WireGuard de verdade: com topologias, túneis, NAT e tudo que eu descobri configurando 7 nodes espalhados por casa, Hostinger e GCP.</description><pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Veja como montar uma VPN WireGuard de verdade: com topologias, túneis, NAT e
tudo que eu descobri configurando 7 nodes espalhados por casa, Hostinger e GCP.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Em vídeo&lt;/h2&gt;
&lt;p&gt;Se preferir assistir ao invés de ler, também fiz um vídeo:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://youtu.be/yqsLvVz0Y44&quot;&gt;&lt;img src=&quot;./images/wireguard-1.jpg&quot; alt=&quot;VPN com WireGuard: O Guia Definitivo&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Link: &lt;a href=&quot;https://youtu.be/yqsLvVz0Y44&quot;&gt;https://youtu.be/yqsLvVz0Y44&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Por que WireGuard&lt;/h2&gt;
&lt;p&gt;Sempre que você coloca um software novo no projeto, não está levando só uma nova
feature. Leva o pacote completo. Base de código, dependências, bugs, falhas de
segurança... é tipo casamento 😅.&lt;/p&gt;
&lt;p&gt;O WireGuard ajuda bastante nessa parte, porque a proposta dele é super
minimalista. No Linux, sua implementação fica abaixo de 4.000 linhas de código.&lt;/p&gt;
&lt;p&gt;Especialistas em segurança agradecem.&lt;/p&gt;
&lt;p&gt;Em um final de semana uma pessoa consegue auditar todo o código do
&lt;strong&gt;WireGuard&lt;/strong&gt;, tomar umas, bodar e ainda sobra o domingo inteiro pra curar a
ressaca.&lt;/p&gt;
&lt;p&gt;Além disso, manutenção e desempenho também são otimizados. Com menos código,
temos menos para configurar e o servidor tem menos com o que se preocupar.&lt;/p&gt;
&lt;h3&gt;Opinativo&lt;/h3&gt;
&lt;p&gt;O WireGuard é um protocolo opinativo. Ele não senta com você pra perguntar o que
quer usar, quais algoritmos prefere, quais modos deseja negociar. Não. Ele já
chega dizendo: &amp;quot;vai ser assim&amp;quot;. E pronto.&lt;/p&gt;
&lt;p&gt;Isso reduz flexibilidade? Com certeza. Mas os ganhos superam essa redução.&lt;/p&gt;
&lt;p&gt;Enquanto outras ferramentas, como o OpenVPN, nos dão uma liberdade absurda, o
&lt;strong&gt;WireGuard&lt;/strong&gt; pega alguns atalhos, impõe limites e tira opções.&lt;/p&gt;
&lt;p&gt;E, por incrível que pareça, isso é uma das suas maiores qualidades.&lt;/p&gt;
&lt;p&gt;Eu poderia te falar um milhão de coisas que estava lendo no
&lt;a href=&quot;https://www.wireguard.com/papers/wireguard.pdf&quot;&gt;WhitePaper do WireGuard&lt;/a&gt;, mas
aí já seríamos duas pessoas sem entender o que foi explicado.&lt;/p&gt;
&lt;p&gt;Mas, sério agora, ele encapsula pacotes &lt;em&gt;IPv4&lt;/em&gt;/&lt;em&gt;IPv6&lt;/em&gt; sobre o protocolo UDP e
trabalha na camada 3. Só isso!&lt;/p&gt;
&lt;p&gt;Nada de bridge de camada 2. Nada de túnel sobre TCP. Isso já evita
&lt;a href=&quot;https://en.wikipedia.org/wiki/Tunneling_protocol#TCP_meltdown_problem&quot;&gt;TCP-over-TCP meltdown&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Silencioso&lt;/h3&gt;
&lt;p&gt;Outra característica muito boa é o silêncio por padrão.&lt;/p&gt;
&lt;p&gt;Se um pacote não puder ser autenticado, o WireGuard simplesmente o ignora. Não
responde. Não explica. Não negocia.&lt;/p&gt;
&lt;p&gt;Isso pode atrapalhar um pouco na hora de depurar algum problema, claro
(aconteceu comigo, como veremos 😂). Mas, do ponto de vista de segurança, é
bonito de ver. O protocolo fica quieto, como se soubesse que tem alguém tentando
fazer algo inesperado.&lt;/p&gt;
&lt;h3&gt;Cryptokey Routing&lt;/h3&gt;
&lt;p&gt;Nunca ouviu esse termo? Eu também não! É do próprio &lt;strong&gt;WireGuard&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Em vez de separar totalmente autenticação e roteamento, o WireGuard amarra cada
chave pública a uma lista de IPs permitidos dentro do túnel.&lt;/p&gt;
&lt;p&gt;Na saída, isso funciona como tabela de rotas. Na entrada, funciona como uma ACL
(&lt;em&gt;Access Control List&lt;/em&gt;). Pra ficar uma frase mais bonita: identidade
criptográfica e caminho de rede andam juntos.&lt;/p&gt;
&lt;p&gt;Isso chega a mudar até a sua forma de pensar, porque fica mais direto e
previsível.&lt;/p&gt;
&lt;p&gt;E isso vai ficar muito claro quando começarmos a configurar, você vai ver que
mudar um número no &lt;code&gt;AllowedIPs&lt;/code&gt; muda tudo.&lt;/p&gt;
&lt;p&gt;No final, é essa soma de restrições que faz o WireGuard ser tão interessante:
menos negociação, menos ambiguidade, menos superfície para erro. E uma operação
muito mais direta.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Topologias: Hub-and-Spoke e Mesh&lt;/h2&gt;
&lt;p&gt;Eu sei... eu sei... Já estamos chegando lá.&lt;/p&gt;
&lt;p&gt;Mas, antes de fazer qualquer configuração, primeiro você precisa definir qual
topologia de rede vai usar. Então, responder essas perguntas pode ajudar.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Você tem um servidor que controla tudo?&lt;/li&gt;
&lt;li&gt;Vai conectar quantos dispositivos?&lt;/li&gt;
&lt;li&gt;Um dispositivo precisa falar diretamente com o outro?&lt;/li&gt;
&lt;li&gt;Vai compartilhar a Internet pela VPN?&lt;/li&gt;
&lt;li&gt;Tem &lt;a href=&quot;https://en.wikipedia.org/wiki/Network_address_translation&quot;&gt;NAT&lt;/a&gt; (a gente
fura 😂)?&lt;/li&gt;
&lt;li&gt;e outras...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;O fato é que, no mundo de redes, existem várias topologias que você pode usar.&lt;/p&gt;
&lt;p&gt;Nós, no entanto, não estamos em um curso de redes. Além disso, no &lt;strong&gt;WireGuard&lt;/strong&gt;
dá pra fazer muita coisa
(&lt;a href=&quot;https://pt.wikipedia.org/wiki/Efeito_Dunning%E2%80%93Kruger&quot;&gt;se não tudo&lt;/a&gt; o
que você quiser) apenas usando essas duas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Network_topology&quot;&gt;Hub-and-Spoke ou Estrela&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Mesh_networking&quot;&gt;Topologia Mesh&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Caso queira algo mais formal, os links acima vão te ajudar a entender melhor
essas topologias de rede. Mas vou deixar um atalho para os mais apressados.&lt;/p&gt;
&lt;h3&gt;Hub-and-spoke&lt;/h3&gt;
&lt;p&gt;Meus dispositivos se conectam a um servidor central e ele faz o roteamento dos
pacotes para onde for necessário.&lt;/p&gt;
&lt;p&gt;O &lt;strong&gt;Hub&lt;/strong&gt; é onde tudo se conecta, o servidor. Os &lt;strong&gt;Spokes&lt;/strong&gt; são os dispositivos
que falam com o servidor quando precisam alcançar alguma rota.&lt;/p&gt;
&lt;p&gt;Vamos imaginar &lt;strong&gt;três spokes&lt;/strong&gt; e &lt;strong&gt;um hub&lt;/strong&gt;. Um &lt;code&gt;server&lt;/code&gt; (hub) e três
dispositivos (spoke), &lt;code&gt;node_a&lt;/code&gt;, &lt;code&gt;node_b&lt;/code&gt;, &lt;code&gt;node_c&lt;/code&gt;. Assim, você escala este
modelo para quantos nodes precisar.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Node&lt;/strong&gt; (nó) é um aparelho qualquer conectado à rede (Computador, servidor,
smartphone...).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt; Ilustração simplificada de Hub-and-Spoke com WireGuard VPN.

                          ┏ HUB ━━━━━━━━━━━━━┓
          ┏━━━━━━━━━━━━━▶ ┃ server           ┃ ◀━━━━━━━━━━━━━━┓
          ┃               ┃ 10.0.0.2/24      ┃                ┃
          ┃               ┗━━━━━━━━━━━━━━━━━━┛                ┃
          ┃                         ▲                         ┃
          ┃                         ┃                         ┃
          ┃                         ┃                         ┃
  10.0.0.0/24 (Split)        0.0.0.0/0 (Full)       10.0.0.0/24 (Split)
          ┃                         ┃                         ┃
          ┃                         ┃                         ┃
          ▼                         ▼                         ▼
 ┏ SPOKE ━━━━━━━━━━━┓     ┏ SPOKE ━━━━━━━━━━━┓     ┏ SPOKE ━━━━━━━━━━━┓
 ┃ node_a           ┃     ┃ node_b           ┃     ┃ node_c           ┃
 ┃ 10.0.0.3/24      ┃     ┃ 10.0.0.4/24      ┃     ┃ 10.0.0.5/24      ┃
 ┗━━━━━━━━━━━━━━━━━━┛     ┗━━━━━━━━━━━━━━━━━━┛     ┗━━━━━━━━━━━━━━━━━━┛

 Split Túnel: parte das redes do node saem pelo VPN (como em 10.0.0.0/24).
 Full Túnel:  todas as redes do node saem pela conexão VPN (como em 0.0.0.0/0).
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Você pode fazer o roteamento como preferir no &lt;strong&gt;Hub&lt;/strong&gt;. Ou seja, o &lt;code&gt;server&lt;/code&gt; pode
rotear os pacotes para dentro ou para fora da rede. Isso permite uma
configuração extremamente flexível.&lt;/p&gt;
&lt;p&gt;Por exemplo, &lt;code&gt;node_b&lt;/code&gt; poderia falar com &lt;code&gt;node_c&lt;/code&gt;, &lt;code&gt;node_a&lt;/code&gt;, com a Internet e até
com outra rede. Isso tudo é controlado pela diretiva &lt;code&gt;AllowedIPs&lt;/code&gt; do arquivo de
configuração do &lt;code&gt;WireGuard&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;✅ Prós&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Segurança e auditoria centralizadas&lt;/li&gt;
&lt;li&gt;Simplicidade de implementação&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;❌ Contras&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Aumento de Latência (Hairpinning): se não tomar cuidado com a configuração,
seus IPs locais passam a sair para a Internet ao acessar nodes da mesma rede.
Aconteceu na minha rede. Ao pingar o node ao lado (mesma mesa), a rota foi até
São Paulo (no Hub) e voltou (estou em Minas).&lt;/li&gt;
&lt;li&gt;SPOF (Single Point Of Failure): é muito comum que redes menores tenham um
único &lt;strong&gt;Hub&lt;/strong&gt;. Como ele controla tudo, toda sua &lt;strong&gt;VPN&lt;/strong&gt; para se ele cair.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Topologia Mesh&lt;/h3&gt;
&lt;p&gt;Na Topologia Mesh, cada node tem uma conexão direta com os outros nodes. Isso
permite ter uma VPN sem nenhum servidor, apenas um dispositivo que conhece os
outros (e vice-versa).&lt;/p&gt;
&lt;p&gt;E olha que coisa interessante: como todo mundo conhece todo mundo, não há
necessidade de roteamento. O próprio formato da rede em si já é roteado.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; Ilustração simplificada de topologia Mesh com WireGuard VPN.

          10.0.0.4/32 →         ← 10.0.0.3/32
  ┌ PEER ────────────┐ ◀───────▶ ┌ PEER ────────────┐
  │ node_a           │           │ node_b           │
  │ 10.0.0.3/32      │           │ 10.0.0.4/32      │
  └──────────────────┘           └──────────────────┘
    ▲               ▲             ▲               ▲
    │                ╲           ╱                │
    │            10.0.0.6/32 ↓  ╱                 │
 10.0.0.5/32 ↓         ╲       ╱             10.0.0.6/32 ↓
    │                   ╲ 10.0.0.5/32 ↓           │
    │                    ╲   ╱                    │
    │                     ╲ ╱                     │
    │                      ╱                      │
    │                     ╱ ╲                     │
    │                    ╱   ╲                    │
    │           10.0.0.4/32 ↑ ╲                   │
 10.0.0.3/32 ↑         ╱       ╲             10.0.0.4/32 ↑
    │                 ╱    10.0.0.3/32 ↑          │
    │                ╱           ╲                │
    ▼               ▼             ▼               ▼
  ┌ PEER ────────────┐           ┌ PEER ────────────┐
  │ node_c           │           │ node_d           │
  │ 10.0.0.5/32      │           │ 10.0.0.6/32      │
  └──────────────────┘ ◀───────▶ └──────────────────┘
          10.0.0.6/32 →         ← 10.0.0.5/32

 Em Mesh, cada node tem uma conexão direta com os outros nodes.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isso te dá várias vantagens em termos de desempenho, estabilidade e alta
disponibilidade, mas também te dá algumas boas dores de cabeça na hora de
escalar.&lt;/p&gt;
&lt;p&gt;Adicionar novos nodes à rede ou até rotacionar as chaves é um pesadelo. Com 4
nodes, cada um tendo uma conexão direta com os outros, estamos falando em 6
conexões. Agora veja como isso cresce rápido:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4 nodes: 6 conexões&lt;/li&gt;
&lt;li&gt;6 nodes: 15 conexões&lt;/li&gt;
&lt;li&gt;8 nodes: 28 conexões&lt;/li&gt;
&lt;li&gt;10 nodes: 45 conexões&lt;/li&gt;
&lt;li&gt;12 nodes: 66 conexões&lt;/li&gt;
&lt;li&gt;14 nodes: 91 conexões&lt;/li&gt;
&lt;li&gt;16 nodes: 120 conexões&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Entendeu, não é? Cada node novo adicionado na rede fará você voltar a configurar
todos os outros nodes, mais a nova conexão do que estiver sendo adicionado.&lt;/p&gt;
&lt;p&gt;A conta para isso é: &lt;code&gt;N (N-1) / 2&lt;/code&gt;. Exemplo com 20 nodes: &lt;code&gt;20*(20-1)/2 = 190&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;E não pense que 16 nodes é muito. Eu, com minha rede minúscula, já bati 7 nodes
brincando de montar VPNs.&lt;/p&gt;
&lt;p&gt;Se eu adicionasse os dispositivos da minha residência, já subiria fácil para 14
nodes. Você precisa contar tudo o que for fazer parte da rede (ou então voltar
para o NAT). Relógios, TVs, Tablets, Laptops, Smartphones, talvez até geladeiras
(se você gosta dessas coisas 🤔). Tudo isso pode entrar na sua conexão VPN
também.&lt;/p&gt;
&lt;h4&gt;✅ Prós&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Melhor desempenho e latência muito mais baixa&lt;/li&gt;
&lt;li&gt;Resiliência (sem ponto único de falha). Um node cair afeta apenas ele mesmo (e
os nodes que precisarem dele), o restante da rede funciona normalmente&lt;/li&gt;
&lt;li&gt;Existem gerenciadores que ajudam a manter a configuração em dia&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;❌ Contras&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Escalar, a partir de poucos nodes, se torna insustentável. Você vai precisar
de softwares de terceiros para gerenciar todas as conexões (existem vários).&lt;/li&gt;
&lt;li&gt;Quando os dois nodes estão atrás de NAT, não tem como fechar a conexão. Um
deles sempre precisa conseguir receber pelo menos um pacote para fechar a
conexão.&lt;/li&gt;
&lt;li&gt;Fazer auditoria é mais complicado sem um ponto centralizado de conexões.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Qual usar?&lt;/h3&gt;
&lt;p&gt;Você precisa analisar o seu caso. Mas basta olhar o funcionamento do WireGuard
que dá para ter uma noção.&lt;/p&gt;
&lt;p&gt;Para autenticar um node, uma das pontas precisa conseguir enviar um pacote
diretamente para ele. Isso significa que você precisa que um deles tenha IP
válido na Internet.&lt;/p&gt;
&lt;p&gt;Um &lt;code&gt;node_a&lt;/code&gt; que tem IP válido não consegue se conectar com um &lt;code&gt;node_b&lt;/code&gt; que está
atrás de NAT. Mas o contrário é possível. O &lt;code&gt;node_b&lt;/code&gt; consegue chegar diretamente
no &lt;code&gt;node_a&lt;/code&gt;. Isso fecharia a conexão.&lt;/p&gt;
&lt;p&gt;É por este motivo que precisamos de um servidor quando falamos em VPN.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Falando nisso 💜&lt;/p&gt;
&lt;p&gt;Se precisar de servidor VPS, tenho link e cupom que te dá um belo desconto por
até 2 anos se quiser.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://hostinger.com/otaviomiranda&quot;&gt;https://hostinger.com/otaviomiranda&lt;/a&gt;&lt;br&gt;Cupom: OTAVIOMIRANDA&lt;/p&gt;
&lt;p&gt;Obrigado à Hostinger por acreditar no meu conteúdo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Agora, imagine que você está conectando 10 escritórios. Todos eles atrás de NAT.
Você vai precisar de um servidor externo para fechar essas conexões.&lt;/p&gt;
&lt;p&gt;Se você acompanhou o que eu disse, claramente precisamos de uma rede
&lt;strong&gt;Hub-and-spoke&lt;/strong&gt; aqui.&lt;/p&gt;
&lt;p&gt;Por outro lado, se todos os nodes estão de cara para a Internet, dá para usar
&lt;strong&gt;Mesh&lt;/strong&gt; sem problemas.&lt;/p&gt;
&lt;p&gt;E, por fim, se você tem uma rede híbrida, dá para fazer uma configuração híbrida
também. Onde estiver de frente para a Internet, use &lt;strong&gt;Mesh&lt;/strong&gt;, onde não for
possível, &lt;strong&gt;Hub-and-spoke&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Instalando o WireGuard&lt;/h2&gt;
&lt;p&gt;O &lt;strong&gt;WireGuard&lt;/strong&gt; funciona em todos os sistemas mais conhecidos, então é melhor
você seguir o tutorial de instalação do site deles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.wireguard.com/install/&quot;&gt;Instalar WireGuard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sim! Funciona em iOS, Android, Linux, macOS, Windows e vários outros sistemas.&lt;/p&gt;
&lt;p&gt;Para o macOS, estou usando a versão do &lt;code&gt;brew&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# macOS
brew install wireguard-tools
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para os VPSs e VM, todos tem o &lt;strong&gt;Ubuntu Server 24.04 LTS&lt;/strong&gt;, então:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Ubuntu Server 24.04 LTS
sudo apt install wireguard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para iOS (iPhone), estou usando a
&lt;a href=&quot;https://apps.apple.com/us/app/wireguard/id1441195209&quot;&gt;versão oficial&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Também tenho um Fedora Asahi aqui, estou usando a versão do &lt;strong&gt;Fedora&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Fedora
 sudo dnf install wireguard-tools
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Configurando uma rede híbrida no WireGuard&lt;/h2&gt;
&lt;p&gt;Vamos entender o que tenho para colocar em rede e quais são os desafios. Alguns
problemas são propositais, apenas para fins didáticos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rede Local (Minas Gerais)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A rede local recebe um IP do provedor e distribui IPs internos. Entrada e saída
usam NAT, ou seja, todos os dispositivos da rede interna saem com o mesmo IP do
roteador do provedor.&lt;/p&gt;
&lt;p&gt;Também significa que não tem como chegar em um dispositivo específico dentro da
minha rede, já que o NAT vai bloquear (teria que mexer com redirecionamento de
portas, o que não vem ao caso agora).&lt;/p&gt;
&lt;p&gt;Então a limitação é: os nodes da minha rede local precisam iniciar a conexão com
dispositivos externos, só assim consigo autenticar usando o &lt;strong&gt;WireGuard&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Funciona tanto para &lt;strong&gt;Hub-spoke&lt;/strong&gt; quanto para &lt;strong&gt;Mesh&lt;/strong&gt;, mas o dispositivo do
outro lado precisa estar visível na Internet. Eu também deixei uma pegadinha nos
outros nodes que vão forçar o uso de &lt;strong&gt;Hub-and-spoke&lt;/strong&gt;. Já falaremos sobre isso.&lt;/p&gt;
&lt;p&gt;Os dispositivos são (por hostname):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;m132&lt;/code&gt; - Laptop - &lt;code&gt;192.168.0.108/24&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;m4128&lt;/code&gt; - Laptop - &lt;code&gt;192.168.0.109/24&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fedoraair&lt;/code&gt; - Laptop - &lt;code&gt;192.168.0.114/24&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Smartphone&lt;/code&gt; - iOS - &lt;code&gt;Dinâmico&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Hostinger (São Paulo)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Sem segredo aqui. Todos estão nos datacenters da Hostinger, de cara para a
Internet. São 3 VPSs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kvm2&lt;/code&gt; - VPS - &lt;code&gt;76.13.71.178/24&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kvm4&lt;/code&gt; - VPS - &lt;code&gt;191.101.70.130/23&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kvm8&lt;/code&gt; - VPS - &lt;code&gt;89.116.73.152/24&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;VM Google Cloud Platform (Iowa, EUA)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Este é mais um servidor em outra rede completamente diferente das duas
anteriores. Rede da Google, em Council Bluffs, Iowa, EUA (&lt;code&gt;us-central1-b&lt;/code&gt;).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gc_micro&lt;/code&gt; - VM - &lt;code&gt;Dinâmico&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;E essa foi a pegadinha que deixei para apimentar as coisas. Eu poderia fixar o
IP nessa VM, porém vou manter dinâmico.&lt;/p&gt;
&lt;p&gt;Se você entendeu tudo até aqui, já deve ter notado que não tenho como fazer
&lt;strong&gt;Mesh&lt;/strong&gt; da minha &lt;strong&gt;rede local&lt;/strong&gt; (NAT) para a rede da Google. Até dá para fazer,
mas quando o IP da VM mudar, não terei mais conexão. A VM não conseguirá chegar
nas minhas máquinas locais e minhas máquinas locais terão o IP da VM incorreto,
porque mudou.&lt;/p&gt;
&lt;p&gt;A solução mais simples para isso é &lt;strong&gt;Hub-and-spoke&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Gerador de Configuração WireGuard Config Generator&lt;/h2&gt;
&lt;p&gt;De tanto ficar fazendo essas configurações do WireGuard na mão, acabei criando o
&lt;a href=&quot;https://wireguard.otaviomiranda.com.br/&quot;&gt;WireGuard Config Generator&lt;/a&gt;. Isso nem
é público, não tem ads, não salva dados, nem cookies. Tudo roda no seu navegador
usando a Web Crypto API. As chaves são geradas localmente e nunca saem da sua
máquina. Foi simplesmente um aplicativo que criei para solucionar um problema
meu mesmo.&lt;/p&gt;
&lt;p&gt;Ele suporta &lt;strong&gt;Hub-and-spoke&lt;/strong&gt; e &lt;strong&gt;Mesh&lt;/strong&gt; separadamente. Para montar uma rede
híbrida como a minha, gerei primeiro a base Hub-and-spoke (com o &lt;code&gt;kvm2&lt;/code&gt; como
Hub), depois troquei para Mesh e juntei as configs manualmente. Não é o ideal,
mas é muito mais rápido do que fazer tudo do zero.&lt;/p&gt;
&lt;p&gt;🚨 Vou te explicar todas as configurações, mas te aconselho fortemente a usar o
&amp;quot;Generator&amp;quot;. Já perdi incontáveis horas tentando encontrar qual chave ou IP eu
errei (foi por isso que o criei).&lt;/p&gt;
&lt;p&gt;Depois de te passar medo 😂, vamos ver como fazer isso na unha (ainda assim, vou
usar a base do Generator).&lt;/p&gt;
&lt;h3&gt;Como funciona o WireGuard?&lt;/h3&gt;
&lt;p&gt;Isso varia bastante de sistema para sistema. Porém, pelo menos no Linux e macOS,
depois de instalado, você cria um arquivo de configuração com o nome da
Interface em &lt;code&gt;/etc/wireguard/wg0.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;No caso do macOS, essa pasta nem existia, então criei ela também.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Cria a pasta do WireGuard
sudo mkdir /etc/wireguard

# Cria o arquivo de configuração do WireGuard
 sudo touch /etc/wireguard/wg0.conf

# Ajusta as permissões do arquivo de configuração do WireGuard
sudo chmod 600 /etc/wireguard/wg0.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Os comandos acima são para Linux e macOS. No Windows eu não sei dizer como
funciona, mas não deve ser muito diferente disso.&lt;/p&gt;
&lt;p&gt;Com este arquivo pronto, agora só precisamos preencher os dados de &lt;code&gt;Interface&lt;/code&gt;
(nossa interface de rede local) e &lt;code&gt;Peer&lt;/code&gt;. Cada &lt;strong&gt;Peer&lt;/strong&gt; é um dispositivo que
vamos nos conectar.&lt;/p&gt;
&lt;h3&gt;Gerando as chaves&lt;/h3&gt;
&lt;p&gt;Antes de configurar qualquer coisa, cada node precisa de um par de chaves:
&lt;strong&gt;privada&lt;/strong&gt; e &lt;strong&gt;pública&lt;/strong&gt;. Se você já usou SSH com chave, é o mesmo conceito.&lt;/p&gt;
&lt;p&gt;A chave privada fica só no seu node. Nunca sai de lá. A chave pública você
distribui para os peers que precisam se conectar a você.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Gera a chave privada
wg genkey
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isso vai cuspir uma string em Base64. Essa é a sua chave privada. Guarda ela
(nunca, em hipótese alguma, mostre essa chave para ninguém).&lt;/p&gt;
&lt;p&gt;Agora, para gerar a pública a partir da privada:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Gera a chave pública a partir da privada
echo &amp;quot;SUA_CHAVE_PRIVADA&amp;quot; | wg pubkey
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se quiser fazer tudo de uma vez e já salvar nos arquivos:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Gera o par de chaves e salva nos arquivos
wg genkey | sudo tee /etc/wireguard/private.key | wg pubkey | sudo tee /etc/wireguard/public.key &amp;gt; /dev/null ;
# Ajuste as permissões dos arquivos importantes
sudo chmod 600 /etc/wireguard/{wg0.conf,private.key}

# COPIE A CHAVE PÚBLICA E ANOTE A QUAL DISPOSITIVO ELA PERTENCE
sudo cat /etc/wireguard/public.key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Faça isso em &lt;strong&gt;todos&lt;/strong&gt; os nodes. No final, cada node terá sua chave privada e
você terá a chave pública de cada um para distribuir.&lt;/p&gt;
&lt;h3&gt;Anatomia do arquivo de configuração&lt;/h3&gt;
&lt;p&gt;O arquivo &lt;code&gt;wg0.conf&lt;/code&gt; tem duas seções: &lt;code&gt;[Interface]&lt;/code&gt; e &lt;code&gt;[Peer]&lt;/code&gt;. Você terá
&lt;strong&gt;uma&lt;/strong&gt; Interface e &lt;strong&gt;um ou mais&lt;/strong&gt; Peers.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Interface]
PrivateKey = &amp;lt;chave privada deste node&amp;gt;
ListenPort = 51820
Address = &amp;lt;IP do node na VPN&amp;gt;/mascara

[Peer]
PublicKey = &amp;lt;chave pública do peer&amp;gt;
AllowedIPs = &amp;lt;IPs que esse peer pode usar no túnel&amp;gt;
Endpoint = &amp;lt;IP público:porta do peer&amp;gt;
PersistentKeepalive = 25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Calma que vou explicar cada campo.&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;[Interface]&lt;/code&gt;: Quem sou eu?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PrivateKey&lt;/code&gt;&lt;/strong&gt; a chave privada deste node. Aquela que você gerou e não
compartilha com ninguém.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ListenPort&lt;/code&gt;&lt;/strong&gt; a porta UDP que o WireGuard vai escutar. O padrão é &lt;code&gt;51820&lt;/code&gt;.
Pode ser qualquer uma, mas lembre de liberar no firewall. Se quer uma dica,
mantenha o padrão &lt;code&gt;51820&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Address&lt;/code&gt;&lt;/strong&gt; o IP deste node &lt;strong&gt;dentro da VPN&lt;/strong&gt;. É um IP que você inventa.
Nada a ver com o IP da sua rede ou da Internet. É o endereço que identifica
este node dentro do túnel. Eu costumo usar a rede &lt;code&gt;10.100.0.0/24&lt;/code&gt; alterando
apenas o final de 2 para cima (&lt;code&gt;10.100.0.2&lt;/code&gt;, &lt;code&gt;10.100.0.3&lt;/code&gt;, ...
&lt;code&gt;10.100.0.254&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;code&gt;[Peer]&lt;/code&gt;: Quem são os outros?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PublicKey&lt;/code&gt;&lt;/strong&gt; a chave pública do peer. É o que identifica ele.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;AllowedIPs&lt;/code&gt;&lt;/strong&gt; essa é a diretiva mais importante do &lt;strong&gt;WireGuard&lt;/strong&gt;. Ela faz
duas coisas ao mesmo tempo:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Na saída:&lt;/strong&gt; funciona como tabela de rotas. &amp;quot;Para onde enviar pacotes
destinados a esse IP?&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Na entrada:&lt;/strong&gt; funciona como ACL. &amp;quot;Aceito pacotes vindos desse peer apenas
se o IP de origem estiver nessa lista.&amp;quot;&lt;/li&gt;
&lt;li&gt;Se colocar &lt;code&gt;10.100.0.4/32&lt;/code&gt;, só o IP &lt;code&gt;10.100.0.4&lt;/code&gt; passa.&lt;/li&gt;
&lt;li&gt;Se colocar &lt;code&gt;10.100.0.0/24&lt;/code&gt;, toda a sub-rede &lt;code&gt;10.100.0.x&lt;/code&gt; passa.&lt;/li&gt;
&lt;li&gt;Se colocar &lt;code&gt;0.0.0.0/0&lt;/code&gt;, &lt;strong&gt;tudo&lt;/strong&gt; passa. Isso é o Full Túnel.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Endpoint&lt;/code&gt;&lt;/strong&gt; o IP público (ou domínio) e porta do peer. É para onde o
WireGuard manda o primeiro pacote. Esse campo é &lt;strong&gt;opcional&lt;/strong&gt;. Se o peer está
atrás de NAT ou tem IP dinâmico, ele não precisa de Endpoint, porque é ele
quem vai iniciar a conexão.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PersistentKeepalive&lt;/code&gt;&lt;/strong&gt; intervalo em segundos para enviar um pacote vazio ao
peer. Serve para manter a conexão ativa, especialmente quando tem NAT no
caminho. Se o NAT não receber pacotes por um tempo, ele fecha o mapeamento e o
peer fica inalcançável. O valor &lt;code&gt;25&lt;/code&gt; (segundos) é o recomendado.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Configurando o Hub (servidor central)&lt;/h2&gt;
&lt;p&gt;Vamos começar pelo coração da rede: o &lt;code&gt;kvm2&lt;/code&gt;. Ele é o Hub. É o servidor que
todos os outros nodes conhecem e que faz o roteamento quando necessário.&lt;/p&gt;
&lt;p&gt;Lembra do &lt;code&gt;AllowedIPs&lt;/code&gt;? No Hub, cada peer recebe um &lt;code&gt;/32&lt;/code&gt; (um IP específico).&lt;/p&gt;
&lt;p&gt;Isso porque o Hub sabe exatamente quem é quem. Ele não precisa rotear sub-redes
inteiras para um peer, só o IP daquele node.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# /etc/wireguard/wg0.conf — kvm2 (HUB)

[Interface]
PrivateKey = &amp;lt;chave privada do kvm2&amp;gt;
ListenPort = 51820
Address = 10.100.0.2/24, fd10:100::2/64
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Repare no &lt;code&gt;Address&lt;/code&gt;. Estou usando &lt;strong&gt;duas&lt;/strong&gt; redes: uma IPv4 (&lt;code&gt;10.100.0.2/24&lt;/code&gt;) e
uma IPv6 (&lt;code&gt;fd10:100::2/64&lt;/code&gt;). A rede IPv6 usa o prefixo &lt;code&gt;fd&lt;/code&gt;, que é para redes
privadas (equivalente ao &lt;code&gt;10.x.x.x&lt;/code&gt; no IPv4). Você pode usar só IPv4 se
preferir.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Agora os peers. Vou colocar dois para você entender o padrão, depois é só
replicar.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# kvm4 - Hostinger VPS
[Peer]
PublicKey = &amp;lt;chave pública do kvm4&amp;gt;
AllowedIPs = 10.100.0.4/32, fd10:100::4/128
Endpoint = 191.101.70.130:51820
PersistentKeepalive = 25

# m132 - Laptop na rede local
[Peer]
PublicKey = &amp;lt;chave pública do m132&amp;gt;
AllowedIPs = 10.100.0.25/32, fd10:100::25/128
Endpoint = 187.108.118.25:51820
PersistentKeepalive = 25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O &lt;code&gt;kvm4&lt;/code&gt; tem IP fixo, então o Endpoint é direto. O &lt;code&gt;m132&lt;/code&gt; está atrás de NAT, mas
o Hub tem o IP público do roteador como Endpoint. Funciona? Funciona. Mas tem um
detalhe.&lt;/p&gt;
&lt;p&gt;Na minha rede local eu tenho 3 máquinas, todas saindo pelo mesmo IP público.&lt;/p&gt;
&lt;p&gt;Isso significa que o Hub não pode iniciar a conexão para uma máquina específica,
porque o NAT não saberia para qual delas entregar o pacote. Quem precisa iniciar
são as máquinas locais. Quando elas fazem o handshake, o Hub aprende a rota
correta (com a porta NAT de cada uma) e a comunicação flui.&lt;/p&gt;
&lt;p&gt;O &lt;code&gt;PersistentKeepalive = 25&lt;/code&gt; garante que, uma vez conectado, o NAT não feche o
mapeamento por inatividade.&lt;/p&gt;
&lt;h3&gt;Compartilhando a Internet pelo Hub&lt;/h3&gt;
&lt;p&gt;Se você quer que um node navegue na Internet usando o IP do servidor (Full
Túnel), o Hub precisa fazer NAT masquerading. É como dizer: &amp;quot;tudo que chegar
pelo túnel e precisar sair para a Internet, sai com o meu IP&amp;quot;.&lt;/p&gt;
&lt;p&gt;Para isso, adicionamos regras no &lt;code&gt;[Interface]&lt;/code&gt; usando &lt;code&gt;PostUp&lt;/code&gt; e &lt;code&gt;PostDown&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Interface]
PrivateKey = &amp;lt;chave privada do kvm2&amp;gt;
ListenPort = 51820
Address = 10.100.0.2/24, fd10:100::2/64

# Habilita o encaminhamento de pacotes
PostUp = sysctl -w net.ipv4.ip_forward=1
PostUp = sysctl -w net.ipv6.conf.all.forwarding=1

# Regras de firewall para NAT masquerading
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip6tables -A FORWARD -i %i -j ACCEPT
PostUp = ip6tables -A FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostUp = ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

# Limpeza (quando o WireGuard desliga)
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = ip6tables -D FORWARD -i %i -j ACCEPT
PostDown = ip6tables -D FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostDown = ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = sysctl -w net.ipv4.ip_forward=0
PostDown = sysctl -w net.ipv6.conf.all.forwarding=0
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;%i&lt;/code&gt;&lt;/strong&gt; é substituído automaticamente pelo nome da interface (&lt;code&gt;wg0&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;eth0&lt;/code&gt;&lt;/strong&gt; é a interface de rede que tem acesso à Internet no servidor. No seu
caso pode ser &lt;code&gt;ens3&lt;/code&gt;, &lt;code&gt;enp1s0&lt;/code&gt; ou outro nome. Para descobrir, rode &lt;code&gt;ip route&lt;/code&gt;
e veja qual interface aparece na rota &lt;code&gt;default&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;PostUp&lt;/code&gt;&lt;/strong&gt; roda quando o WireGuard sobe. &lt;strong&gt;&lt;code&gt;PostDown&lt;/code&gt;&lt;/strong&gt; roda quando desce.
Assim o servidor fica limpo quando a VPN está desligada.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Isso só precisa existir no Hub. Os spokes não precisam dessas regras (a não ser
que também compartilhem Internet para outra rede).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Configurando os Spokes&lt;/h2&gt;
&lt;p&gt;Agora vamos para o lado dos spokes. A ideia é a mesma em todos: dizer quem eu
sou (&lt;code&gt;Interface&lt;/code&gt;) e quem eu conheço (&lt;code&gt;Peer&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;Spoke com IP fixo (VPS)&lt;/h3&gt;
&lt;p&gt;O &lt;code&gt;kvm4&lt;/code&gt; é um VPS da Hostinger com IP fixo. Ele tem conexão direta com o Hub e
também faz mesh com os outros VPSs e com os nodes locais.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# /etc/wireguard/wg0.conf — kvm4 (Spoke + Mesh)

[Interface]
PrivateKey = &amp;lt;chave privada do kvm4&amp;gt;
ListenPort = 51820
Address = 10.100.0.4/24, fd10:100::4/64

# kvm2 (HUB) - Rota para toda a sub-rede passa por aqui
[Peer]
PublicKey = &amp;lt;chave pública do kvm2&amp;gt;
AllowedIPs = 10.100.0.0/24, fd10:100::0/64
Endpoint = 76.13.71.178:51820
PersistentKeepalive = 25

# kvm8 - Mesh direto (VPS com IP fixo)
[Peer]
PublicKey = &amp;lt;chave pública do kvm8&amp;gt;
AllowedIPs = 10.100.0.8/32, fd10:100::8/128
Endpoint = 89.116.73.152:51820
PersistentKeepalive = 25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Olha o que acontece com o &lt;code&gt;AllowedIPs&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Para o &lt;strong&gt;kvm2&lt;/strong&gt; (Hub): &lt;code&gt;10.100.0.0/24&lt;/code&gt;. Toda a sub-rede. Isso significa que
qualquer IP da VPN que o &lt;code&gt;kvm4&lt;/code&gt; não conheça diretamente vai ser encaminhado
para o Hub. O Hub é o &amp;quot;caminho padrão&amp;quot;.&lt;/li&gt;
&lt;li&gt;Para o &lt;strong&gt;kvm8&lt;/strong&gt; (Mesh): &lt;code&gt;10.100.0.8/32&lt;/code&gt;. Só o IP específico. Quando o &lt;code&gt;kvm4&lt;/code&gt;
quiser falar com &lt;code&gt;10.100.0.8&lt;/code&gt;, vai direto, sem passar pelo Hub.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;É aqui que mora a mágica da rede híbrida. O WireGuard escolhe a rota mais
específica. &lt;code&gt;/32&lt;/code&gt; ganha de &lt;code&gt;/24&lt;/code&gt;. Então o mesh é usado quando possível, e o Hub
serve de fallback para o resto.&lt;/p&gt;
&lt;h3&gt;Spoke atrás de NAT (rede local)&lt;/h3&gt;
&lt;p&gt;Os nodes de casa (&lt;code&gt;m132&lt;/code&gt;, &lt;code&gt;m4128&lt;/code&gt;, &lt;code&gt;fedoraair&lt;/code&gt;) seguem o mesmo padrão. A única
diferença é que eles têm peers extras para os vizinhos de rede local, usando IPs
internos.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# /etc/wireguard/wg0.conf — m132 (Spoke local)

[Interface]
PrivateKey = &amp;lt;chave privada do m132&amp;gt;
ListenPort = 51820
Address = 10.100.0.25/24, fd10:100::25/64

# kvm2 (HUB)
[Peer]
PublicKey = &amp;lt;chave pública do kvm2&amp;gt;
AllowedIPs = 10.100.0.0/24, fd10:100::0/64
Endpoint = 76.13.71.178:51820
PersistentKeepalive = 25

# kvm4 - Mesh direto
[Peer]
PublicKey = &amp;lt;chave pública do kvm4&amp;gt;
AllowedIPs = 10.100.0.4/32, fd10:100::4/128
Endpoint = 191.101.70.130:51820
PersistentKeepalive = 25

# kvm8 - Mesh direto
[Peer]
PublicKey = &amp;lt;chave pública do kvm8&amp;gt;
AllowedIPs = 10.100.0.8/32, fd10:100::8/128
Endpoint = 89.116.73.152:51820
PersistentKeepalive = 25

# m4128 - Vizinho de LAN (Endpoint local!)
[Peer]
PublicKey = &amp;lt;chave pública do m4128&amp;gt;
AllowedIPs = 10.100.0.26/32, fd10:100::26/128
Endpoint = 192.168.0.109:51820
PersistentKeepalive = 25

# fedoraair - Vizinho de LAN (Endpoint local!)
[Peer]
PublicKey = &amp;lt;chave pública do fedoraair&amp;gt;
AllowedIPs = 10.100.0.27/32, fd10:100::27/128
Endpoint = 192.168.0.114:51820
PersistentKeepalive = 25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Repare nos Endpoints dos vizinhos de LAN: &lt;code&gt;192.168.0.x&lt;/code&gt;. São IPs internos da
rede local. Quando o &lt;code&gt;m132&lt;/code&gt; quer falar com o &lt;code&gt;m4128&lt;/code&gt;, ele vai direto pela rede
local, sem sair para a Internet e sem passar pelo Hub.&lt;/p&gt;
&lt;p&gt;Isso é exatamente o problema de &lt;strong&gt;hairpinning&lt;/strong&gt; que comentei lá em cima. Se eu
não colocasse esses Endpoints locais, o pacote iria até o Hub em São Paulo e
voltaria. Da minha mesa em Minas para São Paulo e de volta. Para falar com o
laptop ao lado.&lt;/p&gt;
&lt;h3&gt;Spoke com IP dinâmico (gc_micro)&lt;/h3&gt;
&lt;p&gt;O &lt;code&gt;gc_micro&lt;/code&gt; é uma VM no Google Cloud com IP dinâmico. Ele não tem Endpoint
fixo, então nenhum peer coloca o IP dele na configuração.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# /etc/wireguard/wg0.conf — gc_micro (Spoke dinâmico)

[Interface]
PrivateKey = &amp;lt;chave privada do gc_micro&amp;gt;
ListenPort = 51820
Address = 10.100.0.28/24, fd10:100::28/64

# kvm2 (HUB) - Rota para toda a sub-rede
[Peer]
PublicKey = &amp;lt;chave pública do kvm2&amp;gt;
AllowedIPs = 10.100.0.0/24, fd10:100::0/64
Endpoint = 76.13.71.178:51820
PersistentKeepalive = 25

# kvm4 - Mesh direto
[Peer]
PublicKey = &amp;lt;chave pública do kvm4&amp;gt;
AllowedIPs = 10.100.0.4/32, fd10:100::4/128
Endpoint = 191.101.70.130:51820
PersistentKeepalive = 25

# kvm8 - Mesh direto
[Peer]
PublicKey = &amp;lt;chave pública do kvm8&amp;gt;
AllowedIPs = 10.100.0.8/32, fd10:100::8/128
Endpoint = 89.116.73.152:51820
PersistentKeepalive = 25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O &lt;code&gt;gc_micro&lt;/code&gt; conhece o Hub e os VPSs (que têm IP fixo). Para todo o resto,
depende do Hub. E o mais importante: &lt;strong&gt;ninguém aponta Endpoint para ele&lt;/strong&gt;. Ele
inicia a conexão, o &lt;code&gt;PersistentKeepalive&lt;/code&gt; mantém o NAT aberto e o Hub aprende o
caminho.&lt;/p&gt;
&lt;p&gt;Se a VM reiniciar e ganhar um IP novo? Sem problemas. Ela conecta de novo no
Hub, o Hub atualiza o Endpoint automaticamente e a vida segue.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Subindo o túnel&lt;/h2&gt;
&lt;p&gt;Configurou tudo? Então vamos ligar.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Sobe a interface wg0
sudo wg-quick up wg0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para derrubar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Desce a interface wg0
sudo wg-quick down wg0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se quiser que o WireGuard suba automaticamente junto com o sistema (Linux com
systemd):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Habilita o WireGuard no boot
sudo systemctl enable wg-quick@wg0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para verificar o status da conexão:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Mostra o status de todos os peers
sudo wg show
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O comando &lt;code&gt;wg show&lt;/code&gt; é o seu melhor amigo. Ele mostra quais peers estão
conectados, quando foi o último handshake e quantos dados passaram.&lt;/p&gt;
&lt;h3&gt;Testando a conexão&lt;/h3&gt;
&lt;p&gt;O bom e velho &lt;code&gt;ping&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Pinga o Hub pelo IP da VPN
ping 10.100.0.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se chegar, o túnel está funcionando. Se não chegar, respira fundo e confere:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;O WireGuard está rodando nos dois lados? (&lt;code&gt;sudo wg show&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;As chaves públicas estão corretas? (chave pública do peer A no config do B e
vice-versa)&lt;/li&gt;
&lt;li&gt;A porta &lt;code&gt;51820/UDP&lt;/code&gt; está liberada no firewall?&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;AllowedIPs&lt;/code&gt; inclui o IP que você está tentando alcançar?&lt;/li&gt;
&lt;li&gt;Se tem NAT, o node atrás do NAT iniciou a conexão primeiro?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Lembra que eu disse que o WireGuard é silencioso? Se algo estiver errado, ele
simplesmente não responde. Não tem mensagem de erro bonitinha. Então a depuração
é por eliminação mesmo 😂.&lt;/p&gt;
</content:encoded></item><item><title>WireGuard Config Generator no seu navegador</title><link>https://otaviomiranda.com.br/2026/wireguard-config-generator-no-seu-navegador/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/wireguard-config-generator-no-seu-navegador/</guid><description>WireGuard Config Generator é um gerador de configurações do WireGuard seguro, que não usa nada externo. Sem cookies, sem localStorage e sem salvar nada internamente. Só você e o navagador.</description><pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;O &lt;a href=&quot;https://wireguard.otaviomiranda.com.br/&quot;&gt;WireGuard Config Generator&lt;/a&gt; não é
nada complexo ou feito com o intuito de gerenciar sua conexões de VPN do
WireGuard. A única coisa que ele faz é gerar suas configurações para você.&lt;/p&gt;
&lt;p&gt;Fiz isso apenas para rotacionar as poucas conexões que tenho sem ter que
instalar nada externo. Só preciso adicionar nomes e IPs para o gerador de
configurações do WireGuard fazer todos os arquivos de configuração para cada uma
das minhas máquinas automaticamente.&lt;/p&gt;
&lt;p&gt;Para não usar nada externo, usei a
&lt;a href=&quot;https://developer.mozilla.org/pt-BR/docs/Web/API/Web_Crypto_API&quot;&gt;Web Crypto API&lt;/a&gt;
do próprio navegador.&lt;/p&gt;
&lt;p&gt;Para garantir a segurança e confiabilidade, só uso front-end (HTML, CSS e JS)
sem salvar nada em lugar nenhum:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sem &lt;code&gt;Analytics&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Sem &lt;code&gt;Cookies&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Sem &lt;code&gt;Base de dados&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Sem &lt;code&gt;Conexão externa&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Além disso, o código do
&lt;a href=&quot;https://github.com/luizomf/wgfront&quot;&gt;WireGuard Config Generator é aberto&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Se você busca por algo que gerencie toda a conexão para você, talvez seja melhor
usar o &lt;a href=&quot;https://github.com/juanfont/headscale&quot;&gt;Headscale&lt;/a&gt; ou Tailscale.&lt;/p&gt;
&lt;p&gt;Agora, se quer apenas gerar ou rotacionar suas chaves de forma segura acesse:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wireguard.otaviomiranda.com.br/&quot;&gt;WireGuard Config Generator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Faltam algumas coisas ainda. Por exemplo, gostaria de colocar a opção de
compartilhamento de Internet, roteamento, full túnel e várias outras coisas.
Mas, enquanto não monto isso, use o seguinte na sua configuração do WireGuard
quando precisar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Para Compartilhamento de Internet adicione isso no servidor
# NAT masquerading to share internet
PostUp = sysctl -w net.ipv4.ip_forward=1
PostUp = sysctl -w net.ipv6.conf.all.forwarding=1

PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip6tables -A FORWARD -i %i -j ACCEPT
PostUp = ip6tables -A FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostUp = ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = ip6tables -D FORWARD -i %i -j ACCEPT
PostDown = ip6tables -D FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostDown = ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = sysctl -w net.ipv4.ip_forward=0
PostDown = sysctl -w net.ipv6.conf.all.forwarding=0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Exemplo de configuração completo&lt;/h2&gt;
&lt;p&gt;Servidor (Hub) — &lt;code&gt;/etc/wireguard/wg0.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Interface]
PrivateKey = &amp;lt;SERVER_PRIVATE_KEY&amp;gt;
Address = 10.0.0.1/24
ListenPort = 51820

# Para Compartilhamento de Internet adicione isso no servidor
# NAT masquerading to share internet
PostUp = sysctl -w net.ipv4.ip_forward=1
PostUp = sysctl -w net.ipv6.conf.all.forwarding=1

PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip6tables -A FORWARD -i %i -j ACCEPT
PostUp = ip6tables -A FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostUp = ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = ip6tables -D FORWARD -i %i -j ACCEPT
PostDown = ip6tables -D FORWARD -o %i -m state --state RELATED,ESTABLISHED -j ACCEPT
PostDown = ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = sysctl -w net.ipv4.ip_forward=0
PostDown = sysctl -w net.ipv6.conf.all.forwarding=0

# Cliente 1
[Peer]
PublicKey = &amp;lt;NOTEBOOK_PUBLIC_KEY&amp;gt;
PresharedKey = &amp;lt;PSK_VPS_NOTEBOOK&amp;gt;
AllowedIPs = 10.0.0.2/32

# Cliente 2
[Peer]
PublicKey = &amp;lt;CELULAR_PUBLIC_KEY&amp;gt;
PresharedKey = &amp;lt;PSK_VPS_CELULAR&amp;gt;
AllowedIPs = 10.0.0.3/32

# Cliente 3
[Peer]
PublicKey = &amp;lt;HOME_SERVER_PUBLIC_KEY&amp;gt;
PresharedKey = &amp;lt;PSK_VPS_SERVIDOR&amp;gt;
AllowedIPs = 10.0.0.4/32, 192.168.1.0/24 # túnel + LAN da casa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cliente (Exemplo) — &lt;code&gt;/etc/wireguard/wg0.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Interface]
PrivateKey = &amp;lt;NOTEBOOK_PRIVATE_KEY&amp;gt;
Address = 10.0.0.2/32
DNS = 1.1.1.1, 8.8.8.8

[Peer]
PublicKey = &amp;lt;SERVER_PUBLIC_KEY&amp;gt;
PresharedKey = &amp;lt;PSK_VPS_NOTEBOOK&amp;gt;
Endpoint = &amp;lt;IP_PUBLICO_VPS&amp;gt;:51820
AllowedIPs = 0.0.0.0/0, ::/0           # full tunnel (todo tráfego pela VPN)
# AllowedIPs = 10.0.0.0/24             # split tunnel (só rede interna)
PersistentKeepalive = 25
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota sobre &lt;code&gt;eth0&lt;/code&gt;:&lt;/strong&gt; este nome de interface pública pode ser &lt;code&gt;ens3&lt;/code&gt;,
&lt;code&gt;enp1s0&lt;/code&gt;, etc. Descubra com: &lt;code&gt;ip route show default | awk &amp;#39;{print $5}&amp;#39;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>A Base Para Observabilidade: Stack LGTM (Loki, Grafana, Tempo, Mimir)</title><link>https://otaviomiranda.com.br/2026/a-base-para-observabilidade-stack-lgtm-loki-grafana-tempo-mimir/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/a-base-para-observabilidade-stack-lgtm-loki-grafana-tempo-mimir/</guid><description>Como saí do achismo com logs e montei uma stack LGTM real, localmente e numa VPS, para entender métricas, traces e alertas.</description><pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Até poucos dias atrás, eu não sabia quase nada sobre essa sopa de letrinhas:
Loki, Grafana, Tempo, Mimir, Alloy, OpenTelemetry e outras que vão aparecendo
nos meus estudos.&lt;/p&gt;
&lt;p&gt;Na minha cabeça de &amp;quot;dev raiz&amp;quot;, observabilidade era algo como:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;Joga uns logs aí, abre o terminal, procura erro e vai.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Funciona? Às vezes. Sempre damos um jeito de resolver. O problema é sempre está
correndo atrás do rabo. Sempre &amp;quot;reagindo ao erro&amp;quot; e não &amp;quot;prevenindo o erro&amp;quot;.&lt;/p&gt;
&lt;p&gt;E tem mais, chega uma hora em que o log sozinho não responde o que você quer
saber. Ele só te mostra que algo aconteceu. Mas não responde algumas perguntas
que tendem a aparecer. Alguns exemplos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;quanto aquilo piorou ao longo do tempo?&lt;/li&gt;
&lt;li&gt;onde a requisição ficou lenta?&lt;/li&gt;
&lt;li&gt;foi no meu código ou na infra?&lt;/li&gt;
&lt;li&gt;qual a quantidade de usuários afetados?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depois de algumas tentativas falhas de estudar observabilidade no passado, dessa
vez resolvi pegar firme. Óbvio que não sou nenhum especialista, longe disso.
Mas, pelo menos já consigo dar meus pitacos.&lt;/p&gt;
&lt;p&gt;Um dos primeiros desafios que encontrei, foi essa sopa de letrinhas. Eu ainda
estava só nos logs, quando apareceram: Métricas, Traces e Profiles.&lt;/p&gt;
&lt;p&gt;Foi exatamente por isso que resolvi montar uma stack LGTM completa e
disponibilizar isso, primeiro para quem me acompanha. (O próprio
&lt;a href=&quot;https://github.com/grafana/docker-otel-lgtm&quot;&gt;Grafana&lt;/a&gt; tem essa stack pronta,
mas eu precisava montar a minha).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Stack LGTM em vídeo&lt;/h2&gt;
&lt;p&gt;Gravei um vídeo falando mais detalhadamente sobre isso, caso queira assistir,
segue abaixo:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://youtu.be/8VlmEK5ler0?si=ESYvJFkl6doVynaz&quot;&gt;&lt;img src=&quot;./images/lgtm.jpg&quot; alt=&quot;Observabilidade: Loki, Grafana, Tempo e Mimir (LGTM Stack)&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://youtu.be/8VlmEK5ler0&quot;&gt;Observabilidade: Loki, Grafana, Tempo e Mimir (LGTM Stack)&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;O log é bom, mas...&lt;/h2&gt;
&lt;p&gt;Todo desenvolvedor faz isso: acontece alguma coisa estranha na aplicação e a
reação imediata é sair espalhando &lt;code&gt;print&lt;/code&gt;, &lt;code&gt;console.log&lt;/code&gt;, &lt;code&gt;logger.info&lt;/code&gt; ou
qualquer variação dessa ideia. Quando é fora da aplicação, &lt;code&gt;tail&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;,
&lt;code&gt;rg&lt;/code&gt;...&lt;/p&gt;
&lt;p&gt;Eu faço isso muito mais do que gostaria. Você, provavelmente também faz isso.
Acho que todo mundo faz um pouco disso.&lt;/p&gt;
&lt;p&gt;Ao meu ver, tá tudo certo. O log é bom, mas... log é um evento textual congelado
no tempo. Ele vai te contar algo que aconteceu em um ponto muito específico no
tempo.&lt;/p&gt;
&lt;p&gt;Só que observabilidade de verdade começa quando você quer responder perguntas um
pouco menos específicas. Por exemplo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;isso aconteceu uma vez ou está acontecendo várias vezes sem eu perceber?&lt;/li&gt;
&lt;li&gt;isso começou agora ou já vem piorando faz algum tempo?&lt;/li&gt;
&lt;li&gt;essa requisição passou por onde?&lt;/li&gt;
&lt;li&gt;foi CPU?&lt;/li&gt;
&lt;li&gt;foi rede?&lt;/li&gt;
&lt;li&gt;foi banco?&lt;/li&gt;
&lt;li&gt;foi meu último deploy?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Aí entram os três pilares (tem mais, mas eu ainda estou nesses).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Logs, métricas e traces&lt;/h2&gt;
&lt;h3&gt;Logs&lt;/h3&gt;
&lt;p&gt;Logs são registros de eventos capturados em momentos específicos no tempo. É o
relato bruto. O &amp;quot;aconteceu isso&amp;quot;.&lt;/p&gt;
&lt;p&gt;Neste projetinho que vou disponibilizar mais adiante no texto, os logs saem da
aplicação via &lt;code&gt;stdout&lt;/code&gt;, o Docker coleta, o &lt;code&gt;Grafana Alloy&lt;/code&gt; lê isso e encaminha
para o &lt;code&gt;Loki&lt;/code&gt;. Depois eu exploro tudo na interface do Grafana usando &lt;code&gt;LogQL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;É o básico que todo mundo entende primeiro. E com razão. Log é o tipo de coisa
que você bate o olho e já reconhece.&lt;/p&gt;
&lt;h3&gt;Métricas&lt;/h3&gt;
&lt;p&gt;Métricas são medidas numéricas acompanhadas ao longo do tempo. Aqui a conversa
muda.&lt;/p&gt;
&lt;p&gt;Você não quer mais saber só se &amp;quot;deu erro&amp;quot; em algum ponto. Você quer saber:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;qual foi a taxa de erro?&lt;/li&gt;
&lt;li&gt;quantas requisições por segundo estão chegando?&lt;/li&gt;
&lt;li&gt;quanto de CPU a VPS está usando?&lt;/li&gt;
&lt;li&gt;como a latência se comportou nos últimos minutos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Métrica é tendência, comportamento, padrão. Geralmente, preciso de algum volume
de dados e tempo para análise. Um exemplo de métrica poderia ser o tempo médio
de resposta da API para os usuários em determinado período.&lt;/p&gt;
&lt;p&gt;Sozinhos, Logs não conseguiriam responder isso.&lt;/p&gt;
&lt;p&gt;No projeto, essas métricas vão para o &lt;code&gt;Mimir&lt;/code&gt; e eu consulto tudo com &lt;code&gt;PromQL&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Traces&lt;/h3&gt;
&lt;p&gt;Isso aqui é um pouco mais complexo até conseguir configurar tudo de uma forma
que os dados façam sentido, mas é uma das partes mais interessantes para mim.&lt;/p&gt;
&lt;p&gt;Trace é o caminho completo de uma requisição. E por requisição, não estou
falando apenas de requisições HTTP. Qualquer caminho que os dados façam em
qualquer aplicação.&lt;/p&gt;
&lt;p&gt;Ele mostra por onde a chamada passou e onde o tempo foi gasto em cada um dos
pontos. Não interessa se foi uma função do seu código ou alguma parte da infra.
Se configurado de maneira correta, você conseguiria ver precisamente onde uma
requisição pode ter gerado um erro ou ficado mais lenta.&lt;/p&gt;
&lt;p&gt;Então, em vez de olhar só para o resultado final, você passa a olhar para a
jornada inteira:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;entrou aqui&lt;/li&gt;
&lt;li&gt;passou por esse middleware&lt;/li&gt;
&lt;li&gt;ficou esse tempo nessa etapa&lt;/li&gt;
&lt;li&gt;terminou desse jeito&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No meu caso, estou usando &lt;code&gt;Tempo&lt;/code&gt; para armazenar traces e &lt;code&gt;OpenTelemetry&lt;/code&gt; para
instrumentar a API.&lt;/p&gt;
&lt;p&gt;É a peça que mais ajuda a sair do &amp;quot;acho que o gargalo está aqui&amp;quot; para &amp;quot;não, está
aqui mesmo&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Obs.:&lt;/strong&gt; já podemos tirar os &lt;code&gt;prints&lt;/code&gt; e &lt;code&gt;console.logs&lt;/code&gt; de &amp;quot;Chegou Aqui&amp;quot; do
código.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Grafana Alloy - Ele é quem distruibui os dados&lt;/h2&gt;
&lt;p&gt;Uma das coisas que eu não entendia no começo era:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;Beleza, eu tenho logs, métricas e traces. Mas quem pega isso tudo e leva para
os lugares certos?&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No meu projeto, esse papel ficou com o &lt;code&gt;Grafana Alloy&lt;/code&gt;. Ele funciona como um
coletor e roteador.&lt;/p&gt;
&lt;p&gt;Pega logs do Docker, recebe métricas e traces da aplicação, coleta sinais do
host e dos containers e distribui cada coisa para o backend certo&lt;/p&gt;
&lt;p&gt;O fluxo fica mais ou menos assim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API Logs -&amp;gt; Docker -&amp;gt; Alloy -&amp;gt; &lt;code&gt;Loki&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;API Métricas -&amp;gt; OTel -&amp;gt; Alloy -&amp;gt; &lt;code&gt;Mimir&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;API traces -&amp;gt; OTel -&amp;gt; Alloy -&amp;gt; &lt;code&gt;Tempo&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Otel é OpenTelemetry.&lt;/p&gt;
&lt;p&gt;O &lt;code&gt;Grafana&lt;/code&gt; entra depois como a interface onde eu exploro tudo.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Falando em OpenTelemetry (OTel)&lt;/h2&gt;
&lt;p&gt;O &lt;code&gt;OpenTelemetry&lt;/code&gt; entra dentro da aplicação. Tipo o que você faz com seu Logger,
mas para métricas e traces. O &lt;strong&gt;OTel&lt;/strong&gt; também faz &lt;strong&gt;log&lt;/strong&gt; e &lt;strong&gt;profiling&lt;/strong&gt;, mas
ainda não cheguei nessa página do livro 😅.&lt;/p&gt;
&lt;p&gt;Mas, ele foi o padrão que usei para instrumentar a API e emitir telemetria sem
ter que sair inventando formato próprio para tudo. Na verdade, hoje em dia ele
já é o padrão da indústria (todo mundo já usa).&lt;/p&gt;
&lt;p&gt;Foi ele que me permitiu gerar métricas e traces da aplicação de um jeito mais
organizado e menos artesanal.&lt;/p&gt;
&lt;p&gt;Ou seja:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;o &lt;code&gt;Alloy&lt;/code&gt; coleta e encaminha&lt;/li&gt;
&lt;li&gt;o &lt;code&gt;OpenTelemetry&lt;/code&gt; ajuda a aplicação a emitir os sinais&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;A parte divertida: subir isso em um VPS real&lt;/h2&gt;
&lt;p&gt;Montar tudo localmente é ótimo para aprender. Mas localmente tudo sempre parece
mais bonito do que realmente é.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Se precisar de VPS&lt;/p&gt;
&lt;p&gt;Tenho link e cupom de 10% de desconto adicional na Hostinger para planos de 12
e 24 meses.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://hostinger.com/otaviomiranda&quot;&gt;hostinger.com/otaviomiranda&lt;/a&gt;&lt;br&gt;Cupom: OTAVIOMIRANDA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Você está na sua máquina. Sem latência de internet. Sem borda pública. Sem
firewall de verdade. Sem aquele sentimento de &amp;quot;agora isso aqui mora em outro
lugar e pode dar ruim&amp;quot;. E, o mais importante, sem bots de Internet tentando
fazer brute force no seu SSH ou qualquer formulário disponível no seu front-end.&lt;/p&gt;
&lt;p&gt;Então resolvi subir a stack numa VPS real.&lt;/p&gt;
&lt;p&gt;E aqui entra uma coisa importante: eu não queria fazer um tutorial gigante de
&amp;quot;digite 300 comandos comigo&amp;quot;. A verdade é que 2025 já foi muito cruel com quem
dedicou tempo para criar tutoriais. Então fiz diferente.&lt;/p&gt;
&lt;p&gt;Documentei o passo a passo em dois guias:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;um
&lt;a href=&quot;https://github.com/luizomf/lgtm1/blob/main/docs/DEV_GUIDE.md&quot;&gt;guia completo&lt;/a&gt;,
explicando o que cada comando faz&lt;/li&gt;
&lt;li&gt;e um
&lt;a href=&quot;https://github.com/luizomf/lgtm1/blob/main/docs/DEV_GUIDE_SENIOR.md&quot;&gt;guia mais direto&lt;/a&gt;,
só com os comandos para subir o servidor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Assim, este texto ou meu vídeo não se tornam um walkthrough cansativo. Quem
quiser replicar tem tudo mastigado.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;O deploy ficou simples de propósito&lt;/h2&gt;
&lt;p&gt;Tentei abstrair o máximo da parte chata para deixar a stack mais fácil de
estudar.&lt;/p&gt;
&lt;p&gt;No repositório, a base do deploy gira em torno do &lt;code&gt;Justfile&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Então, em vez de sair lembrando &lt;code&gt;docker compose&lt;/code&gt; gigantesco, arquivo de
ambiente, receita de tráfego e outras miudezas, eu deixei comandos curtos para
as tarefas principais.&lt;/p&gt;
&lt;p&gt;Por exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cp .env.example .env
# Configure o seu .env
just deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depois disso, consigo gerar tráfego, disparar alertas, resetar ambiente,
explorar dashboards e repetir cenários sem precisar decorar um carnaval de
comandos.&lt;/p&gt;
&lt;p&gt;Esse tipo de coisa parece detalhe, mas faz muita diferença quando você quer
estudar sem transformar tudo em atrito.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Segurança: o Grafana não precisa ficar pelado na internet&lt;/h2&gt;
&lt;p&gt;Outra decisão importante foi não expor tudo publicamente. Mas, quero mudar isso
já como um próximo passo.&lt;/p&gt;
&lt;p&gt;Eu tinha algumas opções na mesa no momento da configuração:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Traefik com HTTPS para API e Grafana&lt;/li&gt;
&lt;li&gt;Traefik com HTTPS para API e Grafana liberando apenas meu IP&lt;/li&gt;
&lt;li&gt;VPN WireGuard e Grafana apenas no VPN&lt;/li&gt;
&lt;li&gt;E outras...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No meu setup, fiz o seguinte: a API fica atrás de &lt;code&gt;Traefik&lt;/code&gt; em &lt;code&gt;80/443&lt;/code&gt;, mas o
Grafana fica privado, acessível pela rede do &lt;code&gt;WireGuard&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Isso me dá uma divisão que queria, mas não expõe nada do Grafana para a
Internet.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a aplicação pública fica pública&lt;/li&gt;
&lt;li&gt;a interface administrativa fica privada&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Como laboratório, ainda vou abrir o Grafana para a Internet para &amp;quot;OBSERVAR 😂&amp;quot; o
que acontece.&lt;/p&gt;
&lt;h2&gt;O mais legal de tudo&lt;/h2&gt;
&lt;p&gt;O mais legal desse processo foi perceber como o entendimento vai mudando muito
rápido quando você começa a ver as peças funcionando juntas.&lt;/p&gt;
&lt;p&gt;Dois dias antes de montar isso, eu mal sabia o que era LGTM. Pouco depois, eu já
estava discutindo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;single binary no Loki&lt;/li&gt;
&lt;li&gt;traces no Tempo&lt;/li&gt;
&lt;li&gt;métricas do host&lt;/li&gt;
&lt;li&gt;alertas no Mimir&lt;/li&gt;
&lt;li&gt;Grafana privado via WireGuard&lt;/li&gt;
&lt;li&gt;latência real da API vs delay injetado&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;O cérebro humano é uma máquina engraçada. Isso acontece com você também?&lt;/p&gt;
&lt;p&gt;Primeiro você se sente um idiota. Depois começa a reconhecer os nomes. Depois
entende o fluxo. Depois começa a ter opinião. Depois está aqui escrevendo sobre
isso.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Se você quiser estudar isso&lt;/h2&gt;
&lt;p&gt;Te convido a usar o meu projeto. Ele está público, gratuito e foi pensado para
iniciantes que querem aprender observabilidade vendo algo real funcionar.&lt;/p&gt;
&lt;p&gt;Foi pensado assim porque eu também me considero inciante nisso.&lt;/p&gt;
&lt;p&gt;O projeto, até agora, tem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;aplicação de demo&lt;/li&gt;
&lt;li&gt;stack LGTM completa&lt;/li&gt;
&lt;li&gt;dashboards provisionados&lt;/li&gt;
&lt;li&gt;tráfego simulando cenários previsíveis&lt;/li&gt;
&lt;li&gt;guias para subir localmente e numa VPS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Devo continuar mexendo, então não leve isso ao pé da letra. Mas, se você quiser
dar uma olhada, o repositório está aqui:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/luizomf/lgtm1&quot;&gt;github.com/luizomf/lgtm1&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Se for usar VPS para brincar com isso em ambiente real, eu usei uma da
&lt;a href=&quot;https://hostinger.com/otaviominranda&quot;&gt;Hostinger&lt;/a&gt; nesse laboratório e ela deu
conta do recado. Mas o mais importante aqui não é o provedor. É você finalmente
parar de depender de achismo para entender o que sua aplicação está fazendo.&lt;/p&gt;
&lt;p&gt;No fim, era isso que eu queria.&lt;/p&gt;
&lt;p&gt;Menos &amp;quot;hmm, estranho&amp;quot;. Mais &amp;quot;agora eu sei onde olhar&amp;quot;.&lt;/p&gt;
</content:encoded></item><item><title>De HTML, CSS e JS para Astro: finalmente migrei</title><link>https://otaviomiranda.com.br/2026/de-html-css-e-js-para-astro-finalmente-migrei/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/de-html-css-e-js-para-astro-finalmente-migrei/</guid><description>Migrei meu site de HTML, CSS e JavaScript puro para Astro SSG e explico as decisões técnicas, os desafios e como a IA (Codex, Claude e Gemini) acelerou a migração.</description><pubDate>Fri, 27 Feb 2026 15:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;./images/astro-cover.webp&quot; alt=&quot;Logo do Astro&quot;&gt;&lt;/p&gt;
&lt;p&gt;Já tem alguns dias que estou falando sobre esta migração na
&lt;a href=&quot;https://www.youtube.com/@otaviomiranda/posts&quot;&gt;comunidade do meu canal&lt;/a&gt;. Hoje,
finalmente terminei!&lt;/p&gt;
&lt;p&gt;Meu site foi de &lt;strong&gt;HTML, CSS e JavaScript PUROS&lt;/strong&gt; para o
&lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; (&lt;em&gt;usando SSG - Static Site Generation&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Além disso, este foi um dos primeiros projetos em que mais administrei do que
digitei código. Diria que &lt;strong&gt;95%&lt;/strong&gt; do código atual foi escrito por 3 LLMs
diferentes: &lt;strong&gt;Claude Code&lt;/strong&gt; &lt;em&gt;(Opus 4.6)&lt;/em&gt;, &lt;strong&gt;Codex App&lt;/strong&gt; &lt;em&gt;(GPT 5.3 Codex High)&lt;/em&gt; e
&lt;strong&gt;Antigravity&lt;/strong&gt; &lt;em&gt;(Gemini 3.1 Pro High)&lt;/em&gt;. Também usei variações desses modelos
para tarefas simples ou mais complexas.&lt;/p&gt;
&lt;p&gt;Usando um arquivo de regras simples, &lt;code&gt;git&lt;/code&gt; e GitHub, consegui manter o contexto
do que estava em andamento até a conclusão do projeto. Isso me permitiu até
mesmo trocar de modelo ao longo da migração sem muitos problemas.&lt;/p&gt;
&lt;p&gt;Vamos entender mais detalhes sobre isso adiante.&lt;/p&gt;
&lt;p&gt;Mas, primeiro vamos garantir que você não vai cometer os mesmos erros que eu.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Meu erro ao usar HTML, CSS e JS puros&lt;/h2&gt;
&lt;p&gt;Ao criar um website com HTML, CSS e JS puros, é muito provável que você termine
com uma estrutura assim (ou variações disso):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.
├── css
│   └── styles.css
├── images
│   └── ...
├── js
│   └── scripts.js
├── 2025
│   ├── meu-post-1
│   │   └── index.html
│   ├── meu-post-2
│   │   └── index.html
├── 2026
│   ├── meu-post-3
│   │   └── index.html
│   ├── meu-post-4
│   │   └── index.html

... vários anos e posts ...

│ 
└── index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No começo, isso parece uma boa ideia.&lt;/p&gt;
&lt;p&gt;Só que, sem um padrão ou framework, você vai precisar copiar e colar o diretório
de uma página para criar outra (ou inventar uma outra maneira qualquer).&lt;/p&gt;
&lt;p&gt;Com o passar do tempo, isso vai fazer você terminar com centenas de páginas com
variações levemente diferentes do &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Todas com repetições do mesmo cabeçalho, rodapé, menu e qualquer outra coisa que
estiver no seu layout.&lt;/p&gt;
&lt;p&gt;Como todo bom &lt;strong&gt;dev&lt;/strong&gt;, cheio de projetos para entregar, você vai pensar:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;Se funciona, vou manter como está! Se precisar, depois melhoro a estrutura.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Tudo vai ficar bem até que...&lt;/h3&gt;
&lt;p&gt;Você precisa alterar algo.&lt;/p&gt;
&lt;p&gt;Pense que você criou um site de notícias que fala de assuntos variados. Como são
muitos assuntos, você pode publicar mais de uma vez por dia. Então, rapidamente
você tem 999 notícias.&lt;/p&gt;
&lt;p&gt;Só que algo passou despercebido na vigésima quinta notícia que você publicou. Um
erro de digitação no rodapé do seu &lt;code&gt;index.html&lt;/code&gt;. O seu hábito de copiar e colar
clonou este erro para mais 974 páginas.&lt;/p&gt;
&lt;p&gt;Ao publicar sua próxima notícia, você percebe que é um grande momento, mas
aquele erro está pegando mal. Então você pensa:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;Um script Python resolve!&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Talvez! Mas, se isso ainda não tinha passado pela sua cabeça, de agora em diante
você lembra disso o tempo todo.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;E se aparecer outro erro?&amp;quot;&lt;br&gt;&amp;quot;E se eu tiver que alterar o layout e o CSS?&amp;quot;&lt;br&gt;&amp;quot;E se eu quiser adicionar ou remover um link de menu?&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Refatorar todo o site nessa altura do campeonato é complicado. Você está com
vários outros projetos em andamento. Deadlines batendo na porta.&lt;/p&gt;
&lt;p&gt;Seu script não vai capturar todas as nuances dos posts, porque todo ser humano
tem bursts de dopamina que geram micro alterações de código ao longo do tempo.&lt;/p&gt;
&lt;p&gt;E nós sabemos que você nunca voltou para alterar todos aqueles quase 1000 posts.&lt;/p&gt;
&lt;h3&gt;As janelas estão quebradas...&lt;/h3&gt;
&lt;p&gt;A partir daqui, começa a acontecer algo muito parecido com a
&lt;a href=&quot;https://pt.wikipedia.org/wiki/Teoria_das_janelas_quebradas&quot;&gt;teoria das janelas quebradas&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Você passa a &amp;quot;vandalizar&amp;quot; seu próprio site. Adiciona &amp;quot;só mais um script&amp;quot; para
alguma coisa específica, &amp;quot;um ajuste de margem aqui&amp;quot;, &amp;quot;uma div ali&amp;quot;... Este site
nunca vai estar perfeito.&lt;/p&gt;
&lt;p&gt;Isso vai rapidamente da empolgação de algo novo para o &lt;em&gt;&amp;quot;medo de quebrar outro
trecho do site&amp;quot;&lt;/em&gt;, para o &lt;em&gt;&amp;quot;Eu não ligo mais&amp;quot;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Um belo dia, você simplesmente para de publicar completamente. Só manter o que
já tem, já será sua vitória.&lt;/p&gt;
&lt;p&gt;Exatamente o que aconteceu comigo.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;A refatoração mal sucedida&lt;/h2&gt;
&lt;p&gt;Com o advento dos agentes para código com LLMs cada vez mais inteligentes, criei
coragem para fazer essa refatoração. Mas, antes de colocar qualquer IA no
projeto tive essa ideia brilhante:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;Vou refatorar isso aqui na mão mesmo mantendo HTML, CSS e JS&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Claro! Vamos cometer o mesmo erro duas vezes seguidas. Já que vamos errar,
erramos em tudo o que for possível para não restar dúvidas sobre o erro 😅.&lt;/p&gt;
&lt;p&gt;Olhei algumas tendências no CodePen e Dribbble. Não sou bom com design, por
isso, tudo que adiciono nos meus layouts vem de coisas que vejo na Internet e
gosto.&lt;/p&gt;
&lt;h3&gt;Meu único código do projeto&lt;/h3&gt;
&lt;p&gt;Decidi que queria uma section &lt;code&gt;Hero&lt;/code&gt; no topo do site com um texto bem grande e
centralizado.&lt;/p&gt;
&lt;p&gt;Me inspirei no design do &lt;a href=&quot;https://antigravity.google/&quot;&gt;Antigravity&lt;/a&gt;, com as
partículas interativas que ficam se mexendo suavemente.&lt;/p&gt;
&lt;p&gt;Cheguei a fazer 3 efeitos de background para decidir qual usar. Estão todos
abaixo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://codepen.io/luizomf/full/ZYOdpdx&quot;&gt;Primeiro canvas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codepen.io/luizomf/full/yyJdoWP&quot;&gt;Segundo canvas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://otaviomiranda.com.br/&quot;&gt;Final (Home do site)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Perdi uns 2 ou 3 dias com isso, mas consegui um resultado que me agradou.&lt;/p&gt;
&lt;p&gt;E essa foi minha única participação em digitação de código neste projeto. O
canvas e o JavaScript que o acompanha.&lt;/p&gt;
&lt;h3&gt;Desistência&lt;/h3&gt;
&lt;p&gt;Cheguei a colocar o canvas na página inicial e fazer alguns ajustes de fonte.&lt;/p&gt;
&lt;p&gt;Queria (e consegui) criar um site onde o conteúdo vem primeiro. Principalmente
na &lt;a href=&quot;/blog/1/&quot;&gt;parte dos posts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Usei uma fonte grande, muito bem espaçada e não tenho anúncios, pop-ups,
cookies...&lt;/p&gt;
&lt;p&gt;Nada além do conteúdo.&lt;/p&gt;
&lt;h3&gt;Código antigo embaixo da cama&lt;/h3&gt;
&lt;p&gt;BoOoO 👻!&lt;/p&gt;
&lt;p&gt;Mesmo tentando remover o máximo de coisas do código antigo sem quebrar nada,
mexer em uma parte do CSS ou JS antigo estragava outras partes do site.&lt;/p&gt;
&lt;p&gt;Trocar o tamanho de algo, significava ter que sair conferindo todas as outras
páginas. Confesso que nem forcei, com uma ou duas tentativas, já desisti disso e
fui atrás de solução.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Eu: Me indique um bom framework para SSG em 2026.&lt;br&gt;IA: Astro!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Como eu ainda não havia usado o &lt;strong&gt;Astro&lt;/strong&gt;, vamos checar do que se trata.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Astro is a JavaScript web framework 🤮🫣☺️💜&lt;/h2&gt;
&lt;p&gt;Ao entrar no &lt;a href=&quot;https://astro.build/&quot;&gt;astro.build&lt;/a&gt;, adivinha a primeira coisa que
vejo?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;Astro is a JavaScript web framework&amp;quot;&lt;/em&gt; (sentiu um calafrio aí?).&lt;/p&gt;
&lt;p&gt;Toda vez que vejo as palavras &lt;strong&gt;JavaScript&lt;/strong&gt; e &lt;strong&gt;Framework&lt;/strong&gt; juntas, a vontade é
tapar os ouvidos e ficar gritando: &lt;em&gt;&amp;quot;lá lá lá lá lá, não quero saber...&amp;quot;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Se você já usou a quantidade de frameworks e libs de JS que eu, deve ter a mesma
sensação. No final você só quer não usar nada.&lt;/p&gt;
&lt;p&gt;Mas, o Astro foi diferente.&lt;/p&gt;
&lt;h3&gt;Conceitos do Astro&lt;/h3&gt;
&lt;p&gt;Olha só que coincidência, alguns dos conceitos do &lt;strong&gt;Astro&lt;/strong&gt; falaram diretamente
comigo, como se eu estivesse em uma consultoria com o framework:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Servidor primeiro: &lt;em&gt;&amp;quot;O Astro melhora o desempenho do seu website
&lt;strong&gt;renderizando componentes no servidor&lt;/strong&gt;, enviando HTML leve para o browser,
com &lt;strong&gt;zero overhead de JavaScript desnecessário&lt;/strong&gt;.&amp;quot;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Voltado para conteúdo: &lt;em&gt;&amp;quot;O Astro foi criado para trabalhar com o seu conteúdo,
não importa onde ele estiver. &lt;strong&gt;Carregue dados do seu sistema de arquivos&lt;/strong&gt;,
APIs externas ou seu CMS favorito.&amp;quot;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Personalizável: &lt;em&gt;&amp;quot;Estenda o Astro com suas ferramentas favoritas. Traga sua
própria UI de componentes, bibliotecas JS, temas, integrações e mais.&amp;quot;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Interessante! Tudo isso realmente está lá no site deles. Agora, quer mais?&lt;/p&gt;
&lt;h3&gt;Astro Islands&lt;/h3&gt;
&lt;p&gt;Se você já usou qualquer framework ou lib JavaScript, deve ter notado que
queremos encapsular o máximo de coisas de um componente.&lt;/p&gt;
&lt;p&gt;Isso evita o problema que eu tive na minha refatoração falha. Editar algo e
quebrar outro componente. Mas, até o momento, eu fazia isso com um único
framework.&lt;/p&gt;
&lt;p&gt;O &lt;strong&gt;Astro&lt;/strong&gt; permite criar ilhas (islands) dentro da página. Dessa forma, um
componente pode usar &lt;em&gt;React&lt;/em&gt;, outro &lt;em&gt;Vue&lt;/em&gt;, outro pode ter &lt;strong&gt;somente HTML puro&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Então não vamos procurar mais.&lt;/p&gt;
&lt;h3&gt;Fechado com Astro 💜&lt;/h3&gt;
&lt;p&gt;A partir daqui o negócio até que fluiu bem.&lt;/p&gt;
&lt;p&gt;Só tem aquele fato que mencionei antes &lt;em&gt;&amp;quot;ainda não havia usado o &lt;strong&gt;Astro&lt;/strong&gt;&amp;quot;&lt;/em&gt;.
Então deixa eu chamar os LLMs e começar os trabalhos.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;LLMs: problemas e soluções&lt;/h2&gt;
&lt;p&gt;O meu intuito com a IA neste projeto não é fazer &amp;quot;Vibe Coding&amp;quot;. É o oposto.
Quero a IA trabalhando como um colega qualquer (que digita 1000x mais rápido do
que eu).&lt;/p&gt;
&lt;p&gt;Mas, temos um grande problema atualmente: &lt;strong&gt;NÃO EXISTE UM PADRÃO&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Como tudo é muito novo, o que posso te passar são apenas experiências que tive
&lt;strong&gt;TESTANDO&lt;/strong&gt; algumas coisas.&lt;/p&gt;
&lt;h3&gt;O problema do contexto&lt;/h3&gt;
&lt;p&gt;Sempre que você inicia um novo agente no projeto, ele vem &lt;strong&gt;em branco&lt;/strong&gt;. É como
um desenvolvedor entrando em uma base de código &lt;strong&gt;pela primeira vez&lt;/strong&gt;. Precisa
ler a documentação ou todo o projeto para entender o que será feito.&lt;/p&gt;
&lt;p&gt;O problema é que agentes têm limites de tamanho na janela de contexto e, mesmo
que não tivessem, também existe o problema do
&lt;a href=&quot;https://medium.com/@pfarzana1313/context-rot-why-bigger-isnt-always-better-for-llms-091f1bdcfb83&quot;&gt;Context Rot&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Se você quer fazer algo grande sem dores de cabeça, a melhor opção é seguir a
técnica de &lt;em&gt;dividir para conquistar&lt;/em&gt;. Divida o problema em partes pequenas o
suficiente para serem gerenciadas e resolva as pequenas partes uma por vez.&lt;/p&gt;
&lt;p&gt;Foi exatamente o que fiz, com alguns percalços até encontrar algo que funcionou.&lt;/p&gt;
&lt;h3&gt;Primeira tentativa: AGENTS.md&lt;/h3&gt;
&lt;p&gt;No meu arquivo &lt;code&gt;AGENTS.md&lt;/code&gt;, só apontei o modelo para outros arquivos separados:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MEMORY.md&lt;/code&gt; - estado da sessão, o que foi feito e o que falta fazer (tipo um
&lt;code&gt;CHANGELOG&lt;/code&gt; mas para o LLM).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SOUL.md&lt;/code&gt; - personalidade e tom do agente (seja conciso, fale em inglês, etc).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;USER.md&lt;/code&gt; - Meu nome, softwares disponíveis e coisas relevantes sobre o
usuário (eu).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AGENTS.md&lt;/code&gt; - Aponta o agente para os arquivos anteriores.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A ideia era simples: antes de fazer qualquer coisa, a IA lê esses arquivos e
segue as regras.&lt;/p&gt;
&lt;p&gt;Terminou o código, atualiza o &lt;code&gt;MEMORY.md&lt;/code&gt; com o que aconteceu e faz um commit do
que mudou. Assim eu só reviso o commit no final.&lt;/p&gt;
&lt;p&gt;Se eu precisar trocar de modelo ou simplesmente limpar o contexto, o &lt;code&gt;MEMORY.md&lt;/code&gt;
lembra tudo para a IA.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quando funciona (caminho feliz):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;De início, achei que esse era o modo perfeito. Trabalhei tranquilamente uma
manhã inteira dessa forma. Gemini, Codex e Claude respeitaram as regras e eu
estava revisando o código.&lt;/p&gt;
&lt;p&gt;Chamei isso de &amp;quot;loop&amp;quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Prompt para a tarefa:
  LLM:
    -&amp;gt; AGENTS.md
      -&amp;gt; SOUL.md
      -&amp;gt; USER.md
   -&amp;gt; Faz o código
      -&amp;gt; MEMORY.md (Contexto)
      -&amp;gt; commit (Contexto)
  EU:
    -&amp;gt; reviso
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Problema:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Quando a task era mais complexa, todos os modelos falhavam. E o pior, não dava
pra saber onde seria a falha.&lt;/p&gt;
&lt;p&gt;Corrigi isso adicionando as regras direto no arquivo global de cada LLM
(&lt;code&gt;GEMINI.md&lt;/code&gt;, &lt;code&gt;CODEX.md&lt;/code&gt; e &lt;code&gt;CLAUDE.md&lt;/code&gt;). Esses arquivos sempre são lidos, então
elas não esquecem. Mesmo assim, ainda passava alguma coisa sem atualização.&lt;/p&gt;
&lt;p&gt;Como percebi que eles estavam sempre fazendo o &lt;code&gt;commit&lt;/code&gt;, adicionei um hook
validando minhas regras. Se o modelo tentasse fazer &lt;code&gt;commit&lt;/code&gt; sem atualizar
&lt;code&gt;MEMORY.md&lt;/code&gt;, eu gerava um erro explicando as regras de novo.&lt;/p&gt;
&lt;p&gt;Isso funcionou? Sim! Mas com vários problemas.&lt;/p&gt;
&lt;p&gt;Primeiro, o hook atrapalha você também. Quando você for fazer algum &lt;code&gt;commit&lt;/code&gt;,
seu hook vai fazer você ter que atualizar o &lt;code&gt;MEMORY.md&lt;/code&gt; também 😂.&lt;/p&gt;
&lt;p&gt;Segundo, a fricção do modelo com este sistema. Se você ficar olhando ele
trabalhar, vai ver isso:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Modelo faz o código&lt;/li&gt;
&lt;li&gt;Tenta fazer &lt;code&gt;commit&lt;/code&gt; sem atualizar a memória&lt;/li&gt;
&lt;li&gt;Erro no &lt;code&gt;commit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Modelo volta e lê o &lt;code&gt;AGENTS.md&lt;/code&gt; novamente&lt;/li&gt;
&lt;li&gt;Atualiza o &lt;code&gt;MEMORY.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Faz o &lt;code&gt;commit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Te avisa que esqueceu do &lt;code&gt;MEMORY.md&lt;/code&gt;, mas corrigiu&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Trabalhei alguns dias assim. Mas, essa fricção estressa você e atrapalha o
modelo. Então tentei outra coisa.&lt;/p&gt;
&lt;h3&gt;Issues, Branch, PR e Merge&lt;/h3&gt;
&lt;p&gt;Regras em um único arquivo global do modelo. Para cada modelo, usei seu próprio
arquivo (&lt;code&gt;GEMINI.md&lt;/code&gt;, &lt;code&gt;CODEX.md&lt;/code&gt; e &lt;code&gt;CLAUDE.md&lt;/code&gt;). Sem espalhar dados em outros
arquivos.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-md&quot;&gt;# NOME_MODELO.md

- Quem sou eu e como me trate
- O que é o projeto
- Decisões de arquitetura
- Regras do workflow (já explico)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nessas regras do workflow, fiz o modelo trabalhar como qualquer outro
desenvolvedor. Vai fazer algo novo no projeto? Ok, siga esses passos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Abra uma &lt;em&gt;Issue&lt;/em&gt; no repositório&lt;/li&gt;
&lt;li&gt;Crie um novo &lt;em&gt;Branch&lt;/em&gt; para o que for fazer e faça&lt;/li&gt;
&lt;li&gt;Terminou? Crie uma &lt;em&gt;Pull Request&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Eu reviso a PR e faço o merge&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Por incrível que pareça, nenhuma IA erra nunca neste processo. Creio que elas
foram bem treinadas em código open source 😅.&lt;/p&gt;
&lt;p&gt;Mas e o contexto? Elas sabem usar o git muito bem. Só avisar o processo para o
modelo que ele vai ler &lt;em&gt;Issues&lt;/em&gt;, histórico de &lt;code&gt;commits&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;Por falar nisso, você também nem precisa conversar com o modelo usando
mensagens. Abra uma &lt;em&gt;Issue&lt;/em&gt; no repositório e avise ao modelo:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;Trabalhe na issue #N&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Simples assim!&lt;/p&gt;
&lt;h3&gt;Os três modelos&lt;/h3&gt;
&lt;p&gt;Os modelos que usei foram:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Claude Code&lt;/strong&gt; &lt;em&gt;(Opus 4.6)&lt;/em&gt; via Claude CLI: Muito bom, mas é o mais caro deles.
Além disso, ele é o que tem menos limite nos meus planos. Meu plano atual é o
Max 5x (e ele bateu limite várias vezes). Ainda tem um plano acima, o 20x (mais
caro ainda). Depois tem a API. Aí o preço não faz nem sentido para mim.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Codex&lt;/strong&gt; &lt;em&gt;(GPT 5.3 Codex High)&lt;/em&gt;: não usei o CLI porque o &lt;strong&gt;Codex App&lt;/strong&gt; está com
uma promoção que te dá o dobro de tokens até abril. Então, vamos economizar, não
é? Eu não chequei essa informação, mas mesmo usando o &lt;strong&gt;Codex App&lt;/strong&gt; um dia
inteiro, não vi onde é o limite (e meu plano é o plus, o mais barato).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Antigravity&lt;/strong&gt; &lt;em&gt;(Gemini 3.1 Pro High)&lt;/em&gt;: eu não sei o motivo, mas eu considero
que &amp;quot;ganhei&amp;quot; o acesso aos produtos do Gemini. Eu já assinava o &lt;strong&gt;Google One&lt;/strong&gt;
para backup no &lt;strong&gt;Google Drive&lt;/strong&gt;. Um belo dia incluíram o &lt;strong&gt;Gemini&lt;/strong&gt; no plano.
Com ele eu também não vi o limite. Mas, pode ser que tenha acontecido. Em alguns
momentos, o modelo começava a dar erro e parava de funcionar. Não sei se foi por
causa do volume de uso ou limite mesmo. Acontece que o &lt;strong&gt;Gemini 3.1 Pro&lt;/strong&gt; acabou
de ser lançado. Nesses momentos, todo mundo quer testar o novo modelo, então
erros são mais comuns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;O ponto principal:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Neste projeto, como era algo mais simples, não vi diferença entre os modelos.
Foi muita &lt;strong&gt;cópia&lt;/strong&gt; e &lt;strong&gt;cola&lt;/strong&gt; do velho para o novo, e todos os modelos se
saíram muito bem.&lt;/p&gt;
&lt;p&gt;Tentei usar um por dia. Quando começava os trabalhos pela manhã, já iniciava com
um deles e ia até o final do dia.&lt;/p&gt;
&lt;p&gt;Em alguns dias eu fui forçado a trocar por erro (Gemini) ou por limite (Claude).&lt;/p&gt;
&lt;h3&gt;O que eles construíram?&lt;/h3&gt;
&lt;p&gt;Se você está lendo isso no meu site, tem a mão deles aí. Foram aproximadamente 2
semanas, comigo basicamente dirigindo e revisando.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sistema de layouts com componentes reutilizáveis&lt;/li&gt;
&lt;li&gt;Content Layer API com schemas Zod para validação de frontmatter&lt;/li&gt;
&lt;li&gt;Syntax highlight nativo via Shiki (zero JS client-side para blocos de código)&lt;/li&gt;
&lt;li&gt;Pipeline de deploy automático via GitHub Actions&lt;/li&gt;
&lt;li&gt;Sitemap automatizado&lt;/li&gt;
&lt;li&gt;Editor Markdown no browser com Monaco e Vim mode&lt;/li&gt;
&lt;li&gt;CLI para criação de posts (&lt;code&gt;npm run post &amp;quot;Título&amp;quot;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Remoção de ~4.000 linhas de código legado&lt;/li&gt;
&lt;li&gt;Dark mode (somente nos posts)&lt;/li&gt;
&lt;li&gt;E mais coisas que eu talvez tenha esquecido.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A única parte que eu fiz foi o canvas de partículas na home.&lt;/p&gt;
&lt;h3&gt;A lição&lt;/h3&gt;
&lt;p&gt;É simples: só tratei modelos de IA como qualquer outro desenvolvedor:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Issue -&amp;gt; branch -&amp;gt; commit -&amp;gt; revisão (VOCÊ) -&amp;gt; merge (VOCÊ)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pelo menos até o momento em que escrevi isso, este foi o modelo que melhor
funcionou.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Este post começou com um site quebrado e o medo de tocar em qualquer coisa.&lt;/p&gt;
&lt;p&gt;Termina com o Astro e &lt;code&gt;builds&lt;/code&gt; automáticos no &lt;strong&gt;GitHub Pages&lt;/strong&gt;. Além disso,
também aderi ao modelo de &lt;em&gt;Issues&lt;/em&gt;. Antes eu fazia &lt;code&gt;push&lt;/code&gt; direto no &lt;code&gt;main&lt;/code&gt;. Isso
me permite até ter rascunhos de posts 😂.&lt;/p&gt;
&lt;p&gt;A lição não é sobre o Astro, sobre IA ou sobre nenhuma ferramenta específica.&lt;/p&gt;
&lt;p&gt;Quando algo está quebrado e doloroso, a solução quase nunca é &lt;em&gt;&amp;quot;só mais um
script&amp;quot;&lt;/em&gt;. É começar de novo com fundações melhores, mesmo que isso signifique
engolir o orgulho e usar outro &lt;code&gt;FrameworkJavaScript&lt;/code&gt; (as duas palavras coladas
uma na outra pra você 🤬).&lt;/p&gt;
</content:encoded></item><item><title>Docker Context com SSH</title><link>https://otaviomiranda.com.br/2026/docker-context-com-ssh/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/docker-context-com-ssh/</guid><description>Aprenda a rodar containers em servidores remotos como se estivesse local com Docker Context e SSH.</description><pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;./images/docker-context.webp&quot; alt=&quot;Foto de Otávio Miranda com a logo do Docker e os textos Docker + SSH&quot;&gt;&lt;/p&gt;
&lt;p&gt;Se você entende &lt;strong&gt;como o Docker funciona&lt;/strong&gt; e o utiliza no seu dia a dia, sabe
que ele é uma mão na roda para muitas coisas além do desenvolvimento. Você pode
até fazer deploy com ele, como
&lt;a href=&quot;https://youtu.be/yxxEk68EDgo?si=t1GbSOZ62aShmcZD&quot;&gt;já mostrei&lt;/a&gt; algumas vezes no
passado.&lt;/p&gt;
&lt;p&gt;Só que existem algumas configurações que uso bem menos do que deveria. Como é o
caso do contexto &lt;em&gt;(Docker Context)&lt;/em&gt;. Uma parte extremamente simples do Docker,
mas que pode salvar o dia e evitar que você fique fazendo conexões remotas sem
necessidade.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Em vídeo:&lt;/strong&gt; se preferir, tenho este mesmo
&lt;a href=&quot;https://youtu.be/tXM3Ifd6_T8&quot;&gt;conteúdo em vídeo&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Mais de um computador? Sem problemas!&lt;/h2&gt;
&lt;p&gt;Vamos montar um cenário fictício e depois te falo como isso se encaixa com o meu
momento atual.&lt;/p&gt;
&lt;p&gt;Suponha que você tem um computador que fica sempre parado e um laptop que usa
para trabalhar com mais conforto pela casa. O computador tem um hardware
excelente e te ajuda nas tarefas mais pesadas. Mas, seu laptop só consegue te
ajudar em cenários de baixo “&lt;em&gt;load&lt;/em&gt;”.&lt;/p&gt;
&lt;p&gt;Usar Docker no laptop vai &lt;strong&gt;engolir&lt;/strong&gt; todo o pouco recurso que ele tem.&lt;/p&gt;
&lt;p&gt;Este cenário é muito similar ao que tenho atualmente. Só que o meu problema é um
pouco diferente.&lt;/p&gt;
&lt;p&gt;Por incrível que pareça, &lt;strong&gt;utilizo três computadores ao mesmo tempo.&lt;/strong&gt; Quando
falo isso, pessoas costumam duvidar. Mas, não é exagero.&lt;/p&gt;
&lt;p&gt;Tenho um &lt;strong&gt;MacBook M1 com 32GB de RAM&lt;/strong&gt; que carrego pra todo lado (vamos
chamá-lo de &lt;strong&gt;m132&lt;/strong&gt;). Nada a reclamar deste laptop. No entanto, ele não roda um
“&lt;em&gt;gpt-oss:120b&lt;/em&gt;“ ou outros modelos open-source mais parrudos.&lt;/p&gt;
&lt;p&gt;Para esses casos mais extremos, tenho um &lt;strong&gt;MacBook M4 com 128GB de RAM&lt;/strong&gt; (que
vamos chamar de &lt;strong&gt;m4128&lt;/strong&gt;). Só que este é o meu computador que “&lt;em&gt;fica parado&lt;/em&gt;”.
Como gravo e edito bastante conteúdo, este computador fica com câmeras,
microfones e várias outras coisas conectadas o tempo todo. Eu sei que poderia
desconectar tudo e usá-lo como um laptop, mas manter tudo conectado evita atrito
na hora de gravar algo novo. Só preciso pressionar um simples botão e já
“&lt;strong&gt;estou no-ar&lt;/strong&gt;”.&lt;/p&gt;
&lt;p&gt;Para complicar um pouco mais a história, o laptop da minha esposa estragou a
tela e ficou encostado por meses. Quando consultamos o preço para troca da tela,
valia mais comprar outro do que mantê-lo.&lt;/p&gt;
&lt;p&gt;Em um belo dia, decidi instalar o &lt;a href=&quot;https://asahilinux.org/&quot;&gt;Asahi Linux&lt;/a&gt; neste
último laptop (uma versão do &lt;strong&gt;Fedora&lt;/strong&gt;). Não estava com muitas esperanças que
tudo funcionasse. Este é um &lt;strong&gt;MacBook Air M1 com 8GB de RAM&lt;/strong&gt; (vamos chamá-lo
carinhosamente de &lt;strong&gt;fedoraair&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;Acontece, que o Fedora rodou tão liso neste MacBook via SSH, que eu só queria
usar o bendito Linux.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Três computadores ao mesmo tempo&lt;/h2&gt;
&lt;p&gt;Agora que você entendeu o cenário, &lt;strong&gt;como isso funciona&lt;/strong&gt;? E, o mais importante:
o que isso tem a ver com &lt;strong&gt;Docker Context&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;Como o &lt;strong&gt;fedoraair&lt;/strong&gt; está com a tela danificada, ele também fica parado. Por
isso, continuo usando o &lt;strong&gt;m132&lt;/strong&gt; o dia todo. Meu ambiente de desenvolvimento
está inteiro no terminal, então não preciso de GUI.&lt;/p&gt;
&lt;p&gt;E sim, uso as IAs todas (Claude, Codex e Gemini) via CLI sem interface gráfica.&lt;/p&gt;
&lt;p&gt;O que fiz foi configurar SSH em todos os computadores. Então, consigo trabalhar
por qualquer um deles simplesmente digitando:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;ssh m132 # &amp;lt;-- Me conecto por aqui
ssh m4128
ssh fedoraair # &amp;lt;-- E trabalho aqui
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sem perceber, acabei montando o &lt;strong&gt;cenário perfeito para o Docker Context&lt;/strong&gt;. A
única coisa que você precisa para isso funcionar da mesma maneira que uso por
aqui é uma conexão SSH com o computador ou servidor remoto.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# Do m132 entro no fedoraair
ssh fedoraair

# Do fedoraair rodo comandos normal
docker run ...

# O Docker Context está apontado para m4128
# Então, comandos do Docker CLI, rodam lá
# e não consomem recursos do fedoraair 🥹.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isso significa que toda vez que estou usando o Docker no fedoraair, estou usando
3 computadores ao mesmo tempo (&lt;em&gt;LITERALMENTE&lt;/em&gt;).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;O que é o Docker Context?&lt;/h2&gt;
&lt;p&gt;Ao invés de entrar via SSH para rodar comandos manualmente no &lt;strong&gt;m4128&lt;/strong&gt;, uso o
cliente do Docker (o CLI) para enviar comandos remotamente para o motor (engine)
do Docker na máquina potente.&lt;/p&gt;
&lt;p&gt;Faça esse teste comigo... Vai lá, coragem... Abre esse terminal aí agora e roda
o comando:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker context ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Você vai ver a lista de contextos configurados na sua máquina.&lt;/p&gt;
&lt;p&gt;Por padrão, ele aponta para um Unix Socket local, o que significa que os
comandos rodam na sua própria máquina.&lt;/p&gt;
&lt;p&gt;Mas nós podemos criar novos contextos para apontar para outras máquinas.&lt;/p&gt;
&lt;h3&gt;Como criar e usar um Docker Context remotamente&lt;/h3&gt;
&lt;p&gt;Suponha que eu queira me conectar ao &lt;strong&gt;m4128&lt;/strong&gt; agora. Posso criar um contexto
novo especificamente para isso.&lt;/p&gt;
&lt;p&gt;Se você tem um servidor, mesma coisa. Não importa onde ele estiver (só precisa
de SSH). Crie o contexto:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker context create m4128 --docker &amp;quot;host=ssh://USUARIO@HOST_OU_IP&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Muda o nome do contexto para o que quiser. Vou usar &lt;strong&gt;m4128&lt;/strong&gt; só porque já me
acostumei com os nomes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Dica: Se você já configurou o seu arquivo &lt;code&gt;.ssh/config&lt;/code&gt; como eu (ensinei isso
&lt;a href=&quot;https://youtu.be/0eG9Vorc-TY?t=1172&amp;si=xuTkSYeD59lJQtSp&quot;&gt;neste vídeo&lt;/a&gt;), você
pode simplesmente usar o alias que você definiu:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker context create m4128 --docker &amp;quot;host=ssh://m4128&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para listar e ver se deu tudo certo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker context ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora que o contexto foi criado, tudo o que precisamos fazer é dizer ao Docker
para usá-lo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker context use m4128
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A partir deste momento, qualquer comando Docker que você digitar no seu terminal
(como &lt;code&gt;docker run&lt;/code&gt;, &lt;code&gt;docker service ls&lt;/code&gt; ou &lt;code&gt;docker node ls&lt;/code&gt;) será executado não
na sua máquina local, mas lá no servidor remoto.&lt;/p&gt;
&lt;p&gt;A magia disso é que você pode interagir com um Swarm ou subir imagens pesadas no
seu provedor de nuvem (como a &lt;a href=&quot;https://hostinger.com/otaviomiranda&quot;&gt;Hostinger&lt;/a&gt;)
direto do seu laptop, sem consumir um único MegaByte da sua memória local!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Intromissão sem vergonha 💜&lt;/p&gt;
&lt;p&gt;Se precisar de servidor VPS, tenho link e cupom que te dá um belo desconto por
até 2 anos se quiser.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://hostinger.com/otaviomiranda&quot;&gt;https://hostinger.com/otaviomiranda&lt;/a&gt;&lt;br&gt;Cupom: OTAVIOMIRANDA&lt;/p&gt;
&lt;p&gt;Obrigado à Hostinger por acreditar no meu conteúdo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;O Cuidado Necessário: Bind Mounts vs Named Volumes&lt;/h2&gt;
&lt;p&gt;Nem tudo são flores. Ao utilizar esse setup, você precisa ficar &lt;strong&gt;atento aos
Volumes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Quando usamos &lt;a href=&quot;https://docs.docker.com/engine/storage/bind-mounts/&quot;&gt;bind mount&lt;/a&gt;
(&lt;em&gt;que atrela uma pasta local do seu projeto ao container&lt;/em&gt;), o Docker tentará
fazer o bind da pasta que existe lá no servidor, e não na sua máquina local de
onde você disparou o comando. Afinal, o motor do Docker está rodando
remotamente.&lt;/p&gt;
&lt;h3&gt;Como resolver isso?&lt;/h3&gt;
&lt;p&gt;Se a sua imagem não depende de arquivos locais, você pode usar o setup remoto
sem problemas.&lt;/p&gt;
&lt;p&gt;Se você precisa de volumes, recomendo utilizar Named Volumes no seu
&lt;code&gt;docker-compose.yml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Exemplo de uso com Named Volumes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  app:
    image: nginx:latest
    volumes:
      - dados-app:/var/www/html

volumes:
  dados-app:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dessa forma, o Docker se encarrega de criar e gerenciar o volume na máquina
remota, evitando transtornos de sincronização de pastas ou de erros ao tentar
mapear uma pasta que só existe no seu computador local.&lt;/p&gt;
&lt;p&gt;Existem outras formas de contornar isso, mas essa é, de longe, a mais fácil.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Alternativa com Variável de Ambiente&lt;/h2&gt;
&lt;p&gt;Para uma alteração rápida, sem precisar criar e salvar um contexto inteiro, você
pode utilizar a variável de ambiente &lt;code&gt;DOCKER_HOST&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;export DOCKER_HOST=ssh://m4128
docker ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ele fará praticamente o mesmo processo, de forma direta e provisória. Recomendo
usar o docker context para um setup mais contínuo e organizado.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Seja para economizar recursos locais utilizando um servidor potente ou para
gerenciar seu ambiente em nuvem remotamente, o Docker Context via SSH é uma
ferramenta indispensável no seu cinto de utilidades &lt;strong&gt;DevOps&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Se você gostou deste conteúdo e não quer perder as próximas dicas de só me
seguir! Posto aqui e em várias outras redes direto 😘.&lt;/p&gt;
</content:encoded></item><item><title>Shiki - Syntax highlighter</title><link>https://otaviomiranda.com.br/2026/shiki_syntax_highlighter_generator/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2026/shiki_syntax_highlighter_generator/</guid><description>For my personal use. I just want the end HTML and CSS for Shiki Syntax highlighter (Not the JavaScript). This would be only for pure static sites.</description><pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;
  For my personal use. I just want the end HTML and CSS from
  &lt;a rel=&quot;nofollow noopener noreferrer&quot; href=&quot;https://shiki.matsu.io/&quot;
    &gt;Shiki Syntax highlighter&lt;/a
  &gt;
  (Not the JavaScript). This would be only for pure static sites.
&lt;/p&gt;

&lt;div class=&quot;form&quot;&gt;
  &lt;div class=&quot;form-row&quot;&gt;
    &lt;label for=&quot;shiki-language&quot;&gt;Language:&lt;/label&gt;
    &lt;select id=&quot;shiki-language&quot; class=&quot;shiki-language&quot;&gt;&lt;/select&gt;
  &lt;/div&gt;

  &lt;div class=&quot;form-row&quot;&gt;
    &lt;label for=&quot;shiki-theme&quot;&gt;Theme:&lt;/label&gt;
    &lt;select id=&quot;shiki-theme&quot; class=&quot;shiki-theme&quot;&gt;&lt;/select&gt;
  &lt;/div&gt;

  &lt;div class=&quot;form-row&quot;&gt;
    &lt;textarea
      id=&quot;textarea-code&quot;
      placeholder=&quot;Place your code here.&quot;
    &gt;&lt;/textarea&gt;
  &lt;/div&gt;

  &lt;div class=&quot;form-row form-row-submit&quot;&gt;
    &lt;div class=&quot;link-container&quot;&gt;
      &lt;div class=&quot;link-bg-blur&quot;&gt;&lt;/div&gt;
      &lt;div class=&quot;link-bg&quot;&gt;&lt;/div&gt;
      &lt;button id=&quot;get-code&quot; type=&quot;submit&quot; class=&quot;link&quot;&gt;Get code&lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Your beautiful code will be available below.&lt;/p&gt;

&lt;div id=&quot;shiki-result&quot;&gt;&lt;/div&gt;

&lt;script&gt;

(function () {
    &apos;use strict&apos;;

    const languages = [
        &apos;AppleScript&apos;,
        &apos;AsciiDoc&apos;,
        &apos;asm&apos;,
        &apos;Astro&apos;,
        &apos;AWK&apos;,
        &apos;Ballerina&apos;,
        &apos;Beancount&apos;,
        &apos;Berry&apos;,
        &apos;BibTeX&apos;,
        &apos;Bicep&apos;,
        &apos;Blade&apos;,
        &apos;C&apos;,
        &apos;C3&apos;,
        &apos;Cadence&apos;,
        &apos;Cairo&apos;,
        &apos;Clarity&apos;,
        &apos;Clojure&apos;,
        &apos;CMake&apos;,
        &apos;COBOL&apos;,
        &apos;CODEOWNERS&apos;,
        &apos;CodeQL&apos;,
        &apos;CoffeeScript&apos;,
        &apos;Coq&apos;,
        &apos;CSS&apos;,
        &apos;CSV&apos;,
        &apos;CUE&apos;,
        &apos;Cypher&apos;,
        &apos;D&apos;,
        &apos;Dart&apos;,
        &apos;DAX&apos;,
        &apos;Desktop&apos;,
        &apos;Diff&apos;,
        &apos;Dockerfile&apos;,
        &apos;dotEnv&apos;,
        &apos;Edge&apos;,
        &apos;Elixir&apos;,
        &apos;Elm&apos;,
        &apos;ERB&apos;,
        &apos;Erlang&apos;,
        &apos;Fennel&apos;,
        &apos;Fish&apos;,
        &apos;Fluent&apos;,
        &apos;GDResource&apos;,
        &apos;GDScript&apos;,
        &apos;GDShader&apos;,
        &apos;Genie&apos;,
        &apos;Gherkin&apos;,
        &apos;Gleam&apos;,
        &apos;GLSL&apos;,
        &apos;GN&apos;,
        &apos;Gnuplot&apos;,
        &apos;Go&apos;,
        &apos;GraphQL&apos;,
        &apos;Groovy&apos;,
        &apos;Hack&apos;,
        &apos;Handlebars&apos;,
        &apos;Haskell&apos;,
        &apos;Haxe&apos;,
        &apos;Hjson&apos;,
        &apos;HLSL&apos;,
        &apos;HTML&apos;,
        &apos;HTTP&apos;,
        &apos;Hurl&apos;,
        &apos;HXML&apos;,
        &apos;Hy&apos;,
        &apos;Imba&apos;,
        &apos;INI&apos;,
        &apos;Java&apos;,
        &apos;JavaScript&apos;,
        &apos;Jinja&apos;,
        &apos;Jison&apos;,
        &apos;JSON&apos;,
        &apos;JSON5&apos;,
        &apos;JSONc&apos;,
        &apos;JSONl&apos;,
        &apos;Jsonnet&apos;,
        &apos;JSSM&apos;,
        &apos;JSX&apos;,
        &apos;Julia&apos;,
        &apos;KDL&apos;,
        &apos;Kotlin&apos;,
        &apos;Kusto&apos;,
        &apos;LaTeX&apos;,
        &apos;Lean4&apos;,
        &apos;Less&apos;,
        &apos;Liquid&apos;,
        &apos;Logo&apos;,
        &apos;Lua&apos;,
        &apos;Luau&apos;,
        &apos;Makefile&apos;,
        &apos;Markdown&apos;,
        &apos;Marko&apos;,
        &apos;MATLAB&apos;,
        &apos;MDC&apos;,
        &apos;MDX&apos;,
        &apos;Mermaid&apos;,
        &apos;Mojo&apos;,
        &apos;MoonBit&apos;,
        &apos;Move&apos;,
        &apos;Nextflow&apos;,
        &apos;Nginx&apos;,
        &apos;Nim&apos;,
        &apos;Nix&apos;,
        &apos;nushell&apos;,
        &apos;OCaml&apos;,
        &apos;Odin&apos;,
        &apos;OpenSCAD&apos;,
        &apos;Pascal&apos;,
        &apos;Perl&apos;,
        &apos;PHP&apos;,
        &apos;Pkl&apos;,
        &apos;Polar&apos;,
        &apos;PostCSS&apos;,
        &apos;PowerQuery&apos;,
        &apos;PowerShell&apos;,
        &apos;Prisma&apos;,
        &apos;Prolog&apos;,
        &apos;Pug&apos;,
        &apos;Puppet&apos;,
        &apos;PureScript&apos;,
        &apos;Python&apos;,
        &apos;QML&apos;,
        &apos;R&apos;,
        &apos;Racket&apos;,
        &apos;Raku&apos;,
        &apos;RegExp&apos;,
        &apos;Rel&apos;,
        &apos;RON&apos;,
        &apos;Ruby&apos;,
        &apos;Rust&apos;,
        &apos;SAS&apos;,
        &apos;Sass&apos;,
        &apos;Scala&apos;,
        &apos;Scheme&apos;,
        &apos;SCSS&apos;,
        &apos;ShaderLab&apos;,
        &apos;Shell&apos;,
        &apos;Smalltalk&apos;,
        &apos;Solidity&apos;,
        &apos;SPARQL&apos;,
        &apos;SQL&apos;,
        &apos;SSH-Config&apos;,
        &apos;Stata&apos;,
        &apos;Stylus&apos;,
        &apos;SurrealQL&apos;,
        &apos;Svelte&apos;,
        &apos;Swift&apos;,
        &apos;Systemd&apos;,
        &apos;TalonScript&apos;,
        &apos;Tasl&apos;,
        &apos;Tcl&apos;,
        &apos;Templ&apos;,
        &apos;Terraform&apos;,
        &apos;TeX&apos;,
        &apos;TOML&apos;,
        &apos;ts-tags&apos;,
        &apos;TSV&apos;,
        &apos;TSX&apos;,
        &apos;Turtle&apos;,
        &apos;Twig&apos;,
        &apos;TypeScript&apos;,
        &apos;TypeSpec&apos;,
        &apos;Typst&apos;,
        &apos;V&apos;,
        &apos;Vala&apos;,
        &apos;Verilog&apos;,
        &apos;VHDL&apos;,
        &apos;Viml&apos;,
        &apos;Vue&apos;,
        &apos;Vue-HTML&apos;,
        &apos;Vue-Vine&apos;,
        &apos;Vyper&apos;,
        &apos;wasm&apos;,
        &apos;Wenyan&apos;,
        &apos;WGSL&apos;,
        &apos;Wikitext&apos;,
        &apos;Wolfram&apos;,
        &apos;XML&apos;,
        &apos;XSL&apos;,
        &apos;YAML&apos;,
        &apos;ZenScript&apos;,
        &apos;Zig&apos;,
    ];

    const themes = [
        &apos;andromeeda&apos;,
        &apos;aurora-x&apos;,
        &apos;ayu-dark&apos;,
        &apos;ayu-light&apos;,
        &apos;ayu-mirage&apos;,
        &apos;catppuccin-frappe&apos;,
        &apos;catppuccin-latte&apos;,
        &apos;catppuccin-macchiato&apos;,
        &apos;catppuccin-mocha&apos;,
        &apos;dark-plus&apos;,
        &apos;dracula&apos;,
        &apos;dracula-soft&apos;,
        &apos;everforest-dark&apos;,
        &apos;everforest-light&apos;,
        &apos;github-dark&apos;,
        &apos;github-dark-default&apos;,
        &apos;github-dark-dimmed&apos;,
        &apos;github-dark-high-contrast&apos;,
        &apos;github-light&apos;,
        &apos;github-light-default&apos;,
        &apos;github-light-high-contrast&apos;,
        &apos;gruvbox-dark-hard&apos;,
        &apos;gruvbox-dark-medium&apos;,
        &apos;gruvbox-dark-soft&apos;,
        &apos;gruvbox-light-hard&apos;,
        &apos;gruvbox-light-medium&apos;,
        &apos;gruvbox-light-soft&apos;,
        &apos;horizon&apos;,
        &apos;houston&apos;,
        &apos;kanagawa-dragon&apos;,
        &apos;kanagawa-lotus&apos;,
        &apos;kanagawa-wave&apos;,
        &apos;laserwave&apos;,
        &apos;light-plus&apos;,
        &apos;material-theme&apos;,
        &apos;material-theme-darker&apos;,
        &apos;material-theme-lighter&apos;,
        &apos;material-theme-ocean&apos;,
        &apos;material-theme-palenight&apos;,
        &apos;min-dark&apos;,
        &apos;min-light&apos;,
        &apos;monokai&apos;,
        &apos;night-owl&apos;,
        &apos;night-owl-light&apos;,
        &apos;nord&apos;,
        &apos;one-dark-pro&apos;,
        &apos;one-light&apos;,
        &apos;plastic&apos;,
        &apos;poimandres&apos;,
        &apos;red&apos;,
        &apos;rose-pine&apos;,
        &apos;rose-pine-dawn&apos;,
        &apos;rose-pine-moon&apos;,
        &apos;slack-dark&apos;,
        &apos;slack-ochin&apos;,
        &apos;snazzy-light&apos;,
        &apos;solarized-dark&apos;,
        &apos;solarized-light&apos;,
        &apos;synthwave-84&apos;,
        &apos;tokyo-night&apos;,
        &apos;vesper&apos;,
        &apos;vitesse-black&apos;,
        &apos;vitesse-dark&apos;,
        &apos;vitesse-light&apos;,
    ];

    const selectLanguage = document.querySelector(&apos;#shiki-language&apos;);
    const selectTheme = document.querySelector(&apos;#shiki-theme&apos;);
    const getCodeBtn = document.querySelector(&apos;#get-code&apos;);
    const shikiResult = document.querySelector(&apos;#shiki-result&apos;);
    const code = document.querySelector(&apos;#textarea-code&apos;);

    // Verify elements existence to avoid errors on other pages
    if (
        !selectLanguage ||
        !selectTheme ||
        !getCodeBtn ||
        !shikiResult ||
        !code
    ) {
        return;
    }

    languages.forEach(language =&gt; {
        const option = document.createElement(&apos;option&apos;);
        option.value = language;
        option.textContent = language;
        selectLanguage.appendChild(option);
    });

    themes.forEach(theme =&gt; {
        const option = document.createElement(&apos;option&apos;);
        option.value = theme;
        option.textContent = theme;
        selectTheme.appendChild(option);
    });

    getCodeBtn.addEventListener(&apos;click&apos;, getShikiResult);
    selectLanguage.addEventListener(&apos;change&apos;, getShikiResult);
    selectTheme.addEventListener(&apos;change&apos;, getShikiResult);

    async function getShikiResult() {
        try {
            const value = code.value.trim();

            if (!value) {
                return;
            }

            // Loading state
            const originalBtnText = getCodeBtn.textContent;
            getCodeBtn.textContent = &apos;Loading...&apos;;
            getCodeBtn.disabled = true;
            shikiResult.style.opacity = &apos;0.5&apos;;

            const { codeToHtml } = await import(&apos;https://esm.run/shiki@3.22&apos;);

            shikiResult.innerHTML = &apos;&apos;;

            const selectedLang = selectLanguage.value;
            const selectedTheme = selectTheme.value;

            const html = await codeToHtml(value, {
                lang: selectedLang.toLowerCase().replace(&apos; &apos;, &apos;_&apos;),
                theme: selectedTheme,
            });

            const preview = document.createElement(&apos;div&apos;);
            preview.classList.add(&apos;preview-result&apos;);
            preview.innerHTML = html;
            shikiResult.appendChild(preview);

            const txtAreaResult = document.createElement(&apos;textarea&apos;);
            txtAreaResult.classList.add(&apos;textarea-result&apos;);
            txtAreaResult.value = html;
            shikiResult.appendChild(txtAreaResult);

        } catch (e) {
            console.error(e);
            alert(&apos;An error occurred while generating the code. Please check the console for details.&apos;);
            shikiResult.innerHTML = &apos;&lt;p style=&quot;color: red;&quot;&gt;Error generating code. See console.&lt;/p&gt;&apos;;
        } finally {
            // Restore state
            getCodeBtn.textContent = &apos;Get code&apos;;
            getCodeBtn.disabled = false;
            shikiResult.style.opacity = &apos;1&apos;;
        }
    }
})();

&lt;/script&gt;</content:encoded></item><item><title>f-strings em Python: coisas que você não sabia</title><link>https://otaviomiranda.com.br/2025/f-strings-no-python-do-basico-ao-avancado/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2025/f-strings-no-python-do-basico-ao-avancado/</guid><description>Uso avançado de f-strings para formatação de strings em Python, cobrindo conceitos como concatenação, formatação numérica e representação em diferentes bases numéricas.</description><pubDate>Mon, 10 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Este texto explora o uso avançado das f-strings no Python, incluindo
concatenação, formatação numérica, representação em diferentes bases e várias
outras técnicas que talvez você não conheça.&lt;/p&gt;
&lt;p&gt;Eu me baseei na transcrição do meu vídeo original, que você pode assistir aqui:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/yt2wPLGMAA0&quot;&gt;f-strings em Python: coisas que você não sabia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mas atenção: o texto não é idêntico ao vídeo. Incluí novos exemplos de código,
explicações alternativas e detalhei melhor alguns tópicos que achei
interessantes. Espero que você goste!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dica importante:&lt;/strong&gt; coloquei MUITA coisa bacana nos comentários dos exemplos de
código, não deixe de conferir.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ℹ️ Baseado &lt;a href=&quot;https://youtu.be/yt2wPLGMAA0&quot;&gt;no meu vídeo original&lt;/a&gt;, este texto
foi gerado inicialmente com o apoio de Inteligência Artificial e da
transcrição do vídeo feita pelo &lt;a href=&quot;https://youtu.be/y15070biffg&quot;&gt;Whisper&lt;/a&gt;.
Posteriormente, reescrevi o conteúdo para garantir clareza e precisão.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;Introdução: concatenando strings com inteiros&lt;/h2&gt;
&lt;p&gt;Um desafio comum na programação é concatenar strings com outros tipos de dados,
como inteiros. Em Python, se você tentar concatenar diretamente uma string com
um inteiro, terá um erro de tipo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;numero = 123456789
texto = &amp;quot;Número: &amp;quot;

# Isso gera um erro: TypeError: can only concatenate str (not &amp;quot;int&amp;quot;) to str
print(texto + numero)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para resolver isso, você pode converter explicitamente o inteiro para string
usando a função &lt;code&gt;str()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;numero = 123456789
texto = &amp;quot;Número: &amp;quot;

# Convertendo o inteiro para string antes da concatenação
print(texto + str(numero))
# Saída: Número: 123456789
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mas as &lt;strong&gt;f-strings&lt;/strong&gt; oferecem uma solução muito mais elegante, legível e
eficiente para essa tarefa. Vamos conferir na prática como utilizar esse recurso
poderoso do Python!&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Introdução às &lt;code&gt;f-strings&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;As &lt;code&gt;f-strings&lt;/code&gt; (formatação de string com prefixo &lt;code&gt;f&lt;/code&gt;) oferecem uma sintaxe
concisa e poderosa para incorporar expressões Python diretamente dentro de uma
string (&lt;code&gt;str&lt;/code&gt;). Para utilizá-las, basta prefixar a string com &lt;code&gt;f&lt;/code&gt; e envolver a
expressão desejada entre chaves &lt;code&gt;{}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Exemplo básico:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;numero = 123456789
texto = f&amp;quot;Número: {numero}&amp;quot;
print(texto) # Saída: Número: 123456789
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse método funciona com strings de uma ou várias linhas, com aspas simples,
duplas ou triplas.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Formatação numérica com f-strings&lt;/h2&gt;
&lt;p&gt;As &lt;code&gt;f-strings&lt;/code&gt; facilitam muito a formatação numérica, permitindo aplicar
separadores de milhares e definir facilmente quantas casas decimais você quer
exibir.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Separadores de milhares&lt;/h3&gt;
&lt;p&gt;Você pode utilizar &lt;strong&gt;underscores (&lt;code&gt;_&lt;/code&gt;)&lt;/strong&gt; ou &lt;strong&gt;vírgulas (&lt;code&gt;,&lt;/code&gt;)&lt;/strong&gt; após os dois
pontos (&lt;code&gt;:&lt;/code&gt;) dentro das chaves para destacar os milhares. Veja os exemplos:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;numero = 123456789

# Usando underscore como separador de milhares
print(f&amp;quot;Número com underscores: {numero:_}&amp;quot;)  # Saída: 123_456_789

# Usando vírgula como separador de milhares
print(f&amp;quot;Número com vírgulas: {numero:,}&amp;quot;)     # Saída: 123,456,789
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esses formatos deixam os números grandes bem mais fáceis de ler!&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Definindo casas decimais com &lt;code&gt;.nf&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Para formatar números com ponto flutuante usando &lt;code&gt;f-strings&lt;/code&gt;, basta utilizar
&lt;code&gt;.nf&lt;/code&gt;, onde &lt;code&gt;n&lt;/code&gt; é a quantidade desejada de casas decimais após o ponto.&lt;/p&gt;
&lt;p&gt;Veja os exemplos:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;numero_float = 198.7654321

# Limitando a duas casas decimais
print(f&amp;quot;Duas casas decimais: {numero_float:.2f}&amp;quot;)  # Saída: 198.77

# Arredondando para inteiro (sem casas decimais)
print(f&amp;quot;Sem casas decimais: {numero_float:.0f}&amp;quot;)   # Saída: 199

# Limitando a quatro casas decimais
print(f&amp;quot;Quatro casas decimais: {numero_float:.4f}&amp;quot;)  # Saída: 198.7654
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Com isso você consegue controlar exatamente como seu número será exibido.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Combinando separador de milhares e casas decimais&lt;/h3&gt;
&lt;p&gt;Você também pode combinar os separadores de milhares com a definição de casas
decimais, tudo numa única f-string. Veja o exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;numero_float = 19876.54321

# Separador de milhares com vírgula e duas casas decimais
print(f&amp;quot;Formatado: {numero_float:,.2f}&amp;quot;)  # Saída: 19,876.54
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Essa abordagem é especialmente útil em dashboards, relatórios financeiros e
interfaces voltadas ao usuário, tornando os valores numéricos bem mais legíveis.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Exibindo sinais (positivo e negativo)&lt;/h3&gt;
&lt;p&gt;É possível exibir explicitamente os sinais positivos ou negativos em números
usando o símbolo &lt;code&gt;+&lt;/code&gt;. Veja o exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;numero_positivo = 10
numero_negativo = -10

# Exibindo explicitamente o sinal positivo
print(f&amp;quot;Número positivo: {numero_positivo:+d}&amp;quot;)  # Saída: +10

# Número negativo já exibe o sinal por padrão
print(f&amp;quot;Número negativo: {numero_negativo:d}&amp;quot;)   # Saída: -10
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Observação:&lt;/strong&gt; O sinal &lt;code&gt;+&lt;/code&gt; força a exibição do sinal também em números
positivos. Para negativos, o sinal aparece automaticamente.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3&gt;Porcentagem&lt;/h3&gt;
&lt;p&gt;Para representar números como porcentagem, utilize o especificador &lt;code&gt;%&lt;/code&gt; junto com
a quantidade desejada de casas decimais:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;porcentagem = 0.98

# Exibindo porcentagem com duas casas decimais
print(f&amp;quot;Porcentagem: {porcentagem:.2%}&amp;quot;)  # Saída: 98.00%
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;Conversões avançadas: &lt;code&gt;bytes&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;str&lt;/code&gt; e &lt;code&gt;hex&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;É possível realizar conversões entre diferentes tipos, como string, inteiro,
hexadecimal, octal, binário, ASCII, bytes e outros formatos usando funções
internas (built-ins) do Python. Quando combinamos essas funções com as
&lt;code&gt;f-strings&lt;/code&gt;, temos um poder imenso nas mãos.&lt;/p&gt;
&lt;p&gt;Além disso, usando &lt;code&gt;!r&lt;/code&gt; dentro das f-strings, você exibe diretamente a
representação interna do objeto (o mesmo que usar &lt;code&gt;repr()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Importante:&lt;/strong&gt; Ao usar f-strings, os objetos são automaticamente convertidos
para strings (&lt;code&gt;str&lt;/code&gt;). Strings têm métodos especiais como &lt;code&gt;encode&lt;/code&gt; para converter
em bytes, e bytes têm o método &lt;code&gt;decode&lt;/code&gt; para voltar para string. Você pode
conferir mais detalhes sobre esse assunto no artigo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://otaviomiranda.com.br/2020/normalizacao-unicode-em-python/&quot;&gt;Normalização Unicode em Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Agora, veja na prática como converter um inteiro em hexadecimal, depois em
bytes, em string novamente, e finalmente voltar ao inteiro original. Este
exemplo combina várias técnicas que você pode usar em Python:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Número inteiro original
num = 255  # inteiro normal: 255

# Convertendo inteiro para hexadecimal (str)
int_to_hex = f&amp;#39;{num:x}&amp;#39;  # resultado: &amp;#39;ff&amp;#39;

# Convertendo o hexadecimal (str) em bytes
hex_to_bytes = int_to_hex.encode(&amp;#39;utf-8&amp;#39;)  # resultado: b&amp;#39;ff&amp;#39;

# Convertendo bytes de volta para string
bytes_to_str = hex_to_bytes.decode(&amp;#39;utf-8&amp;#39;)  # resultado: &amp;#39;ff&amp;#39;

# Finalmente, convertendo a string hexadecimal de volta para inteiro
hex_to_int = int(bytes_to_str, 16)  # resultado: 255

# Exibindo representações internas usando !r (repr)
print(f&amp;quot;{num = !r}&amp;quot;)           # num = 255
print(f&amp;quot;{int_to_hex = !r}&amp;quot;)    # int_to_hex = &amp;#39;ff&amp;#39;
print(f&amp;quot;{hex_to_bytes = !r}&amp;quot;)  # hex_to_bytes = b&amp;#39;ff&amp;#39;
print(f&amp;quot;{bytes_to_str = !r}&amp;quot;)  # bytes_to_str = &amp;#39;ff&amp;#39;
print(f&amp;quot;{hex_to_int = !r}&amp;quot;)    # hex_to_int = 255

# Exemplos adicionais com :o, :b, :x e :X
print(f&amp;quot;Inteiro: {num = }&amp;quot;)          # Inteiro: num = 255
print(f&amp;quot;Octal: {num = :o}&amp;quot;)          # Octal: num = 377
print(f&amp;quot;Binário: {num = :b}&amp;quot;)        # Binário: num = 11111111
print(f&amp;quot;Hexadecimal: {num = :x}&amp;quot;)    # Hexadecimal: num = ff
print(f&amp;quot;HEXADECIMAL: {num = :X}&amp;quot;)    # HEXADECIMAL: num = FF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Entendendo as formatações usadas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:o&lt;/code&gt; exibe o valor em octal.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:b&lt;/code&gt; exibe o valor em binário.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:x&lt;/code&gt; exibe o valor em hexadecimal (letras minúsculas).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:X&lt;/code&gt; exibe o valor em hexadecimal (letras maiúsculas).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!r&lt;/code&gt; exibe a representação do objeto (equivalente ao &lt;code&gt;repr()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;=&lt;/code&gt; exibe também a expressão que gerou o valor dentro da f-string
(&lt;code&gt;num = valor_de_num&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;É bastante coisa nova, né? A seguir eu explico tudo isso com mais detalhes!&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;O Operador Walrus (&lt;code&gt;:=&lt;/code&gt;) em f-strings&lt;/h2&gt;
&lt;p&gt;O operador Walrus (&lt;code&gt;:=&lt;/code&gt;) permite atribuir valores diretamente dentro de
expressões, inclusive dentro de f-strings.&lt;/p&gt;
&lt;p&gt;Com ele, você faz uma atribuição inline e já reutiliza o valor calculado
imediatamente, sem precisar declarar a variável previamente.&lt;/p&gt;
&lt;p&gt;Veja o exemplo abaixo: temos uma sequência de bytes que representa a palavra
&amp;quot;Otávio&amp;quot; codificada em UTF-8. Primeiro, decodificamos essa sequência para uma
string. Depois, usando o operador Walrus dentro da f-string, atribuímos esse
valor a uma variável mais curta (&lt;code&gt;v&lt;/code&gt;), que pode ser reutilizada em seguida.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Sequência de bytes que representa a palavra &amp;quot;Otávio&amp;quot;
my_name = b&amp;#39;\x4f\x74\xc3\xa1\x76\x69\x6f&amp;#39;

# Decodificando bytes para uma variável com nome longo
variavel_longa_e_chata = my_name.decode(&amp;#39;utf-8&amp;#39;)  # resultado: &amp;quot;Otávio&amp;quot;

# Usando operador Walrus diretamente dentro da f-string para atribuir a `v`
print(f&amp;quot;Saída: {(v := variavel_longa_e_chata) = }&amp;quot;)
# Saída: (v := variavel_longa_e_chata) = &amp;#39;Otávio&amp;#39;

# Agora `v` já está disponível para reutilização
print(f&amp;quot;Reutilizando `v`: {v} foi muito legal. {v}&amp;quot;)
# Saída: Reutilizando `v`: Otávio foi muito legal. Otávio
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O trecho &lt;code&gt;{(v := variavel_longa_e_chata) = }&lt;/code&gt; dentro da f-string exibe tanto o
código executado quanto o valor atribuído. Isso é especialmente útil em debug,
logs ou simplesmente para mostrar valores intermediários sem precisar criar
variáveis separadas antes do print.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; A sequência de bytes &lt;code&gt;\x4f\x74\xc3\xa1\x76\x69\x6f&lt;/code&gt;, decodificada
com UTF-8, forma a palavra &amp;quot;Otávio&amp;quot;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;Trabalhando com datas e horas usando &lt;code&gt;f-strings&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;A seguir vou mostrar diversos exemplos práticos sobre formatação de datas,
horas, minutos, segundos, milissegundos e microsegundos usando f-strings. Esses
formatos são úteis em praticamente qualquer aplicação que envolva manipulação de
datas e horários.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Transformando segundos em horas, minutos e segundos (&lt;code&gt;H:M:S&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;Nesse exemplo, você vai aprender como formatar uma quantidade total de segundos
no formato tradicional &lt;code&gt;horas:minutos:segundos&lt;/code&gt; (&lt;code&gt;H:M:S&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Primeiro, vamos criar uma função simples para fazer essa conversão pra gente:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def calcula_segundos_para_horas(segundos: int) -&amp;gt; tuple[int, int, int]:
    &amp;quot;&amp;quot;&amp;quot;
    Converte uma quantidade total de segundos em horas, minutos e segundos.

    Exemplo de uso:
        h, min, seg = calcula_segundos_para_horas(31989)
    &amp;quot;&amp;quot;&amp;quot;
    horas = segundos // 60 // 60
    minutos = segundos // 60 % 60
    segundos = segundos % 60

    return horas, minutos, segundos
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora, vamos brincar com as &lt;strong&gt;f-strings&lt;/strong&gt; pra exibir o tempo formatado:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 8 horas, 53 minutos, 9 segundos
# Total desejado: 08:53:09
total_de_segundos = 31989
horas, minutos, segundos = calcula_segundos_para_horas(total_de_segundos)

# Sem formatação (resultado incorreto visualmente)
print(f&amp;quot;Tempo: {horas}:{minutos}:{segundos}&amp;quot;)
# Saída: Tempo: 8:53:9

# Com formatação (padding com zeros à esquerda)
print(f&amp;quot;Tempo: {horas:02}:{minutos:02}:{segundos:02}&amp;quot;)
# Saída correta: Tempo: 08:53:09
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Também é possível exibir essa mesma informação de forma mais descritiva:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Exibindo de forma mais amigável
print(f&amp;quot;{horas} horas, {minutos} minutos e {segundos} segundos&amp;quot;)
# Saída: 8 horas, 53 minutos e 9 segundos
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Você até pode inserir uma condicional diretamente dentro da f-string, embora, na
minha opinião, fique meio feio e bagunçado dependendo da complexidade. Porém, às
vezes pode ser útil:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Retirando os 9 segundos para zerar o valor
total_de_segundos = 31980
horas, minutos, segundos = calcula_segundos_para_horas(total_de_segundos)

# Criando uma string condicional para exibir segundos somente se forem maiores que zero
texto_segundos = f&amp;quot;, {segundos} segundos&amp;quot;
print(f&amp;quot;{horas} horas, {minutos} minutos{texto_segundos if segundos &amp;gt; 0 else &amp;#39;&amp;#39;}&amp;quot;)
# Saída: 8 horas, 53 minutos
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para situações mais complexas, o ideal seria criar uma função de formatação
separada, principalmente para evitar coisas estranhas como &amp;quot;1 segundos&amp;quot;, que
fica bem feio, né?&lt;/p&gt;
&lt;p&gt;Se suas contas eventualmente retornarem números em ponto flutuante (&lt;code&gt;float&lt;/code&gt;),
você pode formatar assim, garantindo que não haverá casas decimais:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;print(f&amp;quot;Tempo: {horas:02.0f}:{minutos:02.0f}:{segundos:02.0f}&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Explicando rapidamente o significado de &lt;code&gt;:02.0f&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:&lt;/code&gt; inicia a formatação;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;02&lt;/code&gt; indica que o número deve ter dois caracteres no total, preenchidos com
zeros à esquerda se necessário (&lt;code&gt;1&lt;/code&gt; vira &lt;code&gt;01&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.0f&lt;/code&gt; significa que não desejamos nenhuma casa decimal (nem mesmo o ponto).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Manipulação de horas usando &lt;code&gt;f-string&lt;/code&gt;, &lt;code&gt;datetime&lt;/code&gt; e &lt;code&gt;timedelta&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Vamos criar uma data base apenas para facilitar a formatação de horas. Neste
momento não precisamos de uma data real, então vou usar algo bem simples:
&lt;code&gt;datetime(1, 1, 1, 8, 23, 1)&lt;/code&gt;. Isso representa o horário &lt;code&gt;08:23:01&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from datetime import datetime

# Criando a hora base
hora = datetime(1, 1, 1, 8, 23, 1)

# Formatando com strftime()
print(f&amp;quot;Hora: {hora.strftime(&amp;#39;%H:%M:%S&amp;#39;)}&amp;quot;)
# Saída: Hora: 08:23:01
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Dica:&lt;/strong&gt; Os valores &lt;code&gt;%H&lt;/code&gt;, &lt;code&gt;%M&lt;/code&gt; e &lt;code&gt;%S&lt;/code&gt; representam, respectivamente, horas,
minutos e segundos com zero à esquerda. Veja todas as opções disponíveis na
&lt;a href=&quot;https://docs.python.org/3/library/datetime.html#format-codes&quot;&gt;documentação do Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Você também pode usar &lt;code&gt;timedelta&lt;/code&gt; para adicionar ou subtrair tempo facilmente.
Vamos ver isso em ação:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from datetime import datetime, timedelta

# Hora inicial: 00:00:00
hora_inicial = datetime(1, 1, 1, 0, 0, 0)

# Adicionando 10 horas, 7 minutos e 10 segundos
tempo_adicional = timedelta(hours=10, minutes=7, seconds=10)
nova_hora = hora_inicial + tempo_adicional

# Formatando com strftime()
print(f&amp;quot;Nova hora: {nova_hora.strftime(&amp;#39;%H:%M:%S&amp;#39;)}&amp;quot;)
# Saída: Nova hora: 10:07:10

# Adicionando mais 10 segundos
nova_hora += timedelta(seconds=10)

# Formatando diretamente com f-string (sem strftime explícito)
print(f&amp;quot;Nova hora: {nova_hora:%H:%M:%S}&amp;quot;)
# Saída: Nova hora: 10:07:20
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;Microssegundos e milissegundos usando &lt;code&gt;f-string&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Como faço muitos vídeos diariamente, preciso lidar bastante com pequenas frações
de segundos pra sincronizar legendas e transcrições. O formato típico usado nas
legendas SRT (SubRip) é assim: &lt;code&gt;00:00:00,000&lt;/code&gt;, onde &lt;code&gt;000&lt;/code&gt; são milissegundos.&lt;/p&gt;
&lt;p&gt;A lógica é parecida com a que expliquei anteriormente. Veja na prática:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from datetime import datetime

# Criando um tempo base: 00:02:23
tempo_do_video = datetime(1, 1, 1, 0, 2, 23)

# Exibindo o tempo sem microsegundos
print(f&amp;quot;{tempo_do_video:%H:%M:%S}&amp;quot;)
# Saída: 00:02:23
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora vamos adicionar microssegundos e milissegundos usando &lt;code&gt;timedelta&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from datetime import datetime, timedelta

# Tempo base novamente: 00:02:23
tempo_do_video = datetime(1, 1, 1, 0, 2, 23)

# Adicionando 111 microssegundos (devem aparecer nas últimas três casas)
tempo_do_video += timedelta(microseconds=111)
print(f&amp;quot;{tempo_do_video:%H:%M:%S,%f}&amp;quot;)
# Saída: 00:02:23,000111

# Resetando ao valor inicial
tempo_do_video = datetime(1, 1, 1, 0, 2, 23)

# Agora adicionando 111 milissegundos (primeiras três casas após a vírgula)
tempo_do_video += timedelta(milliseconds=111)
print(f&amp;quot;{tempo_do_video:%H:%M:%S,%f}&amp;quot;)
# Saída: 00:02:23,111000

# Exibindo apenas milissegundos, removendo os três últimos dígitos com fatiamento
print(f&amp;quot;{tempo_do_video:%H:%M:%S,%f}&amp;quot;[:-3])
# Saída: 00:02:23,111
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O que acontece nesse trecho &lt;code&gt;[:-3]&lt;/code&gt; é o seguinte: estou removendo as últimas 3
posições da string resultante, o que chamamos de
&lt;a href=&quot;https://youtu.be/QxjArQ9xZDg?si=MOxtznr0cdQQw5a1&quot;&gt;fatiamento de strings (slicing)&lt;/a&gt;.
Isso é bastante útil quando precisamos adaptar rapidamente o formato das
strings.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Manipulação de datas usando &lt;code&gt;f-string&lt;/code&gt;, &lt;code&gt;datetime&lt;/code&gt; e &lt;code&gt;timedelta&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Só pra reforçar, a formatação de datas completas usando f-strings é exatamente
igual ao que vimos com horas. Veja alguns exemplos práticos:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import locale
from datetime import datetime

# Configurando o locale para o padrão brasileiro (datas, números, etc.)
locale.setlocale(locale.LC_ALL, &amp;#39;pt_BR&amp;#39;)

# Exemplo de data: 02/12/1987 às 10:59:23
nascimento_joaozinho = datetime(1987, 12, 2, 10, 59, 23)

# Formatando a data completa com f-string
print(f&amp;quot;{nascimento_joaozinho:%d de %B de %Y às %H:%M:%S}&amp;quot;)
# Saída: 02 de Dezembro de 1987 às 10:59:23

# Outro exemplo mais detalhado
print(
    f&amp;quot;Joãozinho nasceu no ano de {nascimento_joaozinho:%y}. &amp;quot;
    f&amp;quot;Estávamos no mês de {nascimento_joaozinho:%B}, &amp;quot;
    f&amp;quot;dia {nascimento_joaozinho:%d}. &amp;quot;
    f&amp;quot;Era um dia chuvoso, e o relógio marcava &amp;quot;
    f&amp;quot;{nascimento_joaozinho:%H horas e %M minutos}.&amp;quot;
)
# Saída:
# Joãozinho nasceu no ano de 87.
# Estávamos no mês de Dezembro, dia 02.
# Era um dia chuvoso, e o relógio marcava 10 horas e 59 minutos.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok, talvez eu tenha exagerado nesse último exemplo... 😅 Mas acho que você sacou
a ideia e provavelmente é isso que você vai usar no dia a dia.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Representação de objetos com f-strings: &lt;code&gt;!r&lt;/code&gt;, &lt;code&gt;!s&lt;/code&gt;, &lt;code&gt;!a&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Dentro das f-strings você pode exibir a representação de um objeto usando os
sufixos &lt;code&gt;!s&lt;/code&gt;, &lt;code&gt;!r&lt;/code&gt; ou &lt;code&gt;!a&lt;/code&gt;. Cada um chama respectivamente as funções internas do
Python: &lt;code&gt;str()&lt;/code&gt;, &lt;code&gt;repr()&lt;/code&gt; e &lt;code&gt;ascii()&lt;/code&gt; sobre o valor exibido.&lt;/p&gt;
&lt;p&gt;Para entender melhor, vamos criar uma classe simples e ver esses métodos em
ação:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Pessoa:
    def __init__(self, nome: str, sobrenome: str, idade: int) -&amp;gt; None:
        self.nome = nome
        self.sobrenome = sobrenome
        self.idade = idade

    def __repr__(self) -&amp;gt; str:
        cls_name = self.__class__.__name__
        attrs = &amp;quot;, &amp;quot;.join(
            f&amp;quot;{chave}={valor!r}&amp;quot; for chave, valor in self.__dict__.items()
        )
        return f&amp;quot;{cls_name}({attrs})&amp;quot;

    def __str__(self) -&amp;gt; str:
        return f&amp;quot;{self.nome} {self.sobrenome}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Usando &lt;code&gt;!s&lt;/code&gt; — chama o método &lt;code&gt;__str__&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;pessoa = Pessoa(&amp;quot;Luiz Otávio&amp;quot;, &amp;quot;Miranda&amp;quot;, 30)

print(f&amp;quot;{pessoa}&amp;quot;)    # Saída: Luiz Otávio Miranda
print(f&amp;quot;{pessoa!s}&amp;quot;)  # Mesma saída acima; !s chama explicitamente str()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aqui o Python usa o método &lt;code&gt;__str__&lt;/code&gt;, gerando uma versão amigável e legível para
humanos.&lt;/p&gt;
&lt;h3&gt;Usando &lt;code&gt;!r&lt;/code&gt; — chama o método &lt;code&gt;__repr__&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;print(f&amp;quot;{pessoa!r}&amp;quot;)
# Saída: Pessoa(nome=&amp;#39;Luiz Otávio&amp;#39;, sobrenome=&amp;#39;Miranda&amp;#39;, idade=30)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora o Python usa o método &lt;code&gt;__repr__&lt;/code&gt;, que gera uma string detalhada e
idealmente útil para recriar o objeto.&lt;/p&gt;
&lt;h3&gt;Usando &lt;code&gt;!a&lt;/code&gt; — chama o método &lt;code&gt;ascii()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;O método &lt;code&gt;ascii()&lt;/code&gt; funciona como &lt;code&gt;repr()&lt;/code&gt;, porém escapa caracteres não-ASCII,
substituindo-os por sequências especiais (&lt;code&gt;\x&lt;/code&gt;, &lt;code&gt;\u&lt;/code&gt; ou &lt;code&gt;\U&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;print(f&amp;quot;{pessoa!a}&amp;quot;)
# Saída: Pessoa(nome=&amp;#39;Luiz Ot\xe1vio&amp;#39;, sobrenome=&amp;#39;Miranda&amp;#39;, idade=30)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note que o caractere “á” (fora do padrão ASCII básico) foi convertido para
&lt;code&gt;\xe1&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Explorando escapes: de &lt;code&gt;á&lt;/code&gt; para &lt;code&gt;\xe1&lt;/code&gt; e de volta&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;valor = &amp;quot;á&amp;quot;

print(&amp;quot;\xe1&amp;quot;)        # &amp;#39;\x&amp;#39; + e1 → á
print(&amp;quot;\u00e1&amp;quot;)      # &amp;#39;\u&amp;#39; + 00e1 → á
print(&amp;quot;\U000000e1&amp;quot;)  # &amp;#39;\U&amp;#39; + 000000e1 → á

# De caractere para hexadecimal:
valor_hex = hex(ord(valor))   # &amp;#39;0xe1&amp;#39;

# De hexadecimal para caractere:
print(chr(int(valor_hex, 16)))  # á
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Os primeiros 128 códigos Unicode coincidem com os 128 caracteres ASCII
originais. Como “á” fica no ponto de código 225, ele está fora desse intervalo —
por isso é escapado.&lt;/p&gt;
&lt;p&gt;Para mergulhar mais fundo nesse assunto, confira o artigo
&lt;a href=&quot;https://otaviomiranda.com.br/2020/normalizacao-unicode-em-python/&quot;&gt;normalização Unicode em Python&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Padding e Alinhamento de Strings e Números com f-strings&lt;/h2&gt;
&lt;p&gt;F-strings oferecem controle preciso sobre o preenchimento (padding) e o
alinhamento do texto dentro de um espaço definido. Isso é feito utilizando
especificadores de formatação dentro das chaves &lt;code&gt;{}&lt;/code&gt;, após os dois pontos &lt;code&gt;:&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A sintaxe geral de formatação é &lt;code&gt;:{fill}{align}{width}&lt;/code&gt;, onde:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fill&lt;/code&gt; (opcional): O caractere a ser usado para preencher o espaço. Se
omitido, o padrão é espaço (&lt;code&gt; &lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;align&lt;/code&gt; (opcional): O tipo de alinhamento.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;&lt;/code&gt;: Alinha o texto à esquerda (padrão para strings).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;gt;&lt;/code&gt;: Alinha o texto à direita (padrão para números).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;^&lt;/code&gt;: Centraliza o texto.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;width&lt;/code&gt;: A largura total mínima do campo. O texto será preenchido até atingir
essa largura.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vamos ver alguns exemplos práticos de como aplicar esses especificadores:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;nome = &amp;quot;Python&amp;quot; # Alterado para &amp;quot;Python&amp;quot; para um exemplo mais genérico

# --- Alinhamento com Espaços (padding padrão) ---
print(f&amp;quot;&amp;#39;{nome:&amp;lt;15}&amp;#39;&amp;quot;) # Aspas para visualizar o padding
# Alinhamento à esquerda (padrão para strings), preenchido com espaços até 15 caracteres.
# Saída: &amp;#39;Python         &amp;#39;

print(f&amp;quot;&amp;#39;{nome:&amp;gt;15}&amp;#39;&amp;quot;)
# Alinhamento à direita, preenchido com espaços até 15 caracteres.
# Saída: &amp;#39;         Python&amp;#39;

print(f&amp;quot;&amp;#39;{nome:^15}&amp;#39;&amp;quot;)
# Alinhamento ao centro, preenchido com espaços até 15 caracteres.
# Saída: &amp;#39;    Python     &amp;#39;

# Para números, o preenchimento com &amp;#39;0&amp;#39; à esquerda é comum
numero_item = 7
print(f&amp;quot;{numero_item:03}&amp;quot;)
# Preenchimento com &amp;#39;0&amp;#39; à esquerda até 3 caracteres (útil para numeração).
# Saída: 007

# Você pode usar qualquer caractere para preencher. O caractere de
# preenchimento vem ANTES do alinhamento.
produto_id = &amp;quot;ABC&amp;quot;
print(f&amp;quot;ID: {produto_id:x&amp;lt;10}&amp;quot;)
# Preenche com &amp;#39;x&amp;#39; e alinha à esquerda
# Saída: ID: ABCxxxxxxx

preco = 123.45
print(f&amp;quot;Preço: {preco:*&amp;gt;12.2f}&amp;quot;)
# Preenche com &amp;#39;*&amp;#39; e alinha à direita, 2 casas decimais
# Saída: Preço: ******123.45

# Numeração de arquivos com zeros à esquerda (ex: 000001.mp4)
for i in range(3):
    print(f&amp;quot;{i:06}.mp4&amp;quot;)
# Saída:
# 000000.mp4
# 000001.mp4
# 000002.mp4

# Alinhamento e preenchimento combinado para um formato específico
valor = 1048576
print(f&amp;quot;Resultado: {valor:!&amp;lt;20}&amp;quot;)
# Preenchimento com &amp;#39;!&amp;#39; à esquerda, alinhamento à esquerda, largura total de 20.
# Saída: Resultado: 1048576!!!!!!!!!!!!!
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Inserindo expressões e sinal de igual (&lt;code&gt;=&lt;/code&gt;) em f-strings&lt;/h2&gt;
&lt;p&gt;As f-strings permitem inserir diretamente expressões Python no texto e, melhor
ainda, exibir automaticamente a expressão seguida do seu resultado usando o
sinal de igual (&lt;code&gt;=&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Por exemplo, ao invés de fazer algo assim para debug:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;print(f&amp;quot;variavel = {variavel}&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;você pode simplificar para:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;print(f&amp;quot;{variavel = }&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O resultado será exatamente o mesmo, exibindo tanto a expressão quanto o valor
atribuído. Veja vários exemplos abaixo (não esqueça de ler os comentários!):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;x = 3.55
y = 7.12

# Forma tradicional, sem o sinal de igual automático:
print(f&amp;quot;{x} + {y} = {x + y}&amp;quot;)  # Saída: 3.55 + 7.12 = 10.67

# Forma simplificada usando &amp;quot;=&amp;quot; dentro das chaves (ideal para debug)
print(f&amp;quot;{x = }, {y = }, {x + y = }&amp;quot;)
# Saída: x = 3.55, y = 7.12, x + y = 10.67

# Usando expressões com literais diretamente:
print(f&amp;quot;5 * 2 = {5 * 2}&amp;quot;)  # Saída: 5 * 2 = 10

# Exibindo o valor de uma variável diretamente:
firstName = &amp;quot;Luiz Otávio&amp;quot;
print(f&amp;quot;{firstName}&amp;quot;)      # Saída: Luiz Otávio

# Exibindo com o sinal de igual:
print(f&amp;quot;{firstName = }&amp;quot;)   # Saída: firstName = &amp;#39;Luiz Otávio&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;Conclusão&lt;/h3&gt;
&lt;p&gt;Nesse artigo, exploramos várias formas poderosas de usar as f-strings em Python.
Começamos com concatenação básica, passamos pela formatação numérica avançada
(controle de casas decimais, separadores de milhares), manipulamos datas e
horas, representamos objetos, e terminamos mostrando a avaliação direta de
expressões com o prático sinal de igual (&lt;code&gt;=&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Dominar f-strings é fundamental pra você, que busca escrever código Python mais
limpo, direto e eficiente, especialmente na hora de manipular textos e dados.&lt;/p&gt;
&lt;p&gt;Se você chegou até aqui, espero de verdade que tenha aprendido coisas novas.
Mesmo eu trabalhando há anos com Python e programação, ainda aprendo muita coisa
enquanto escrevo esses textos e gravo meus vídeos.&lt;/p&gt;
&lt;p&gt;Até o próximo artigo! ✌️&lt;/p&gt;
&lt;hr&gt;
</content:encoded></item><item><title>Transcreva áudio com Python: Sussu CLI + OpenAI Whisper</title><link>https://otaviomiranda.com.br/2025/python-sussu-cli-openai-whisper/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2025/python-sussu-cli-openai-whisper/</guid><description>Aprenda a usar o Sussu, uma ferramenta de linha de comando feita em Python que utiliza o modelo Whisper da OpenAI para transcrever áudios e vídeos de forma simples e eficiente.</description><pubDate>Sat, 25 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Essa é uma ferramenta de linha de comando focada em educação e IA offline.
Utiliza o poder do Whisper da OpenAI para transcrever áudios e vídeos de forma
simples e eficiente.&lt;/p&gt;
&lt;p&gt;Esses são todos os links que menciono nos dois vídeos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;whisper em tempo real via microfone -
&lt;a href=&quot;https://otaviomiranda.com.br/2025/whisper-live-sera-que-deu-certo/&quot;&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;logging — Logging facility for Python -
&lt;a href=&quot;https://docs.python.org/3/library/logging.html&quot;&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;sussu(rro): CLI educacional com OpenAI Whisper -
&lt;a href=&quot;https://otaviomiranda.com.br/2025/python-sussu-cli-openai-whisper/&quot;&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;openai / whisper - &lt;a href=&quot;https://github.com/openai/whisper&quot;&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vídeos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vídeo da parte 1 -
&lt;a href=&quot;https://youtu.be/y15070biffg&quot;&gt;Guia Whisper OpenAI Completo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vídeo da Parte 2 -
&lt;a href=&quot;https://youtu.be/SAt7yuO8DX4&quot;&gt;Whisper e Sussu no seu projeto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Sobre o Whisper e a OpenAI&lt;/h2&gt;
&lt;p&gt;Estava curioso para saber se a OpenAI usa o Whisper em seus próprios produtos,
então questionei o ChatGPT...&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Eu:&lt;/strong&gt; A OpenAI usa o Whisper internamente em algum recurso que as pessoas
consomem? &lt;br&gt;&lt;strong&gt;ChatGPT:&lt;/strong&gt; Sim, a OpenAI usa o Whisper como base para recursos de
transcrição e compreensão de áudio em produtos como o ChatGPT com entrada de
voz. Ele também é utilizado em APIs que envolvem reconhecimento de fala, como
a &lt;code&gt;speech-to-text&lt;/code&gt; da OpenAI.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;Sobre este documento:&lt;/h2&gt;
&lt;p&gt;Estou tentando manter este documento (&lt;code&gt;README.md&lt;/code&gt;) sincronizado com o link
abaixo. Caso não encontre o que procura aqui, acesse:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://otaviomiranda.com.br/2025/python-sussu-cli-openai-whisper/&quot;&gt;Transcreva áudio com Python: Sussu CLI + OpenAI Whisper&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Introdução geral&lt;/h2&gt;
&lt;p&gt;Ao rodar este projeto, uma das primeiras coisas que você vai querer fazer é usar
o comando &lt;code&gt;whisper&lt;/code&gt; para fazer a transcrição inicial de algum vídeo ou áudio.
Essa transcrição é um ótimo jeito de ver na prática como o &lt;strong&gt;Whisper&lt;/strong&gt; trabalha
e o que esperar dos resultados.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openai/whisper&quot;&gt;Repositório oficial do &lt;code&gt;whisper&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/luizomf/sussu&quot;&gt;Repositório do &lt;code&gt;sussu&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vamos começar pela &lt;strong&gt;instalação&lt;/strong&gt; do projeto, isso já coloca os comandos &lt;code&gt;sussu&lt;/code&gt;
e &lt;code&gt;whisper&lt;/code&gt; funcionando direto no seu terminal.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Instalação do &lt;code&gt;sussu&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Se você encontrar alguma dificuldade com o ambiente, recomendo meu tutorial
completo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=HuAc85cLRx0&quot;&gt;Ambiente Python Moderno 2025: UV, Ruff, Pyright, pyproject.toml e VS Code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Este projeto utiliza o &lt;strong&gt;Python 3.11.9&lt;/strong&gt; por questões de compatibilidade com o
&lt;strong&gt;Whisper&lt;/strong&gt;. Evite alterar essa versão se não souber o que está fazendo, pois
&lt;strong&gt;eu já testei tudo para você&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Além disso, este projeto usa o &lt;a href=&quot;https://docs.astral.sh/uv/&quot;&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/a&gt; para o
gerenciamento geral (pacotes, versão do Python, etc.).&lt;/p&gt;
&lt;p&gt;Para instalar tudo, basta rodar o comando:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Se ainda não clonou o repositório
git clone https://github.com/luizomf/sussu.git
# Acesse a pasta do projeto
cd sussu
# Sincronizando
uv sync
# é só isso mesmo 😅
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uv sync&lt;/code&gt; é suficiente para:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Baixar e instalar o &lt;code&gt;python 3.11.9&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Criar o ambiente virtual em &lt;code&gt;.venv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Instalar os pacotes necessários&lt;/li&gt;
&lt;li&gt;Buildar o &lt;code&gt;whisper&lt;/code&gt; e o &lt;code&gt;sussu&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code&gt;ffmpeg&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Você também precisará ter o &lt;strong&gt;&lt;code&gt;ffmpeg&lt;/code&gt;&lt;/strong&gt; instalado. Ele é um software de código
aberto com várias ferramentas e bibliotecas para trabalhar com arquivos
multimídia, especialmente áudio e vídeo. Embora o &lt;code&gt;whisper&lt;/code&gt; foque na transcrição
de áudio, o &lt;code&gt;ffmpeg&lt;/code&gt; é quem permite que você transcreva seus vídeos diretamente,
sem precisar convertê-los para áudio antes.&lt;/p&gt;
&lt;p&gt;Para instalar o &lt;code&gt;ffmpeg&lt;/code&gt; no seu sistema, você pode usar um dos comandos abaixo.
Eles foram retirados diretamente do
&lt;a href=&quot;https://github.com/openai/whisper&quot;&gt;repositório oficial do &lt;code&gt;whisper&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# No Ubuntu ou Debian
sudo apt update &amp;amp;&amp;amp; sudo apt install ffmpeg

# No Arch Linux
sudo pacman -S ffmpeg

# No macOS com Homebrew (https://brew.sh/)
brew install ffmpeg

# No Windows com Chocolatey (https://chocolatey.org/)
choco install ffmpeg

# No Windows usando Scoop (https://scoop.sh/)
scoop install ffmpeg

# Adicional: No Windows usando winget (https://winstall.app/apps/Gyan.FFmpeg)
winget install --id=Gyan.FFmpeg -e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Observação:&lt;/strong&gt; Dos comandos listados, os únicos que testei e aprovei (✅) foram
os para &lt;strong&gt;macOS&lt;/strong&gt; e &lt;strong&gt;Ubuntu&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Rodando pela Primeira Vez&lt;/h2&gt;
&lt;p&gt;Para verificar se tudo foi instalado corretamente, você tem duas opções:
&lt;strong&gt;ativar o ambiente virtual&lt;/strong&gt; ou usar o comando &lt;strong&gt;&lt;code&gt;uv run&lt;/code&gt;&lt;/strong&gt;. Sugiro que você
teste com &lt;code&gt;whisper -h&lt;/code&gt;. Esse comando deve exibir a ajuda completa do &lt;code&gt;whisper&lt;/code&gt;,
indicando que ele está funcionando. Veja os exemplos:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run whisper -h
# Ou, se você já ativou o ambiente virtual
whisper -h
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Observação:&lt;/strong&gt; Editores de código como &lt;strong&gt;VS Code&lt;/strong&gt; ou &lt;strong&gt;Zed&lt;/strong&gt; podem ativar o
ambiente virtual automaticamente ao abrir um novo terminal, desde que estejam
configurados corretamente. Se for o seu caso, basta fechar e abrir o terminal
novamente para que as mudanças façam efeito.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code&gt;whisper -h&lt;/code&gt;: Entendendo Alguns Argumentos Importantes&lt;/h2&gt;
&lt;p&gt;Ao digitar &lt;code&gt;whisper -h&lt;/code&gt; ou &lt;code&gt;whisper --help&lt;/code&gt;, você pode se surpreender com a
quantidade de argumentos disponíveis. Mas não se preocupe! Você não precisa
saber o que cada um deles faz. Na verdade, a maioria dos argumentos já vem com
valores padrão que funcionam perfeitamente. No entanto, se você quiser
personalizar um pouco o comportamento da ferramenta, vamos analisar alguns dos
mais importantes.&lt;/p&gt;
&lt;p&gt;O &lt;code&gt;whisper&lt;/code&gt; utiliza a biblioteca &lt;code&gt;argparse&lt;/code&gt; do Python para gerar essa
documentação de ajuda (&lt;code&gt;help&lt;/code&gt;) completa e bem organizada. Se você tiver
interesse em aprender mais sobre como criar interfaces de linha de comando
profissionais com Python, confira meu vídeo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Ad6934NXn4A&quot;&gt;Python e argparse: Do Zero a uma CLI Profissional (Projeto Real na Prática)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Argumentos Essenciais do &lt;code&gt;whisper&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Vamos começar com os argumentos que você usará com mais frequência:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;audio&lt;/code&gt;&lt;/strong&gt;: Este é o &lt;strong&gt;argumento posicional&lt;/strong&gt; principal. Ele representa o
caminho completo (localização) do arquivo de áudio ou vídeo que você quer
transcrever.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemplo:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;whisper /caminho/do/seu/arquivo.mp4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No exemplo acima, você notou que especificamos apenas o caminho do arquivo de
vídeo. Nas próximas seções, vou detalhar as opções que mais utilizo para
personalizar a transcrição.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--model MODEL&lt;/code&gt;&lt;/strong&gt;: Este argumento serve para &lt;strong&gt;definir qual modelo será usado
na transcrição&lt;/strong&gt; do seu áudio ou vídeo. Ele é opcional, e o valor padrão é
&lt;code&gt;turbo&lt;/code&gt;. O modelo &lt;code&gt;turbo&lt;/code&gt; é excelente: rápido e multilíngue, mas requer cerca de
&lt;strong&gt;6GB de VRAM&lt;/strong&gt; para rodar.&lt;/p&gt;
&lt;p&gt;Talvez você queira usar outros modelos que exigem mais ou menos recursos do seu
hardware, ou que possuem mais ou menos parâmetros (como &lt;code&gt;base&lt;/code&gt;, &lt;code&gt;small&lt;/code&gt;,
&lt;code&gt;medium&lt;/code&gt;, etc.).&lt;/p&gt;
&lt;p&gt;Aqui estão os modelos disponíveis e seus requisitos aproximados de VRAM:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;tiny&lt;/code&gt;&lt;/strong&gt;: 39M parâmetros, &lt;code&gt;tiny.en&lt;/code&gt; e &lt;code&gt;tiny&lt;/code&gt;, VRAM ~1 GB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;base&lt;/code&gt;&lt;/strong&gt;: 74M parâmetros, &lt;code&gt;base.en&lt;/code&gt; e &lt;code&gt;base&lt;/code&gt;, VRAM ~1 GB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;small&lt;/code&gt;&lt;/strong&gt;: 244M parâmetros, &lt;code&gt;small.en&lt;/code&gt; e &lt;code&gt;small&lt;/code&gt;, VRAM ~2 GB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;medium&lt;/code&gt;&lt;/strong&gt;: 769M parâmetros, &lt;code&gt;medium.en&lt;/code&gt; e &lt;code&gt;medium&lt;/code&gt;, VRAM ~5 GB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;large&lt;/code&gt;&lt;/strong&gt;: 1550M parâmetros, &lt;code&gt;large&lt;/code&gt;, &lt;code&gt;large-v2&lt;/code&gt; e &lt;code&gt;large-v3&lt;/code&gt;, VRAM ~10 GB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;turbo&lt;/code&gt;&lt;/strong&gt;: 809M parâmetros, &lt;code&gt;turbo&lt;/code&gt;, VRAM ~6 GB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;VRAM&lt;/strong&gt; é um tipo de memória RAM especializada que as placas de vídeo (GPUs)
usam. Mas não se preocupe se você não tiver uma placa de vídeo dedicada! Se seu
computador compartilha a RAM com a GPU, o que acontece em Macs com chips Apple
Silicon (M1, M2, M3 e posteriores), por exemplo, você conseguirá usar os modelos
do Whisper normalmente.&lt;/p&gt;
&lt;p&gt;Nesses casos, o que realmente limita é a &lt;strong&gt;quantidade total de memória RAM
disponível no seu sistema&lt;/strong&gt;. Por exemplo: se você tem apenas 8GB de RAM, o ideal
é testar os modelos &lt;code&gt;tiny&lt;/code&gt;, &lt;code&gt;base&lt;/code&gt; ou &lt;code&gt;small&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A partir do modelo &lt;code&gt;medium&lt;/code&gt;, é bem provável que você perceba uma &lt;strong&gt;queda
drástica no desempenho geral da sua máquina&lt;/strong&gt;, já que a memória será
completamente consumida.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemplo:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;whisper /caminho/do/seu/arquivo.mp4 --model large-v2
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--device DEVICE&lt;/code&gt;&lt;/strong&gt;: Este argumento é para você que possui uma &lt;strong&gt;placa de
vídeo NVIDIA com drivers CUDA&lt;/strong&gt; e uma versão compatível com o PyTorch. Se for o
seu caso, vale a pena usar &lt;code&gt;--device cuda&lt;/code&gt; para aproveitar o processamento da
GPU. Caso contrário, não se preocupe em alterar esta opção, o padrão é &lt;code&gt;cpu&lt;/code&gt;
(processamento pela CPU) e funcionará perfeitamente.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemplo:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;whisper /caminho/do/seu/arquivo.mp4 --model large-v2 --device cpu
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--output_dir&lt;/code&gt; ou &lt;code&gt;-o&lt;/code&gt;&lt;/strong&gt;: Define o &lt;strong&gt;caminho da pasta onde as transcrições
serão salvas&lt;/strong&gt;. Por padrão, os arquivos serão salvos na raiz do projeto (&lt;code&gt;.&lt;/code&gt;).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--output_format&lt;/code&gt; ou &lt;code&gt;-f&lt;/code&gt;&lt;/strong&gt;: Permite que você escolha o &lt;strong&gt;formato da
transcrição ou legenda&lt;/strong&gt; gerada. As opções disponíveis são: &lt;code&gt;txt&lt;/code&gt;, &lt;code&gt;vtt&lt;/code&gt;, &lt;code&gt;srt&lt;/code&gt;,
&lt;code&gt;tsv&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt; e &lt;code&gt;all&lt;/code&gt; (que gera todos os formatos). O padrão é &lt;code&gt;all&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Exemplo:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;O arquivo de saída será &lt;code&gt;srt&lt;/code&gt; (SubRip) na pasta indicada em &lt;code&gt;-o&lt;/code&gt;. Essa pasta
será criada caso não exista.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;whisper /caminho/do/seu/arquivo.mp4 --model turbo -o caminho/da/pasta_de_saida -f srt
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--task&lt;/code&gt;&lt;/strong&gt;: Com este argumento, você pode escolher entre &lt;strong&gt;transcrever o
áudio&lt;/strong&gt; no idioma original ou &lt;strong&gt;traduzir para o inglês&lt;/strong&gt;. As opções são
&lt;code&gt;transcribe&lt;/code&gt; (o padrão, que transcreve no idioma falado no áudio) ou &lt;code&gt;translate&lt;/code&gt;
(que traduz o conteúdo para o inglês).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemplo:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;whisper /caminho/do/seu/arquivo.mp4 --model turbo --task transcribe
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--language&lt;/code&gt;&lt;/strong&gt;: Este argumento permite que você &lt;strong&gt;especifique o idioma falado
no áudio ou vídeo&lt;/strong&gt;. Existem muitas opções de idiomas disponíveis. Se você não
informar esse argumento, o &lt;code&gt;whisper&lt;/code&gt; é inteligente o suficiente para detectar
automaticamente o idioma do conteúdo.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Forma curta (language code):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;[&amp;quot;af&amp;quot;, &amp;quot;am&amp;quot;, &amp;quot;ar&amp;quot;, &amp;quot;as&amp;quot;, &amp;quot;az&amp;quot;, &amp;quot;ba&amp;quot;, &amp;quot;be&amp;quot;, &amp;quot;bg&amp;quot;, &amp;quot;bn&amp;quot;, &amp;quot;bo&amp;quot;, &amp;quot;br&amp;quot;, &amp;quot;bs&amp;quot;, &amp;quot;ca&amp;quot;,
&amp;quot;cs&amp;quot;, &amp;quot;cy&amp;quot;, &amp;quot;da&amp;quot;, &amp;quot;de&amp;quot;, &amp;quot;el&amp;quot;, &amp;quot;en&amp;quot;, &amp;quot;es&amp;quot;, &amp;quot;et&amp;quot;, &amp;quot;eu&amp;quot;, &amp;quot;fa&amp;quot;, &amp;quot;fi&amp;quot;, &amp;quot;fo&amp;quot;, &amp;quot;fr&amp;quot;,
&amp;quot;gl&amp;quot;, &amp;quot;gu&amp;quot;, &amp;quot;ha&amp;quot;, &amp;quot;haw&amp;quot;, &amp;quot;he&amp;quot;, &amp;quot;hi&amp;quot;, &amp;quot;hr&amp;quot;, &amp;quot;ht&amp;quot;, &amp;quot;hu&amp;quot;, &amp;quot;hy&amp;quot;, &amp;quot;id&amp;quot;, &amp;quot;is&amp;quot;, &amp;quot;it&amp;quot;,
&amp;quot;ja&amp;quot;, &amp;quot;jw&amp;quot;, &amp;quot;ka&amp;quot;, &amp;quot;kk&amp;quot;, &amp;quot;km&amp;quot;, &amp;quot;kn&amp;quot;, &amp;quot;ko&amp;quot;, &amp;quot;la&amp;quot;, &amp;quot;lb&amp;quot;, &amp;quot;ln&amp;quot;, &amp;quot;lo&amp;quot;, &amp;quot;lt&amp;quot;, &amp;quot;lv&amp;quot;,
&amp;quot;mg&amp;quot;, &amp;quot;mi&amp;quot;, &amp;quot;mk&amp;quot;, &amp;quot;ml&amp;quot;, &amp;quot;mn&amp;quot;, &amp;quot;mr&amp;quot;, &amp;quot;ms&amp;quot;, &amp;quot;mt&amp;quot;, &amp;quot;my&amp;quot;, &amp;quot;ne&amp;quot;, &amp;quot;nl&amp;quot;, &amp;quot;nn&amp;quot;, &amp;quot;no&amp;quot;,
&amp;quot;oc&amp;quot;, &amp;quot;pa&amp;quot;, &amp;quot;pl&amp;quot;, &amp;quot;ps&amp;quot;, &amp;quot;pt&amp;quot;, &amp;quot;ro&amp;quot;, &amp;quot;ru&amp;quot;, &amp;quot;sa&amp;quot;, &amp;quot;sd&amp;quot;, &amp;quot;si&amp;quot;, &amp;quot;sk&amp;quot;, &amp;quot;sl&amp;quot;, &amp;quot;sn&amp;quot;,
&amp;quot;so&amp;quot;, &amp;quot;sq&amp;quot;, &amp;quot;sr&amp;quot;, &amp;quot;su&amp;quot;, &amp;quot;sv&amp;quot;, &amp;quot;sw&amp;quot;, &amp;quot;ta&amp;quot;, &amp;quot;te&amp;quot;, &amp;quot;tg&amp;quot;, &amp;quot;th&amp;quot;, &amp;quot;tk&amp;quot;, &amp;quot;tl&amp;quot;, &amp;quot;tr&amp;quot;,
&amp;quot;tt&amp;quot;, &amp;quot;uk&amp;quot;, &amp;quot;ur&amp;quot;, &amp;quot;uz&amp;quot;, &amp;quot;vi&amp;quot;, &amp;quot;yi&amp;quot;, &amp;quot;yo&amp;quot;, &amp;quot;yue&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;zh&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Exemplo para português do Brasil: &lt;code&gt;--language pt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;Forma longa (language name):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;[&amp;quot;Afrikaans&amp;quot;, &amp;quot;Albanian&amp;quot;, &amp;quot;Amharic&amp;quot;, &amp;quot;Arabic&amp;quot;, &amp;quot;Armenian&amp;quot;, &amp;quot;Assamese&amp;quot;,
&amp;quot;Azerbaijani&amp;quot;, &amp;quot;Bashkir&amp;quot;, &amp;quot;Basque&amp;quot;, &amp;quot;Belarusian&amp;quot;, &amp;quot;Bengali&amp;quot;, &amp;quot;Bosnian&amp;quot;,
&amp;quot;Breton&amp;quot;, &amp;quot;Bulgarian&amp;quot;, &amp;quot;Burmese&amp;quot;, &amp;quot;Cantonese&amp;quot;, &amp;quot;Castilian&amp;quot;, &amp;quot;Catalan&amp;quot;,
&amp;quot;Chinese&amp;quot;, &amp;quot;Croatian&amp;quot;, &amp;quot;Czech&amp;quot;, &amp;quot;Danish&amp;quot;, &amp;quot;Dutch&amp;quot;, &amp;quot;English&amp;quot;, &amp;quot;Estonian&amp;quot;,
&amp;quot;Faroese&amp;quot;, &amp;quot;Finnish&amp;quot;, &amp;quot;Flemish&amp;quot;, &amp;quot;French&amp;quot;, &amp;quot;Galician&amp;quot;, &amp;quot;Georgian&amp;quot;, &amp;quot;German&amp;quot;,
&amp;quot;Greek&amp;quot;, &amp;quot;Gujarati&amp;quot;, &amp;quot;Haitian&amp;quot;, &amp;quot;Haitian Creole&amp;quot;, &amp;quot;Hausa&amp;quot;, &amp;quot;Hawaiian&amp;quot;, &amp;quot;Hebrew&amp;quot;,
&amp;quot;Hindi&amp;quot;, &amp;quot;Hungarian&amp;quot;, &amp;quot;Icelandic&amp;quot;, &amp;quot;Indonesian&amp;quot;, &amp;quot;Italian&amp;quot;, &amp;quot;Japanese&amp;quot;,
&amp;quot;Javanese&amp;quot;, &amp;quot;Kannada&amp;quot;, &amp;quot;Kazakh&amp;quot;, &amp;quot;Khmer&amp;quot;, &amp;quot;Korean&amp;quot;, &amp;quot;Lao&amp;quot;, &amp;quot;Latin&amp;quot;, &amp;quot;Latvian&amp;quot;,
&amp;quot;Letzeburgesch&amp;quot;, &amp;quot;Lingala&amp;quot;, &amp;quot;Lithuanian&amp;quot;, &amp;quot;Luxembourgish&amp;quot;, &amp;quot;Macedonian&amp;quot;,
&amp;quot;Malagasy&amp;quot;, &amp;quot;Malay&amp;quot;, &amp;quot;Malayalam&amp;quot;, &amp;quot;Maltese&amp;quot;, &amp;quot;Mandarin&amp;quot;, &amp;quot;Maori&amp;quot;, &amp;quot;Marathi&amp;quot;,
&amp;quot;Moldavian&amp;quot;, &amp;quot;Moldovan&amp;quot;, &amp;quot;Mongolian&amp;quot;, &amp;quot;Myanmar&amp;quot;, &amp;quot;Nepali&amp;quot;, &amp;quot;Norwegian&amp;quot;,
&amp;quot;Nynorsk&amp;quot;, &amp;quot;Occitan&amp;quot;, &amp;quot;Panjabi&amp;quot;, &amp;quot;Pashto&amp;quot;, &amp;quot;Persian&amp;quot;, &amp;quot;Polish&amp;quot;, &amp;quot;Portuguese&amp;quot;,
&amp;quot;Punjabi&amp;quot;, &amp;quot;Pushto&amp;quot;, &amp;quot;Romanian&amp;quot;, &amp;quot;Russian&amp;quot;, &amp;quot;Sanskrit&amp;quot;, &amp;quot;Serbian&amp;quot;, &amp;quot;Shona&amp;quot;,
&amp;quot;Sindhi&amp;quot;, &amp;quot;Sinhala&amp;quot;, &amp;quot;Sinhalese&amp;quot;, &amp;quot;Slovak&amp;quot;, &amp;quot;Slovenian&amp;quot;, &amp;quot;Somali&amp;quot;, &amp;quot;Spanish&amp;quot;,
&amp;quot;Sundanese&amp;quot;, &amp;quot;Swahili&amp;quot;, &amp;quot;Swedish&amp;quot;, &amp;quot;Tagalog&amp;quot;, &amp;quot;Tajik&amp;quot;, &amp;quot;Tamil&amp;quot;, &amp;quot;Tatar&amp;quot;,
&amp;quot;Telugu&amp;quot;, &amp;quot;Thai&amp;quot;, &amp;quot;Tibetan&amp;quot;, &amp;quot;Turkish&amp;quot;,&amp;quot;Turkmen&amp;quot;, &amp;quot;Ukrainian&amp;quot;, &amp;quot;Urdu&amp;quot;, &amp;quot;Uzbek&amp;quot;,
&amp;quot;Valencian&amp;quot;, &amp;quot;Vietnamese&amp;quot;, &amp;quot;Welsh&amp;quot;, &amp;quot;Yiddish&amp;quot;, &amp;quot;Yoruba&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Exemplo para português do Brasil: &lt;code&gt;--language Portuguese&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Se precisar de um dicionário completo com todos os idiomas e seus códigos, ele
está disponível em &lt;code&gt;whisper.tokenizer.LANGUAGES&lt;/code&gt; dentro do código do &lt;code&gt;whisper&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemplo:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Para o comando ficar menor, vou manter tudo padrão
# model turbo (padrão)
# task transcribe (padrão)
# etc...
# Idioma falado no vídeo &amp;quot;Português&amp;quot;
whisper /caminho/do/seu/arquivo.mp4 --language pt
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--temperature&lt;/code&gt;:&lt;/strong&gt; controla a &amp;quot;criatividade&amp;quot; do modelo. Vai de &lt;code&gt;0.0&lt;/code&gt; a &lt;code&gt;1.0&lt;/code&gt;.
Quanto mais alto, mais liberdade o modelo tem pra decidir os próximos tokens.
Esse parâmetro interage com &lt;code&gt;--beam_size&lt;/code&gt;, &lt;code&gt;--patience&lt;/code&gt; e &lt;code&gt;--best_of&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--beam_size&lt;/code&gt;:&lt;/strong&gt; número de hipóteses que o modelo mantém em paralelo. Pensa
como se ele testasse vários caminhos ao mesmo tempo e no fim escolhesse o
melhor. O padrão é &lt;code&gt;5&lt;/code&gt; e &lt;strong&gt;só funciona se &lt;code&gt;--temperature == 0.0&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--patience&lt;/code&gt;:&lt;/strong&gt; fator de tolerância que faz o modelo continuar explorando
novas hipóteses mesmo depois de achar uma aceitável. Requer
&lt;code&gt;--temperature == 0.0&lt;/code&gt; e &lt;code&gt;--beam_size &amp;gt; 1&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--best_of&lt;/code&gt;:&lt;/strong&gt; número de amostras diferentes geradas antes de escolher a
melhor. Funciona apenas quando &lt;code&gt;--temperature &amp;gt; 0.0&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Cola rápida:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- temperature &amp;gt; 0 → usa sampling
  ✅ --best_of 5 (5 amostras)
  🔴 --beam_size (ignorado)
  🔴 --patience (ignorado)

- temperature == 0 → usa beam search
  ✅ --beam_size 5 (5 hipóteses)
  ✅ --patience 2 (2 x 5 = 10 hipóteses)
  🔴 --best_of (ignorado)

- temperature == 0 → greedy
  ✅ --beam_size 1 (1 hipótese)
  🔴 --patience (não faz diferença)
  🔴 --best_of (ignorado)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Importante:&lt;/strong&gt; Quanto maiores os valores de &lt;code&gt;--beam_size&lt;/code&gt;, &lt;code&gt;--patience&lt;/code&gt; e
&lt;code&gt;--best_of&lt;/code&gt;, mais lento e &amp;quot;indeciso&amp;quot; o modelo tende a ficar. Isso acontece
porque ele precisa gerar mais hipóteses ou amostras e, em seguida, tomar uma
decisão entre elas. Faça testes rápidos para confirmar esse comportamento.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Observação sincera:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Na prática, o modelo vai responder como foi treinado, independente do seu
capricho nas configs. Trocar &lt;code&gt;temperature&lt;/code&gt;, &lt;code&gt;beam_size&lt;/code&gt;, &lt;code&gt;patience&lt;/code&gt; e afins pode
virar desperdício de tempo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Recomendação direta:&lt;/strong&gt; só mexa nessas opções se:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;o modelo começar a repetir palavras (loop)&lt;/li&gt;
&lt;li&gt;estiver errando demais em blocos grandes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Se for só por causa de uma ou duas palavras... aceita e segue. Ou então faz
igual eu: &lt;strong&gt;testa tudo por uma semana e conclui que o padrão já era bom&lt;/strong&gt; 😅&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemplo:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;O arquivo de saída será &lt;code&gt;srt&lt;/code&gt; (SubRip) na pasta indicada em &lt;code&gt;-o&lt;/code&gt;. Essa pasta
será criada caso não exista.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Greedy: Mais rápido, mas pode errar mais por considerar apenas uma hipótese por vez.
whisper /caminho/do/seu/arquivo.mp4 --temperature 0.0 --beam_size 1

# Beam Search: Utiliza 3 hipóteses em paralelo.
# O &amp;#39;patience&amp;#39; padrão é 1.
whisper /caminho/do/seu/arquivo.mp4 --temperature 0.0 --beam_size 3

# Sampling: Gera 5 amostras diferentes para escolher a melhor.
whisper /caminho/do/seu/arquivo.mp4 --temperature 0.7 --best_of 5
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--temperature_increment_on_fallback&lt;/code&gt;&lt;/strong&gt;: Este argumento permite que você
&lt;strong&gt;aumente a temperatura do modelo em casos de falha na transcrição&lt;/strong&gt;. Se o
modelo encontrar dificuldades na temperatura &lt;code&gt;0.0&lt;/code&gt;, ele fará um &amp;quot;fallback&amp;quot; e
tentará com a temperatura incrementada. O valor também varia de &lt;code&gt;0.0&lt;/code&gt; a &lt;code&gt;1.0&lt;/code&gt;.
No entanto, &lt;strong&gt;cuidado: definir &lt;code&gt;0.0&lt;/code&gt; para este argumento causará um erro
&lt;code&gt;ZeroDivisionError: float division by zero&lt;/code&gt;&lt;/strong&gt; (isso pode ser um pequeno
&amp;quot;bugzinho&amp;quot; 🫣, mas, de fato, não faria muito sentido usar zero aqui, já que o
objetivo é justamente &lt;em&gt;incrementar&lt;/em&gt; a temperatura). O valor padrão é &lt;code&gt;0.2&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--max_line_width&lt;/code&gt;&lt;/strong&gt;: Define a &lt;strong&gt;quantidade máxima de caracteres por linha&lt;/strong&gt;
na sua legenda. O valor padrão é &lt;code&gt;1000&lt;/code&gt; (um limite bastante alto, codificado
diretamente na classe &lt;code&gt;SubtitlesWriter&lt;/code&gt; do &lt;code&gt;whisper&lt;/code&gt;). Eu, particularmente,
costumo usar &lt;code&gt;45&lt;/code&gt; para uma melhor legibilidade. &lt;strong&gt;Importante:&lt;/strong&gt; Se este
argumento for utilizado, ele anula o &lt;code&gt;--max_words_per_line&lt;/code&gt;. &lt;strong&gt;Requer
&lt;code&gt;--word_timestamps True&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--max_line_count&lt;/code&gt;&lt;/strong&gt;: Controla a &lt;strong&gt;quantidade máxima de linhas por legenda&lt;/strong&gt;
(ou &amp;quot;bloco&amp;quot; de texto). Eu uso o valor &lt;code&gt;2&lt;/code&gt;, mas, nos meus testes, percebi que
isso força todas as legendas a terem sempre duas linhas. Para mim, não é um
problema, mas vale a pena você testar para ver como se adapta ao seu caso.
&lt;strong&gt;Requer &lt;code&gt;--word_timestamps True&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--max_words_per_line&lt;/code&gt;&lt;/strong&gt;: Determina a &lt;strong&gt;quantidade máxima de palavras por
linha&lt;/strong&gt; na legenda. O padrão também é um valor alto, &lt;code&gt;1000&lt;/code&gt; (também &amp;quot;hardcoded&amp;quot;
na classe &lt;code&gt;SubtitlesWriter&lt;/code&gt;). Embora eu não costume usá-lo, acredito que &lt;code&gt;5&lt;/code&gt;
palavras por linha pode resultar em uma leitura mais confortável. &lt;strong&gt;Atenção:&lt;/strong&gt;
Será anulado por &lt;code&gt;--max_line_width&lt;/code&gt; caso você use ambos no mesmo comando.
&lt;strong&gt;Requer &lt;code&gt;--word_timestamps True&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--highlight_words&lt;/code&gt;&lt;/strong&gt;: Este é o argumento responsável por criar o &lt;strong&gt;efeito de
&amp;quot;karaokê&amp;quot;&lt;/strong&gt; na sua transcrição. Ele faz com que cada palavra falada seja
sublinhada no momento exato em que é pronunciada. &lt;strong&gt;Requer
&lt;code&gt;--word_timestamps True&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--word_timestamps&lt;/code&gt;&lt;/strong&gt;: Este argumento é a &lt;strong&gt;chave&lt;/strong&gt; para ativar os recursos de
sincronização detalhada. Ao defini-lo como &lt;code&gt;True&lt;/code&gt;, o modelo passará a gerar
&lt;strong&gt;timestamps para cada palavra&lt;/strong&gt;, em vez de apenas por blocos de frase. Isso
pode, sim, aumentar consideravelmente o tempo de transcrição, mas é um requisito
fundamental para que vários outros argumentos (como os de formatação de linha e
destaque de palavras) funcionem. O valor padrão é &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Exemplo Completo de Transcrição Detalhada&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Veja um exemplo de como combinar vários desses argumentos para obter uma
transcrição formatada e com destaque de palavras:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# A &amp;#39;\&amp;#39; (barra invertida no final da linha) é usada apenas para indicar que
# o comando continua na linha de baixo. Isso é uma boa prática para evitar
# que o comando fique muito longo na horizontal e melhora a legibilidade.
whisper meu_video.mp4 \
  --model large-v2 \
  --language pt \
  --output_format srt \
  --word_timestamps True \
  --highlight_words True \
  --max_line_width 45 \
  --max_line_count 2
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--initial_prompt&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Este é um texto opcional que serve como um &lt;strong&gt;&amp;quot;empurrãozinho&amp;quot; para o modelo antes
que ele comece a transcrever&lt;/strong&gt;. Funciona como uma dica de estilo ou contexto. No
entanto, é importante notar que ele só influencia a &lt;strong&gt;primeira &amp;quot;janela&amp;quot; do
áudio&lt;/strong&gt; (que por padrão tem 30 segundos).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemplo Prático:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Se o seu vídeo é sobre programação, especificamente Python, você pode passar um
prompt como este:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;--initial_prompt &amp;quot;vídeo de uma explicação sobre programação com destaque para bibliotecas do Python&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isso pode ajudar o modelo a reconhecer e transcrever termos técnicos de
programação e Python com mais precisão. Mas, como dito, não espere milagres para
o vídeo inteiro; essa influência é apenas um toque inicial. Para as janelas
seguintes, o modelo pode usar o texto transcrito anteriormente, se a opção
&lt;code&gt;--condition_on_previous_text&lt;/code&gt; estiver como &lt;code&gt;True&lt;/code&gt; (que é o padrão).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Analogia para Entender Melhor:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Imagine que é como dizer para um cantor, antes de ele subir no palco: &lt;br&gt;&amp;quot;Tem 300 mil pessoas te esperando, detona lá!&amp;quot; &lt;br&gt;Ele vai subir já no clima certo, mas o resto da performance dependerá do show
em si. &lt;br&gt;Da mesma forma, o modelo continua a transcrição com base no que &amp;quot;ouviu&amp;quot; e
transcreveu depois do prompt inicial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cuidados com o &lt;code&gt;--initial_prompt&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;O &lt;code&gt;--initial_prompt&lt;/code&gt; pode afetar significativamente a forma como o modelo do
&lt;code&gt;whisper&lt;/code&gt; opera. Em alguns casos, ele pode levar à geração de legendas
excessivamente longas ou até mesmo fazer o modelo entrar em &lt;strong&gt;loops de
repetição&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Recomendação:&lt;/strong&gt; Antes de aplicar um prompt em um vídeo completo, faça &lt;strong&gt;testes
rápidos em um trecho menor&lt;/strong&gt; do seu vídeo para observar o resultado. Isso evita
surpresas e economiza tempo de processamento.&lt;/p&gt;
&lt;p&gt;Para cortar facilmente um pedaço do seu vídeo para testes, você pode usar o
&lt;code&gt;ffmpeg&lt;/code&gt; com o seguinte comando:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Com ffmpeg
ffmpeg -i entrada.mp4 -c:v copy -c:a copy -ss 00:05:00.000 -to 00:10:00.000 saida.mp4

# Também dá pra usar --clip_timestamps start, end, start, end... (em segundos)
# O argumento --clip_timestamps é detalhado mais abaixo nesse texto
# No comando abaixo ele transcreve de 1min até 2min (nada mais)
whisper meu_video.mp4 --clip_timestamps 60,120
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Entendendo o Comando &lt;code&gt;ffmpeg&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-i entrada.mp4&lt;/code&gt;: Define o arquivo de vídeo de entrada (o seu vídeo original).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-c:v copy&lt;/code&gt;: Copia o codec de vídeo do arquivo original, sem recodificar. Isso
torna o processo muito mais rápido!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-c:a copy&lt;/code&gt;: Copia o codec de áudio do arquivo original, também sem
recodificar.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-ss 00:05:00.000&lt;/code&gt;: Especifica o ponto de início do corte (neste exemplo, 5
minutos e 0 segundos do vídeo original).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-to 00:10:00.000&lt;/code&gt;: Define o ponto final do corte (neste exemplo, 10 minutos e
0 segundos do vídeo original).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Este comando irá gerar um novo arquivo de vídeo (&lt;code&gt;saida.mp4&lt;/code&gt;) contendo apenas o
segmento entre 00:05:00 e 00:10:00 do vídeo original. Essa técnica é
extremamente útil, especialmente para vídeos mais longos (como os meus de 30+
minutos), pois permite testar configurações específicas em um pedaço pequeno sem
ter que processar o vídeo inteiro.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--condition_on_previous_text&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Este argumento crucial define se &lt;strong&gt;o texto que já foi transcrito será usado como
contexto&lt;/strong&gt; para ajudar a transcrever a próxima &amp;quot;janela&amp;quot; do áudio.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;True&lt;/code&gt; (padrão): É a configuração ideal para a maioria dos casos. Ela ajuda a
manter a &lt;strong&gt;fluidez e a consistência&lt;/strong&gt; do texto, garantindo uma boa coesão
entre os blocos da transcrição.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;False&lt;/code&gt;: Desativa o uso do contexto anterior. Isso pode ser útil para &lt;strong&gt;evitar
&amp;quot;loops de erro&amp;quot;&lt;/strong&gt;, onde o modelo fica repetindo frases ou palavras
indefinidamente.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Exemplo de Uso:&lt;/em&gt; &lt;br&gt;Se a transcrição começar a errar e ficar repetindo, por exemplo,
&lt;code&gt;&amp;quot;Olá, pessoal, hoje vamos falar sobre...&amp;quot;&lt;/code&gt; em loop, desativar este argumento
(&lt;code&gt;--condition_on_previous_text=False&lt;/code&gt;) pode quebrar esse ciclo vicioso.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Recomendações Gerais para Contexto&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Para otimizar suas transcrições, considere as seguintes dicas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Para vídeos &lt;strong&gt;bem gravados&lt;/strong&gt;, com &lt;strong&gt;áudio limpo&lt;/strong&gt; e &lt;strong&gt;sem erros ou repetições
evidentes&lt;/strong&gt;, mantenha o padrão: &lt;code&gt;--condition_on_previous_text=True&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Se o modelo começar a &lt;strong&gt;repetir frases ou palavras&lt;/strong&gt; de forma indesejada,
experimente mudar para &lt;code&gt;--condition_on_previous_text=False&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;--initial_prompt&lt;/code&gt; pode ajudar &lt;strong&gt;somente no início&lt;/strong&gt; da transcrição. Não
espere que ele resolva problemas de consistência para o vídeo inteiro, mas
pode ser útil para guiar o modelo em termos específicos.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Parâmetros que não usei (ou quase não usei 🫣):&lt;/h3&gt;
&lt;p&gt;Esses parâmetros aí de baixo &lt;strong&gt;eu não testei quase nada&lt;/strong&gt; (apenas alguns). Só li
a documentação, pesquei uma ideia geral e traduzi pra você não precisar sofrer.
Se quiser fuçar, fuce, mas vai por sua conta e risco. Pode ser que melhore algo,
pode ser que não mude nada. Vai depender do áudio, da fase da lua e do humor do
modelo 😅.&lt;/p&gt;
&lt;p&gt;Se eu começar a usar alguma dessas opções nas minhas transcrições, prometo que
volto aqui e atualizo esse trecho. Alguns deles eu cheguei a testar de forma
supercifical (explico nos argumentos).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--length_penalty&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Controla a penalização para &lt;em&gt;sequências longas&lt;/em&gt;. Valor típico: entre &lt;code&gt;0.6&lt;/code&gt; e
&lt;code&gt;1.0&lt;/code&gt;. Se você notar que a transcrição tá muito curta ou longa, pode brincar com
isso. Eu não toquei neste argumento.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--suppress_tokens&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Permite suprimir tokens pelo ID. O valor &lt;code&gt;-1&lt;/code&gt; (padrão) já suprime símbolos
esquisitos e só mantém pontuações comuns. Deixa assim, a menos que você saiba o
que está fazendo.&lt;/p&gt;
&lt;p&gt;Exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Isso aqui vai cortar algumas coisas úteis (só exemplo).
# Seu texto não terá: &amp;#39;Olá&amp;#39;, &amp;#39;pessoal&amp;#39;, &amp;#39;,&amp;#39;, &amp;#39;este&amp;#39;, &amp;#39;é&amp;#39;, &amp;#39;meu&amp;#39;, &amp;#39;texto&amp;#39;, &amp;#39;.&amp;#39;
# Obs: texto sem ponto e vírgula fica horrível
whisper /caminho/do/seu/arquivo.mp4 \
    --model turbo \
    --language pt \
    --suppress_tokens=38056,842,24811,11,4065,1136,9230,35503,13
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Quer saber o ID de um token específico?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Seguinte, se você que descobrir algum token para suprimir ou para qualquer outra
coisa, veja um exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; from whisper.tokenizer import get_tokenizer
# get_tokenizer -&amp;gt; multilingual, num_languages=99, language=&amp;#39;pt&amp;#39;, task=&amp;#39;transcribe&amp;#39;
#                  True,         99                Qual idioma    Qual task
&amp;gt;&amp;gt;&amp;gt; tokenizer = get_tokenizer(True, num_languages=99, language=&amp;#39;pt&amp;#39;, task=&amp;#39;transcribe&amp;#39;)
# tokenizer.encode você passa o &amp;#39;valor&amp;#39; e recebe os tokens List[int]
&amp;gt;&amp;gt;&amp;gt; tokenizer.encode(&amp;#39;Olá pessoal, este é meu texto.&amp;#39;)
[38056, 842, 24811, 11, 4065, 1136, 9230, 35503, 13]
# tokenizer.decode você passa os tokens List[int] e recebe o &amp;#39;valor&amp;#39;
&amp;gt;&amp;gt;&amp;gt; tokenizer.decode([38056, 842, 24811, 11, 4065, 1136, 9230, 35503, 13])
&amp;#39;Olá pessoal, este é meu texto.&amp;#39;
&amp;gt;&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--fp16&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Usa precisão &lt;em&gt;float16&lt;/em&gt; para acelerar em GPU.&lt;/p&gt;
&lt;p&gt;No Mac M1, por exemplo, eu sempre uso &lt;code&gt;--fp16 False&lt;/code&gt;, assim ele não fica
mostrando warning de que trocou pra &lt;em&gt;float32&lt;/em&gt;. Essa troca acontece
automaticamente se o seu hardware &lt;strong&gt;não&lt;/strong&gt; suportar &lt;em&gt;float16&lt;/em&gt;, então:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Se suportar:&lt;/strong&gt; passa direto com &lt;em&gt;float16&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Se não suportar:&lt;/strong&gt; ele mostra um aviso e troca para &lt;em&gt;float32&lt;/em&gt; sozinho.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Exemplo do warning:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FP16 is not supported on CPU; using FP32 instead
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--compression_ratio_threshold&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Se a razão de compressão (gzip) do texto for muito alta, ele assume que houve
erro (textos muito repetitivos). Valor padrão é &lt;code&gt;2.4&lt;/code&gt;. Útil pra detectar &lt;em&gt;loop
de repetição&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Como funciona a ideia:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;O Whisper pega o texto, compacta com gzip e compara o &lt;strong&gt;tamanho original&lt;/strong&gt; com o
&lt;strong&gt;tamanho compactado&lt;/strong&gt; para calcular a &lt;strong&gt;razão de compressão&lt;/strong&gt;. Textos
repetitivos geram compressões mais eficientes, ou seja, &lt;strong&gt;razão mais alta&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;Olá, olá, olá, olá, olá...&amp;quot;&lt;/code&gt; → compacta muito → &lt;strong&gt;alta razão&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;O rato roeu a roupa do rei de Roma.&amp;quot;&lt;/code&gt; → mais diversidade → &lt;strong&gt;menor razão&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Se a razão ultrapassar o limite definido (padrão: &lt;code&gt;2.4&lt;/code&gt;), o Whisper &lt;strong&gt;descarta o
trecho&lt;/strong&gt; por considerá-lo problemático (repetitivo, bugado etc).&lt;/p&gt;
&lt;p&gt;Se sua transcrição estiver falhando &lt;strong&gt;sem motivo claro&lt;/strong&gt;, esse filtro pode ser o
culpado. Teste com &lt;code&gt;--compression_ratio_threshold 0&lt;/code&gt; e veja se melhora.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--logprob_threshold&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Se a média do logaritmo da probabilidade (logprob) dos tokens estiver abaixo
disso, ele trata como erro. Padrão: &lt;code&gt;-1.0&lt;/code&gt;. Você consegue ver &lt;code&gt;avg_logprob&lt;/code&gt;
(média do logaritmo da probabilidade) das frases transcritas pelo Whisper no
arquivo &lt;code&gt;.json&lt;/code&gt; final gerado. Este arquivo contém algo similar a isso:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;id&amp;quot;: 102,
  &amp;quot;seek&amp;quot;: 27632,
  &amp;quot;start&amp;quot;: 293.8,
  &amp;quot;end&amp;quot;: 294.66,
  &amp;quot;text&amp;quot;: &amp;quot; primeiro os imports&amp;quot;,
  &amp;quot;tokens&amp;quot;: [51240, 18314, 3003, 41596, 51283],
  &amp;quot;temperature&amp;quot;: 0.0,
  &amp;quot;avg_logprob&amp;quot;: -0.08265516709308235,
  &amp;quot;compression_ratio&amp;quot;: 1.7523364485981308,
  &amp;quot;no_speech_prob&amp;quot;: 1.1688144375965326e-11,
  &amp;quot;words&amp;quot;: [
    {
      &amp;quot;word&amp;quot;: &amp;quot; primeiro&amp;quot;,
      &amp;quot;start&amp;quot;: 293.8,
      &amp;quot;end&amp;quot;: 294.06,
      &amp;quot;probability&amp;quot;: 0.7435079216957092
    },
    {
      &amp;quot;word&amp;quot;: &amp;quot; os&amp;quot;,
      &amp;quot;start&amp;quot;: 294.06,
      &amp;quot;end&amp;quot;: 294.24,
      &amp;quot;probability&amp;quot;: 0.9965941309928894
    },
    {
      &amp;quot;word&amp;quot;: &amp;quot; imports&amp;quot;,
      &amp;quot;start&amp;quot;: 294.24,
      &amp;quot;end&amp;quot;: 294.66,
      &amp;quot;probability&amp;quot;: 0.990346372127533
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aqui &lt;code&gt;avg_logprob&lt;/code&gt; é &lt;code&gt;-0.08265516709308235&lt;/code&gt;. Quanto mais próximo de &lt;code&gt;0&lt;/code&gt;, mais
confiante está o modelo.&lt;/p&gt;
&lt;p&gt;Suponha que o modelo está descartando coisas na transcrição. Você poderia testar
&lt;code&gt;--logprob_threshold=-2.0&lt;/code&gt; ou até &lt;code&gt;--logprob_threshold=-1000&lt;/code&gt; (não descarta
nada).&lt;/p&gt;
&lt;p&gt;Isso pode gerar muito ruído aleatório na transcrição, mas pode fazer ele
detectar o que você quer.&lt;/p&gt;
&lt;p&gt;O contrário também é verdadeiro. Se usar &lt;code&gt;--logprob_threshold=-0.1&lt;/code&gt; (por
exemplo), o modelo vai pegar praticamente só o que tem certeza absoluta que tá
certo. Isso não é uma coisa boa ou ruim, depende do contexto e do seu objetivo.
Na dúvida, manter o padrão costuma ser uma escolha segura.&lt;/p&gt;
&lt;p&gt;Quanto mais rigoroso, mais lento, porque ele vai tentar gerar várias hipóteses
até alcançar esse nível de confiança. No fim das contas, ele vai te entregar um
texto de qualquer jeito, mas pode demorar bem mais e talvez nem seja tão
diferente assim.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--no_speech_threshold&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Se o modelo acredita que é silêncio (probabilidade alta de &lt;code&gt;&amp;lt;|nospeech|&amp;gt;&lt;/code&gt;) &lt;strong&gt;e&lt;/strong&gt;
a decodificação falha (&lt;code&gt;logprob_threshold&lt;/code&gt;), ele descarta o trecho como sendo
silêncio. Isso ajuda a cortar &amp;quot;respiros vazios&amp;quot; da transcrição.&lt;/p&gt;
&lt;p&gt;Essa funcionalidade me encanta, e tenho planos futuros pra ela. Quem sabe a
gente não volta a falar disso mais pra frente?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--prepend_punctuations&lt;/code&gt;&lt;/strong&gt; (com &lt;code&gt;--word_timestamps True&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;Este argumento controla quais caracteres de pontuação que aparecem &lt;strong&gt;antes&lt;/strong&gt; de
uma palavra devem ser &amp;quot;colados&amp;quot; à palavra seguinte, em vez de serem tratados
como um token separado.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Padrão&lt;/strong&gt;: &lt;code&gt;\&amp;quot;\&amp;#39;“¿([{-&lt;/code&gt; (inclui aspas, parênteses, etc.) e requer
&lt;code&gt;--word_timestamps True&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Em teoria, se o modelo gerasse, por exemplo, os tokens &lt;code&gt;(&lt;/code&gt;, &lt;code&gt;arg&lt;/code&gt;, &lt;code&gt;ument&lt;/code&gt;,
&lt;code&gt;os&lt;/code&gt;, &lt;code&gt;)&lt;/code&gt; separadamente (tipo: &lt;code&gt;[7, 33544, 2206, 329, 8]&lt;/code&gt; que formariam
&lt;code&gt;(argumentos)&lt;/code&gt;), o &lt;code&gt;(&lt;/code&gt; e o &lt;code&gt;arg&lt;/code&gt; seriam unidos para formar &lt;code&gt;(arg&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Observação Importante&lt;/strong&gt;: eu testei o &lt;code&gt;whisper&lt;/code&gt; com os idiomas &lt;code&gt;Portuguese&lt;/code&gt; e
&lt;code&gt;English&lt;/code&gt; (90% em &lt;code&gt;Portuguese&lt;/code&gt;, que é meu caso de uso). Em nenhuma das
legendas que gerei houve qualquer caso onde a pontuação viesse antes de alguma
palavra. Na prática, eu realmente não usei este parâmetro.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--append_punctuations&lt;/code&gt;&lt;/strong&gt; (com &lt;code&gt;--word_timestamps True&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;Este argumento controla quais caracteres de pontuação que aparecem &lt;strong&gt;depois&lt;/strong&gt; de
uma palavra devem ser &amp;quot;colados&amp;quot; à palavra anterior.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Padrão&lt;/strong&gt;: &lt;code&gt;\&amp;quot;\&amp;#39;.。,，!！?？:：”)]}、&lt;/code&gt; (inclui aspas, pontos, vírgulas,
interrogações, etc.) e requer &lt;code&gt;--word_timestamps True&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Por exemplo, se os tokens gerados forem &lt;code&gt;Ok&lt;/code&gt; e &lt;code&gt;?&lt;/code&gt; separadamente, e o &lt;code&gt;?&lt;/code&gt;
estiver incluído nesta lista (o que já está por padrão), eles serão unidos para
formar &lt;code&gt;Ok?&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dica Prática&lt;/strong&gt;: Esses argumentos de pontuação só farão uma diferença
perceptível se você precisar que o ponto ou outro símbolo tenha um &lt;code&gt;timestamp&lt;/code&gt;
&lt;em&gt;exatamente&lt;/em&gt; separado da palavra, o que é um caso de uso bastante específico. Na
maioria das situações, o padrão do &lt;code&gt;whisper&lt;/code&gt; já é bastante robusto. Do
contrário, e para simplificar, mantenha os valores padrão.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Outros úteis&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--threads&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Define o número de &lt;em&gt;threads&lt;/em&gt; que o modelo vai usar na CPU. Exemplo:
&lt;code&gt;--threads 4&lt;/code&gt;. Se não passar nada, ele usa o padrão da Torch (geralmente via MKL
ou OMP).&lt;/p&gt;
&lt;p&gt;Nos meus testes (Mac M1), usei &lt;code&gt;1, 4, 10, 100, 1000&lt;/code&gt;. O resultado? Ele só criou
mais &lt;em&gt;threads&lt;/em&gt; e usou mais CPU, &lt;strong&gt;mas a velocidade de transcrição não mudou
absolutamente nada&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Claro, meus testes foram superficiais. Pode ser que em outro sistema, com outra
CPU (ou invocando Cthulhu no terminal), você veja alguma diferença. Eu? Só vi o
cooler suando.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--clip_timestamps&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Permite transcrever ou traduzir apenas trechos específicos do áudio ou vídeo.
Você passa os intervalos como pares &lt;code&gt;start,end&lt;/code&gt; (em segundos). Pode usar vários.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemplos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--clip_timestamps 10,30&lt;/code&gt; → transcreve de 10s até 30s&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--clip_timestamps 60,120&lt;/code&gt; → de 1min até 2min&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--clip_timestamps 10,30,60,120&lt;/code&gt; → dois trechos: 10s–30s e 1min–2min&lt;/li&gt;
&lt;li&gt;⚠️ &lt;code&gt;--clip_timestamps 270&lt;/code&gt; → de 4min30s até o final&lt;/li&gt;
&lt;li&gt;⚠️ &lt;code&gt;--clip_timestamps 60,120,0&lt;/code&gt; → transcreve de 1min–2min &lt;strong&gt;e depois recomeça
do zero até o fim&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Atenção:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Esse último exemplo (&lt;code&gt;60,120,0&lt;/code&gt;) parece um caso não previsto.&lt;/p&gt;
&lt;p&gt;O &lt;code&gt;0&lt;/code&gt; vem depois de &lt;code&gt;120&lt;/code&gt;, mas não forma um par &lt;code&gt;start,end&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Nos testes, isso gerou um comportamento curioso: o modelo transcreveu
normalmente de 1min até 2min, &lt;strong&gt;e depois do início até o final&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Mesmo assim, o VLC interpretou direitinho. Ele realinhou os blocos e ignorou os
duplicados, mostrando só o que fazia sentido cronológico (&lt;strong&gt;aparentemente
cortando o primeiro minuto&lt;/strong&gt;).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;--hallucination_silence_threshold&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Funciona junto com &lt;code&gt;--word_timestamps True&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Ele tenta detectar trechos de silêncio longos que o modelo pode ter &amp;quot;alucinado&amp;quot;
(inventado texto).&lt;/p&gt;
&lt;p&gt;Se você passar &lt;code&gt;--hallucination_silence_threshold 1.5&lt;/code&gt;, ele vai &lt;strong&gt;ignorar
silêncios maiores que 1.5s que geraram texto suspeito&lt;/strong&gt;. Não toquei nesse
argumento.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Usando o Whisper via código&lt;/h2&gt;
&lt;p&gt;Para usar o &lt;code&gt;whisper&lt;/code&gt; via código, é bem simples. Como informado no repositório
deles, basta usar o seguinte para uso normal do whisper.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import whisper

model = whisper.load_model(&amp;quot;turbo&amp;quot;)
result = model.transcribe(&amp;quot;audio.mp3&amp;quot;)
print(result[&amp;quot;text&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para um acesso de mais baixo nível:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import whisper

model = whisper.load_model(&amp;quot;turbo&amp;quot;)

# load audio and pad/trim it to fit 30 seconds
audio = whisper.load_audio(&amp;quot;audio.mp3&amp;quot;)
audio = whisper.pad_or_trim(audio)

# make log-Mel spectrogram and move to the same device as the model
mel = whisper.log_mel_spectrogram(audio, n_mels=model.dims.n_mels).to(model.device)

# detect the spoken language
_, probs = model.detect_language(mel)
print(f&amp;quot;Detected language: {max(probs, key=probs.get)}&amp;quot;)

# decode the audio
options = whisper.DecodingOptions()
result = whisper.decode(model, mel, options)

# print the recognized text
print(result.text)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;E como eu fiz meu código?&lt;/h3&gt;
&lt;p&gt;Eu fiz o código de uma forma que eu continuasse usando todos os parâmetros do
&lt;code&gt;whisper&lt;/code&gt;, porém, adicionando minha própria lógica.&lt;/p&gt;
&lt;p&gt;Basicamente eu simulo que os argumentos estão sendo enviados para mim com
&lt;code&gt;sys.argv&lt;/code&gt; do Python. No Diagrama abaixo eu mostro o processo do &amp;quot;terminal&amp;quot; até
chegar ao &lt;code&gt;argparse&lt;/code&gt;, e do lado direito, como montei meu código também chamando
o argparse.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/diagrama.webp&quot; alt=&quot;Diagrama exibindo como usei o sys.argv para simular o terminal no meu código&quot;&gt;&lt;/p&gt;
&lt;p&gt;Diagrama exibindo como usei o &lt;code&gt;sys.argv&lt;/code&gt; para simular o terminal no meu código&lt;/p&gt;
&lt;p&gt;Veja o código a seguir. Só para constar, tem um logger em outro módulo com o
seguinte código.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging

from rich.logging import RichHandler

logging.basicConfig(
    level=&amp;quot;CRITICAL&amp;quot;,
    format=&amp;quot;%(message)s&amp;quot;,
    datefmt=&amp;quot;[%H:%M]&amp;quot;,
    handlers=[
        RichHandler(
            show_time=True,
            show_level=True,
            rich_tracebacks=True,
            omit_repeated_times=False,
            markup=False,
        )
    ],
)

logger = logging.getLogger(&amp;quot;rich&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora sim, vamos ver o código. Deixei vários comentário explicando tudo.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import argparse
from pathlib import Path

import rich_argparse

from sussu.basic_logger import logger

# Example commands:
#
# sussu whisper ~/Desktop/videos/part_0004.mp4 --temperature 0 --beam_size 1 \
# --device cpu --fp16 False --output_format srt --model tiny --language pt \
# --output_dir ~/Desktop/videos/
#
# sussu one ~/Desktop/videos/part_0004.mp4 --temperature 0 --beam_size 1 \
# --device cpu --fp16 False --output_format srt --model tiny --language pt \
# --output_dir ~/Desktop/videos/
#
# sussu batch --input_dir ~/Desktop/videos/ --temperature 0 --beam_size 1
# --device cpu --fp16 False --output_format srt --model tiny --language pt
# -s video.mp4 part_0000.mp4 --skip_files part_0001.mp4
# --output_dir &amp;#39;this wont do anything here&amp;#39;


# Essa função é basicamente um jeito de &amp;quot;enganar&amp;quot; o cli do `whisper`
# para que ele &amp;quot;entenda&amp;quot; que está sendo chamado com determinados argumentos.
def whisper_cli_runner(whisper_args: list[str]) -&amp;gt; None:
    import sys

    # `whisper` não tem stub, por isso o pyright vai gerar erro (ignorado)
    from whisper.transcribe import cli as whisper_cli  # pyright: ignore

    # Aqui está a malícia. Vamos fingir que o python está recebendo os argumento
    # via sys.argv. Com isso o argparse entra em ação da mesma forma que
    # entraria se estivesse sendo executado via linha de comando.
    sys.argv = [&amp;quot;whisper&amp;quot;, *whisper_args]
    whisper_cli()


# Essa é a nossa função que vai processar os arquivos usando o whisper original
def batch_whisper(
    input_dir: Path, whisper_raw_args: list[str], skip_files: list[str] | None = None
) -&amp;gt; None:
    # Vamos preencher essa lista com os dados que precisamos
    whisper_args: list[str] = []

    # As extensões abaixo podem não conter todas as extensões suportadas pelo
    # ffmpeg, sinta-se à vontade para adicionar novas extensões
    # fmt: off
    allowed_extensions =  {
        &amp;quot;.mp3&amp;quot;, &amp;quot;.wav&amp;quot;, &amp;quot;.flac&amp;quot;, &amp;quot;.aac&amp;quot;, &amp;quot;.m4a&amp;quot;, &amp;quot;.ogg&amp;quot;, &amp;quot;.opus&amp;quot;, &amp;quot;.mp4&amp;quot;, &amp;quot;.mkv&amp;quot;,
        &amp;quot;.webm&amp;quot;, &amp;quot;.mov&amp;quot;, &amp;quot;.avi&amp;quot;, &amp;quot;.3gp&amp;quot;, &amp;quot;.wmv&amp;quot;,
    }
    # fmt: on

    # Às vezes tem alguns arquivos na mesma pasta que são válidos, mas não
    # queremos transcrever (eu só queria agilizar meus testes manuais)
    if not skip_files:
        skip_files = []

    # Passamos em todos os arquivos da pasta enviada pelo usuário
    for file in input_dir.iterdir():
        skip_loop = False
        ########## VAMOS PULAR ALGUNS ARQUIVOS PARA EVITAR ERROS ##########

        # Pulamos quando é um subdiretório
        if file.is_dir():
            logger.warning(f&amp;quot;Directory not allowed: {file.name}&amp;quot;)
            continue

        # Pulamos se a extensão não for permitida
        if file.suffix not in allowed_extensions:
            logger.error(f&amp;quot;File extension not allowed: {file.name}&amp;quot;)
            continue

        # Pulamos também quando o usuário pede para pular aquele arquivo via -s
        for skip_file in skip_files:
            if str(file).endswith(skip_file):
                logger.info(f&amp;quot;File skipped: {file.name}&amp;quot;)
                skip_loop = True

        if skip_loop:
            skip_loop = False
            continue

        ############ DAQUI EM DIANTE VAI PARA O WHISPER ##########

        # O argumento posicional vai sozinho no primeiro índice
        # depois os argumentos desconhecidos
        whisper_args.extend([str(file), *whisper_raw_args])
        logger.debug(f&amp;quot;audio set as {file!s}&amp;quot;)

        # Por fim, adicionamos o outdir para ser sempre a pasta onde está
        # o arquivo original. Isso gera um arquivo de mesmo nome com a extensão
        # `.srt`.
        logger.debug(f&amp;quot;--output_dir set to {file.parent}&amp;quot;)
        whisper_args.extend([&amp;quot;--output_dir&amp;quot;, str(file.parent)])

        # Desativa o modo verboso do `whisper` por padrão para que a gente possa
        # ver nossos logs. Se o user passar algo, usa o que ele passar.
        if &amp;quot;--verbose&amp;quot; not in whisper_args:
            whisper_args += [&amp;quot;--verbose&amp;quot;, &amp;quot;False&amp;quot;]
            logger.debug(&amp;quot;--verbose set to False by default&amp;quot;)

        # Agora só chamar o whisper com os argumentos que montamos
        logger.debug(f&amp;quot;whisper commands are: {whisper_args}&amp;quot;)
        logger.debug(f&amp;quot;Final command: whisper {&amp;#39; &amp;#39;.join(whisper_args)}&amp;quot;)
        whisper_cli_runner(whisper_args)

        # Zeramos os argumentos para o próximo loop
        whisper_args = []


def build_argparse() -&amp;gt; argparse.ArgumentParser:
    # Nosso main parser e o subparser para os comandos
    parser = argparse.ArgumentParser(
        prog=&amp;quot;sussu&amp;quot;, formatter_class=rich_argparse.RawDescriptionRichHelpFormatter
    )
    subparsers = parser.add_subparsers(dest=&amp;quot;command&amp;quot;, required=True)

    ########## WHISPER PARSER ##########

    # Esse subparse só será usado como um wrapper do argparse do whisper.
    # No final das contas, ele só vai chamar `whisper.transcribe.cli()`
    whisper_parser = subparsers.add_parser(
        &amp;quot;whisper&amp;quot;,
        help=&amp;quot;Calls `whisper` directly&amp;quot;,
        conflict_handler=&amp;quot;resolve&amp;quot;,
        aliases=[&amp;quot;one&amp;quot;],
        formatter_class=rich_argparse.RawDescriptionRichHelpFormatter,
    )
    whisper_parser.set_defaults(command=&amp;quot;whisper&amp;quot;)

    # Esse argumento aqui é pra garantir que vamos chamar o help do whisper e
    # não do nosso parser
    whisper_parser.add_argument(
        &amp;quot;-h&amp;quot;, &amp;quot;--help&amp;quot;, help=&amp;quot;Shows `whisper` help.&amp;quot;, action=&amp;quot;store_true&amp;quot;
    )

    ########## NOSSOS PARSERS ##########
    # Minha ideia aqui é criar um subparser `batch` que vai receber um diretório
    # com arquivos de vídeo. Vamos passar em todos os arquivos do diretório e
    # usar o whisper para transcrever cada um deles.

    batch_parser = subparsers.add_parser(
        &amp;quot;batch&amp;quot;,
        help=&amp;quot;Process files with `whisper` in batch mode&amp;quot;,
        formatter_class=rich_argparse.RawDescriptionRichHelpFormatter,
    )

    # Só coloquei essa função aqui para ficar próxima do argumento e facilitar
    # minha explicação na hora de gravar.
    def parse_input_dir(path_str: str) -&amp;gt; Path:
        path = Path(path_str)

        if not path.is_dir():
            msg = f&amp;quot;{path_str!r} is not a directory&amp;quot;
            raise argparse.ArgumentTypeError(msg)

        return path.resolve()

    # Isso deverá ser uma pasta que contém arquivos de vídeo ou áudio
    batch_parser.add_argument(
        &amp;quot;--input_dir&amp;quot;,
        help=&amp;quot;Directory with files to work with&amp;quot;,
        type=parse_input_dir,
        required=True,
    )

    # Para testar, eu estava pulando um monte de arquivos para ir mais rápido
    batch_parser.add_argument(
        &amp;quot;-s&amp;quot;,
        &amp;quot;--skip_files&amp;quot;,
        help=&amp;quot;Name of file(s) to skip&amp;quot;,
        action=&amp;quot;extend&amp;quot;,
        nargs=&amp;quot;+&amp;quot;,
        default=[],
    )

    # Essa foi a maneira mais simples e direta de remover output_dir dos
    # unknown_args. Se isso fosse para o whisper, geraria conflito
    batch_parser.add_argument(&amp;quot;-o&amp;quot;, &amp;quot;--output_dir&amp;quot;, help=argparse.SUPPRESS)
    return parser


def run() -&amp;gt; None:
    ########## PARSE KNOWN ARGS ##########

    # Vamos receber argumentos que são conhecidos (os nossos), e desconhecidos.
    # Argumentos desconhecidos serão repassados para o whisper cli.
    parser = build_argparse()
    args, unknown_args = parser.parse_known_args()

    # Se o comando for whisper, passamos tudo direto para o whisper
    if args.command == &amp;quot;whisper&amp;quot;:
        # Simula -h e --help
        if args.help:
            whisper_cli_runner([&amp;quot;--help&amp;quot;])
            return

        # Executa o whisper normal, só que por baixo de `sussu`
        # Ex.: `sussu whisper audio.mp3` chama o cli original do `whisper` com
        # o argumento `audio.mp3` (ou qualquer outro argumento)
        whisper_cli_runner(unknown_args)
        return

    # Se o comando for `batch`, fazemos nosso trabalho
    if args.command == &amp;quot;batch&amp;quot;:
        batch_whisper(args.input_dir, unknown_args, args.skip_files)


if __name__ == &amp;quot;__main__&amp;quot;:
    run()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;É só isso! Obrigado por ler.&lt;/p&gt;
</content:encoded></item><item><title>Notas Técnicas: Criando CLIs com Python e Argparse</title><link>https://otaviomiranda.com.br/2025/notas-tecnicas-python-argparse-cli/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2025/notas-tecnicas-python-argparse-cli/</guid><description>Acesse as notas técnicas detalhadas e a transcrição semântica do vídeo sobre como criar CLIs profissionais em Python com argparse. Um guia completo para consulta e estudo.</description><pubDate>Sun, 05 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Olá! Este post serve como um guia técnico detalhado e notas de apoio para o meu
vídeo completo sobre a criação de
&lt;a href=&quot;https://youtu.be/Ad6934NXn4A&quot;&gt;CLIs com Python e Argparse&lt;/a&gt;, que você pode
assistir aqui. Se você caiu aqui de paraquedas, comece pelo vídeo!&lt;/p&gt;
&lt;p&gt;O código-fonte completo do projeto está disponível neste
&lt;a href=&quot;https://github.com/luizomf/task_with_python_argparse&quot;&gt;repositório do GitHub&lt;/a&gt;
para você baixar, estudar e acompanhar.&lt;/p&gt;
&lt;p&gt;Para gerar todo o conteúdo de apoio que você verá abaixo, usei algumas coisas
que podem ser interessantes para os devs mais curiosos. O primeiro passo começa
com a montagem do projeto e a gravação do vídeo. Essas partes funcionam
exatamente como sempre funcionaram: crio o projeto antes, gravo o vídeo e uso o
&lt;code&gt;ffmpeg&lt;/code&gt; para gerar uma versão mais compactada do arquivo em MP4.&lt;/p&gt;
&lt;p&gt;Feito isso, eu passo o vídeo por um projetinho chamado
&lt;a href=&quot;https://github.com/luizomf/nlingua2&quot;&gt;&lt;code&gt;nlingua2&lt;/code&gt;&lt;/a&gt; para gerar a transcrição do
vídeo em &lt;code&gt;.srt&lt;/code&gt;. Depois, uso o Gemini para analisar essa transcrição e gerar um
resumo técnico detalhado, que você pode ler na íntegra abaixo. A partir desses
textos, gero os detalhes dos vídeos no Youtube, como título, descrição,
timestamps e tags.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Análise Detalhada da Transcrição (Gerado por IA)&lt;/h2&gt;
&lt;p&gt;A seguir, um resumo trecho por trecho do vídeo.&lt;/p&gt;
&lt;h2&gt;Trecho iniciando em 00:00:00&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Objetivo Principal:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;O objetivo principal deste trecho é introduzir um tutorial sobre como usar o
&lt;code&gt;argparse&lt;/code&gt; do Python para criar ferramentas de linha de comando. O apresentador
propõe um desafio prático: construir a interface de linha de comando (CLI) para
um projeto existente, permitindo a comunicação externa com o programa.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Linguagens/Ferramentas:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Python:&lt;/strong&gt; Linguagem de programação principal usada no projeto.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;argparse:&lt;/strong&gt; Módulo Python para criar interfaces de linha de comando.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rich:&lt;/strong&gt; Biblioteca Python para formatação de texto e saída colorida no
terminal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rich-argparse:&lt;/strong&gt; Extensão do rich para aprimorar a aparência do argparse.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tinyDB:&lt;/strong&gt; Biblioteca para criar um banco de dados simples em formato JSON.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;uv (UltraViolet):&lt;/strong&gt; Gerenciador de ambientes virtuais Python.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;git:&lt;/strong&gt; Sistema de controle de versão usado para gerenciar o código-fonte do
projeto.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pip:&lt;/strong&gt; Instalador de pacotes Python.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pyenv:&lt;/strong&gt; Ferramenta para gerenciar diferentes versões do Python (mencionada,
mas não usada diretamente no trecho).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos Práticos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;O apresentador mostra o projeto no editor de código e explica a estrutura do
desafio.&lt;/li&gt;
&lt;li&gt;Ele demonstra como clonar o repositório do projeto via git, incluindo a
utilização de branches (&lt;code&gt;main&lt;/code&gt; e &lt;code&gt;start_arg_parse&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Alternativamente, ele mostra como baixar o projeto como um arquivo zip.&lt;/li&gt;
&lt;li&gt;Ele ativa o ambiente virtual usando o comando &lt;code&gt;uv sync&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Explica que o &lt;code&gt;uv sync&lt;/code&gt; instala as dependências do projeto (rich,
rich-argparse, tinyDB), cria uma build em modo editável e torna o comando
&lt;code&gt;task&lt;/code&gt; disponível.&lt;/li&gt;
&lt;li&gt;Ele executa o comando &lt;code&gt;task&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos Teóricos Importantes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;O &lt;code&gt;argparse&lt;/code&gt; é apresentado como uma maneira moderna e segura de criar CLIs em
Python, fornecendo uma API para interagir com o programa.&lt;/li&gt;
&lt;li&gt;O tutorial é estruturado como um desafio, com especificações para o CLI a ser
construído, incluindo subcomandos, aliases, descrições, e argumentos.&lt;/li&gt;
&lt;li&gt;O projeto utiliza o &lt;code&gt;rich&lt;/code&gt; para melhorar a formatação da saída no terminal,
incluindo syntax highlighting para o help da CLI.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;tinyDB&lt;/code&gt; é usado para armazenar dados em um arquivo JSON.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;uv&lt;/code&gt; é recomendado como uma alternativa mais simples para gerenciar
ambientes virtuais em comparação com o &lt;code&gt;venv&lt;/code&gt; tradicional ou o &lt;code&gt;pyenv&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;O comando &lt;code&gt;task&lt;/code&gt; é o ponto de entrada para o projeto.&lt;/li&gt;
&lt;li&gt;O projeto envolve &amp;quot;runners&amp;quot; que recebem argumentos do &lt;code&gt;argparse&lt;/code&gt;,
convertem-nos em dicionários, mapeiam-nos para as necessidades internas do
projeto e, finalmente, salvam os dados usando o &lt;code&gt;tinyDB&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;É importante ressaltar que o trecho analisado foca principalmente na
configuração do projeto e na introdução do desafio, sem entrar nos detalhes da
implementação do &lt;code&gt;argparse&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Trecho iniciando em 00:07:02&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Objetivo Principal:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Configurar a estrutura básica de um programa de linha de comando em Python
usando o módulo &lt;code&gt;argparse&lt;/code&gt;, incluindo a definição do comando principal (&lt;code&gt;task&lt;/code&gt;),
a exibição de ajuda (&lt;code&gt;-h&lt;/code&gt; ou &lt;code&gt;--help&lt;/code&gt;), e a inclusão de uma descrição formatada.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Linguagens/Ferramentas:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Módulo &lt;code&gt;argparse&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Ambiente virtual (mencionado, mas não demonstrado diretamente neste trecho)&lt;/li&gt;
&lt;li&gt;UVsync (ferramenta para gerenciar o ambiente virtual, mencionado, mas não
usado diretamente)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Estrutura do projeto:&lt;/strong&gt; Explica a estrutura de pastas do projeto, com
ênfase na pasta &lt;code&gt;src&lt;/code&gt; como raiz, contendo o pacote &lt;code&gt;task&lt;/code&gt;, o módulo
&lt;code&gt;cli.py&lt;/code&gt;, e a função &lt;code&gt;run&lt;/code&gt; dentro de &lt;code&gt;cli.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Função &lt;code&gt;run&lt;/code&gt;:&lt;/strong&gt; Inicialmente, a função &lt;code&gt;run&lt;/code&gt; limpa o terminal e chama a
função &lt;code&gt;build_parser&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Função &lt;code&gt;build_parser&lt;/code&gt;:&lt;/strong&gt; Cria o parser de argumentos usando
&lt;code&gt;argparse.ArgumentParser()&lt;/code&gt;. Retorna o objeto parser criado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chamada à função &lt;code&gt;build_parser&lt;/code&gt;:&lt;/strong&gt; A função &lt;code&gt;run&lt;/code&gt; chama &lt;code&gt;build_parser&lt;/code&gt; e
armazena o resultado na variável &lt;code&gt;parser&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parsing dos argumentos:&lt;/strong&gt; Dentro de &lt;code&gt;run&lt;/code&gt;, utiliza &lt;code&gt;parser.parse_args()&lt;/code&gt;
para processar os argumentos da linha de comando e armazena o resultado na
variável &lt;code&gt;args&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exibição dos argumentos:&lt;/strong&gt; Imprime os argumentos processados com
&lt;code&gt;print(args)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuração do &lt;code&gt;ArgumentParser&lt;/code&gt;:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Define o nome do programa para &lt;code&gt;task&lt;/code&gt; usando &lt;code&gt;prog=&amp;quot;task&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Adiciona uma descrição usando o argumento &lt;code&gt;description&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Discute o uso de strings multilinha para a descrição.&lt;/li&gt;
&lt;li&gt;Apresenta a formatação da descrição e a diferença entre
&lt;code&gt;argparse.RawTextHelpFormatter&lt;/code&gt;, &lt;code&gt;argparse.RawDescriptionHelpFormatter&lt;/code&gt;, e
o formatador padrão (&lt;code&gt;argparse.HelpFormatter&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos Teóricos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uso do módulo &lt;code&gt;argparse&lt;/code&gt; para criar interfaces de linha de comando em Python.&lt;/li&gt;
&lt;li&gt;Importância de estruturar o código para facilitar testes (justificativa para
criar a função &lt;code&gt;build_parser&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Diferenças entre os formatadores de ajuda do &lt;code&gt;argparse&lt;/code&gt; e como controlar a
formatação da descrição e do epílogo.&lt;/li&gt;
&lt;li&gt;O argumento &lt;code&gt;prog&lt;/code&gt; do &lt;code&gt;ArgumentParser&lt;/code&gt; permite controlar o nome do programa
exibido na ajuda.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;argparse&lt;/code&gt; adiciona automaticamente o argumento &lt;code&gt;-h/--help&lt;/code&gt; para exibir a
ajuda.&lt;/li&gt;
&lt;li&gt;O comportamento padrão do &lt;code&gt;argparse&lt;/code&gt; é usar o nome do módulo como nome do
programa se &lt;code&gt;prog&lt;/code&gt; não for especificado.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trecho iniciando em 00:14:02&lt;/h2&gt;
&lt;p&gt;Resumo detalhado do trecho (começando em 00:14:02):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Objetivo principal:&lt;/strong&gt; Explicar como customizar a ajuda (help) e a formatação
de texto em ferramentas de linha de comando criadas com a biblioteca &lt;code&gt;argparse&lt;/code&gt;
do Python, incluindo a utilização de subcomandos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Linguagens/Ferramentas:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Biblioteca &lt;code&gt;argparse&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Módulo &lt;code&gt;textwrap&lt;/code&gt; (função &lt;code&gt;dedent&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Biblioteca &lt;code&gt;rich-argparse&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Customizando o Help:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Demonstra o uso de &lt;code&gt;raw_description_help_formatter&lt;/code&gt; para manter a
descrição curta e sem quebras de linha.&lt;/li&gt;
&lt;li&gt;Apresenta o &lt;code&gt;text_help_formatter&lt;/code&gt; para quebras de linha e indentação, mas
aponta o problema da indentação fixa.&lt;/li&gt;
&lt;li&gt;Utiliza a função &lt;code&gt;dedent&lt;/code&gt; do módulo &lt;code&gt;textwrap&lt;/code&gt; para remover a indentação
indesejada, mantendo as quebras de linha.&lt;/li&gt;
&lt;li&gt;Mostra como adicionar um epílogo longo com quebras de linha e remover a
indentação com &lt;code&gt;dedent&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adicionando Syntax Highlighting:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Introduz a biblioteca &lt;code&gt;rich-argparse&lt;/code&gt; para adicionar cores e destaque de
sintaxe à saída do help.&lt;/li&gt;
&lt;li&gt;Substitui as classes do &lt;code&gt;argparse&lt;/code&gt; (e.g., &lt;code&gt;HelpFormatter&lt;/code&gt;,
&lt;code&gt;RawTextHelpFormatter&lt;/code&gt;) pelas equivalentes do &lt;code&gt;rich-argparse&lt;/code&gt; (e.g.,
&lt;code&gt;RichHelpFormatter&lt;/code&gt;, &lt;code&gt;RichRawTextHelpFormatter&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Criando Subcomandos:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Explica o conceito de subcomandos (como em &lt;code&gt;git add&lt;/code&gt;, &lt;code&gt;git commit&lt;/code&gt;) e sua
utilidade para organizar comandos e opções.&lt;/li&gt;
&lt;li&gt;Mostra como criar subcomandos usando &lt;code&gt;add_subparsers()&lt;/code&gt; e como acessá-los
através da chave &lt;code&gt;command&lt;/code&gt; no objeto &lt;code&gt;Namespace&lt;/code&gt; retornado pelo
&lt;code&gt;argparse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Menciona a diferença entre argumentos posicionais (sem flags) e argumentos
com flags (e.g., &lt;code&gt;-t&lt;/code&gt;, &lt;code&gt;--tag&lt;/code&gt;), indicando sua preferência pelos últimos
devido à flexibilidade da ordem.&lt;/li&gt;
&lt;li&gt;Explica que subcomandos permitem criar namespaces para opções, permitindo
o reuso de nomes de opções (e.g., &lt;code&gt;-t&lt;/code&gt; para &lt;code&gt;create&lt;/code&gt; e &lt;code&gt;search&lt;/code&gt;) com
diferentes funcionalidades.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos Teóricos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A importância de manter o help conciso e fácil de entender.&lt;/li&gt;
&lt;li&gt;O uso de &lt;code&gt;textwrap.dedent()&lt;/code&gt; para formatar texto com quebras de linha sem
indentação fixa.&lt;/li&gt;
&lt;li&gt;A vantagem de usar &lt;code&gt;rich-argparse&lt;/code&gt; para melhorar a legibilidade da saída do
help com syntax highlighting.&lt;/li&gt;
&lt;li&gt;A distinção entre argumentos posicionais e argumentos com flags.&lt;/li&gt;
&lt;li&gt;O conceito de namespaces e como subcomandos os implementam na prática.&lt;/li&gt;
&lt;li&gt;O uso da chave &lt;code&gt;command&lt;/code&gt; no objeto &lt;code&gt;Namespace&lt;/code&gt; para acessar o subcomando
chamado.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trecho iniciando em 00:21:03&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Objetivo principal:&lt;/strong&gt; Configurar um subparser chamado &amp;quot;create&amp;quot; dentro de um
&lt;code&gt;argparse&lt;/code&gt; em Python para um programa de linha de comando chamado &amp;quot;task&amp;quot;. O
apresentador detalha a criação do subparser, adiciona aliases, define a
descrição (&lt;code&gt;description&lt;/code&gt;) e o epílogo (&lt;code&gt;epilogue&lt;/code&gt;) para exibir no help, e
explica a diferença entre argumentos posicionais e opções.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Linguagens:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;&lt;code&gt;argparse&lt;/code&gt; (biblioteca Python para parsing de argumentos de linha de comando)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Definindo o destino do parser:&lt;/strong&gt; O apresentador direciona o parser para a
chave &amp;quot;command&amp;quot;. Define a chave como &lt;code&gt;required&lt;/code&gt; para que o programa gere um
erro caso nenhum subcomando seja fornecido.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Criando o subparser &amp;quot;create&amp;quot;:&lt;/strong&gt; Utiliza &lt;code&gt;subparsers.add_parser(&amp;quot;create&amp;quot;)&lt;/code&gt;
para criar um subparser associado ao comando &amp;quot;create&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adicionando aliases:&lt;/strong&gt; Adiciona os aliases &amp;quot;new&amp;quot; e &amp;quot;add&amp;quot; ao subparser
&amp;quot;create&amp;quot; utilizando o parâmetro &lt;code&gt;aliases=[&amp;quot;new&amp;quot;, &amp;quot;add&amp;quot;]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Definindo description e epilogue:&lt;/strong&gt; Adiciona &lt;code&gt;description&lt;/code&gt; e &lt;code&gt;epilogue&lt;/code&gt; ao
subparser para melhorar a informação exibida no help.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Utilizando &lt;code&gt;formatter_class&lt;/code&gt;:&lt;/strong&gt; Utiliza &lt;code&gt;formatter_class&lt;/code&gt; para formatar a
saída do help.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicando argumentos posicionais:&lt;/strong&gt; Mostra o help gerado e explica a
diferença entre argumentos posicionais (sem traço) e opções (com um ou dois
traços), usando o comando &lt;code&gt;mv&lt;/code&gt; do Unix como exemplo.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;O parâmetro &lt;code&gt;required&lt;/code&gt; em um &lt;code&gt;argparse&lt;/code&gt; força o usuário a fornecer um valor
para o argumento.&lt;/li&gt;
&lt;li&gt;Subparsers permitem estruturar comandos complexos em um programa de linha de
comando.&lt;/li&gt;
&lt;li&gt;Aliases permitem usar diferentes nomes para o mesmo comando.&lt;/li&gt;
&lt;li&gt;Argumentos posicionais dependem da ordem em que são fornecidos, enquanto
opções são identificadas por flags (traços).&lt;/li&gt;
&lt;li&gt;A forma curta de uma opção usa um traço, enquanto a forma longa usa dois. A
forma longa geralmente corresponde ao nome do argumento no objeto.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trecho iniciando em 00:28:03&lt;/h2&gt;
&lt;h2&gt;Resumo detalhado do trecho (00:28:03):&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Objetivo principal:&lt;/strong&gt; Implementar a validação de argumentos de linha de
comando para o comando &lt;code&gt;create&lt;/code&gt;, especificamente para o argumento &lt;code&gt;task&lt;/code&gt;, usando
a biblioteca &lt;code&gt;argparse&lt;/code&gt; em Python.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Linguagens/Ferramentas:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Biblioteca &lt;code&gt;argparse&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pathlib&lt;/code&gt; (mencionada como alternativa para validação de caminhos)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;add_argument()&lt;/code&gt;:&lt;/strong&gt; Adiciona o argumento &lt;code&gt;task&lt;/code&gt; ao subparser &lt;code&gt;create&lt;/code&gt;
usando &lt;code&gt;add_argument&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flags curtas e longas:&lt;/strong&gt; Define as flags &lt;code&gt;-t&lt;/code&gt; (curta) e &lt;code&gt;--task&lt;/code&gt; (longa)
para o argumento.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tipo do argumento (type):&lt;/strong&gt; Define o tipo do argumento como string
(&lt;code&gt;str&lt;/code&gt;), explicando o conceito de &lt;em&gt;factory&lt;/em&gt; e como o &lt;code&gt;argparse&lt;/code&gt; usa classes
para converter e validar os tipos. Menciona que o uso de &lt;code&gt;path&lt;/code&gt; da
biblioteca &lt;code&gt;pathlib&lt;/code&gt; seria uma opção para argumentos que representam
caminhos de arquivos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validação customizada:&lt;/strong&gt; Cria uma função &lt;code&gt;validate_str&lt;/code&gt; que remove espaços
em branco do início e fim da string recebida e levanta um
&lt;code&gt;ArgumentTypeError&lt;/code&gt; com a mensagem &amp;quot;empty value&amp;quot; se a string resultante for
vazia. Define esta função como o tipo (&lt;code&gt;type&lt;/code&gt;) do argumento &lt;code&gt;task&lt;/code&gt; no
&lt;code&gt;add_argument()&lt;/code&gt;, demonstrando como usar funções customizadas para
validação.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;required=True&lt;/code&gt;:&lt;/strong&gt; Define o argumento &lt;code&gt;task&lt;/code&gt; como obrigatório.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;help&lt;/code&gt;:&lt;/strong&gt; Define o texto de ajuda curto para o argumento.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;metavar&lt;/code&gt;:&lt;/strong&gt; Demonstra o uso de &lt;code&gt;metavar&lt;/code&gt; para customizar a exibição do
nome do argumento na ajuda, mas decide manter o padrão (&amp;quot;task&amp;quot;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testes:&lt;/strong&gt; Executa testes com diferentes entradas para o argumento
&lt;code&gt;-t&lt;/code&gt;/&lt;code&gt;--task&lt;/code&gt;, incluindo strings vazias e com espaços em branco, mostrando
como a validação customizada funciona e como o &lt;code&gt;argparse&lt;/code&gt; gera mensagens de
erro apropriadas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Print de depuração:&lt;/strong&gt; Utiliza &lt;code&gt;print&lt;/code&gt; para exibir os valores recebidos,
demonstrando o funcionamento do argumento.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos teóricos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Flags:&lt;/strong&gt; Explica o conceito de &lt;em&gt;flags&lt;/em&gt; curtas e longas para argumentos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Factory:&lt;/strong&gt; Explica o padrão de projeto &lt;em&gt;factory&lt;/em&gt; e como o &lt;code&gt;argparse&lt;/code&gt; o
utiliza para a conversão e validação de tipos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validação com &lt;code&gt;argparse&lt;/code&gt;:&lt;/strong&gt; Demonstra como usar funções customizadas com o
parâmetro &lt;code&gt;type&lt;/code&gt; em &lt;code&gt;add_argument()&lt;/code&gt; para validar a entrada do usuário e
evitar código de validação redundante na lógica principal do programa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;pathlib&lt;/code&gt;:&lt;/strong&gt; Sugere o uso da biblioteca &lt;code&gt;pathlib&lt;/code&gt; para lidar com caminhos de
arquivos de forma mais robusta.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separação de responsabilidades:&lt;/strong&gt; Enfatiza a importância de delegar a
validação de argumentos para o &lt;code&gt;argparse&lt;/code&gt; (ou camadas equivalentes em outras
arquiteturas) em vez de lidar com isso na lógica principal da aplicação.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trecho iniciando em 00:35:04&lt;/h2&gt;
&lt;p&gt;Resumo detalhado do trecho (começando em 00:35:04):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Objetivo principal:&lt;/strong&gt; Explicar o uso de &lt;code&gt;actions&lt;/code&gt; no módulo &lt;code&gt;argparse&lt;/code&gt; do
Python para processamento de argumentos de linha de comando, focando em
&lt;code&gt;boolean_optional_action&lt;/code&gt; para criar flags booleanas com valores invertidos e em
&lt;code&gt;extend&lt;/code&gt; para lidar com listas de argumentos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Linguagens/Ferramentas:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Módulo &lt;code&gt;argparse&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;boolean_optional_action&lt;/code&gt;:&lt;/strong&gt; O apresentador demonstra como usar
&lt;code&gt;boolean_optional_action&lt;/code&gt; para criar uma flag booleana (ex: &lt;code&gt;--don&lt;/code&gt;). Sem
valor, a flag é considerada &lt;code&gt;True&lt;/code&gt;. Com o prefixo &amp;quot;no&amp;quot; (ex: &lt;code&gt;--no-don&lt;/code&gt;), a
flag é &lt;code&gt;False&lt;/code&gt;. Se a flag não for usada, o valor padrão é &lt;code&gt;None&lt;/code&gt;. O
apresentador enfatiza a importância do parâmetro &lt;code&gt;default=False&lt;/code&gt; para
definir o valor como &lt;code&gt;False&lt;/code&gt; caso a flag não seja usada, diferenciando-o de
&lt;code&gt;None&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;action=&amp;quot;extend&amp;quot;&lt;/code&gt; e &lt;code&gt;nargs&lt;/code&gt;:&lt;/strong&gt; O apresentador introduz o uso de
&lt;code&gt;action=&amp;quot;extend&amp;quot;&lt;/code&gt; para adicionar múltiplos valores a uma lista através de
uma mesma flag (ex: &lt;code&gt;--full&lt;/code&gt;). Ele explica o uso de &lt;code&gt;nargs&lt;/code&gt; para controlar a
quantidade de argumentos que a flag aceita. O valor &lt;code&gt;&amp;#39;*&amp;#39;&lt;/code&gt; em &lt;code&gt;nargs=&amp;#39;*&amp;#39;&lt;/code&gt;
permite zero ou mais argumentos para a flag, e o &lt;code&gt;action=&amp;quot;extend&amp;quot;&lt;/code&gt; garante
que cada argumento passado para a flag seja adicionado à lista, ao invés de
sobrescrever os valores anteriores. Menciona também outras opções para
&lt;code&gt;nargs&lt;/code&gt;, como &amp;#39;+&amp;#39; (um ou mais) e a possibilidade de usar um número inteiro
para fixar a quantidade de argumentos.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos Teóricos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;boolean_optional_action&lt;/code&gt;:&lt;/strong&gt; Permite criar flags booleanas com valores
invertidos usando o prefixo &amp;quot;no&amp;quot;. O valor padrão sem a flag é &lt;code&gt;None&lt;/code&gt;, podendo
ser alterado com &lt;code&gt;default&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;action=&amp;quot;extend&amp;quot;&lt;/code&gt;:&lt;/strong&gt; Usado para criar listas de argumentos através de uma
única flag, adicionando cada novo argumento à lista.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;nargs&lt;/code&gt;:&lt;/strong&gt; Controla a quantidade de argumentos que uma flag aceita (ex: &lt;code&gt;*&lt;/code&gt;
para zero ou mais, &lt;code&gt;+&lt;/code&gt; para um ou mais, ou um número inteiro).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Importância do &lt;code&gt;default&lt;/code&gt;:&lt;/strong&gt; Usar &lt;code&gt;default=False&lt;/code&gt; em
&lt;code&gt;boolean_optional_action&lt;/code&gt; é crucial para diferenciar entre a flag não usada
(&lt;code&gt;False&lt;/code&gt;) e a flag explicitamente definida como negativa (&lt;code&gt;--no-don&lt;/code&gt;, também
&lt;code&gt;False&lt;/code&gt;), permitindo uma lógica mais precisa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consulta à documentação:&lt;/strong&gt; O apresentador recomenda consultar a documentação
do Python para entender melhor as opções de &lt;code&gt;actions&lt;/code&gt; e &lt;code&gt;nargs&lt;/code&gt; disponíveis no
módulo &lt;code&gt;argparse&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trecho iniciando em 00:42:05&lt;/h2&gt;
&lt;p&gt;Resumo detalhado do trecho (começando em 00:42:05):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Objetivo Principal:&lt;/strong&gt; Configurar o parsing de argumentos de linha de comando
usando a biblioteca &lt;code&gt;argparse&lt;/code&gt; em Python, focando em como lidar com múltiplos
valores para um mesmo argumento, valores padrão, choices restritas e aliases de
comandos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Linguagens/Ferramentas:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Biblioteca &lt;code&gt;argparse&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos Práticos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Configurando o argumento &lt;code&gt;--tag&lt;/code&gt;:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;action=&amp;quot;extend&amp;quot;&lt;/code&gt;: Permite adicionar múltiplos valores à lista &lt;code&gt;tags&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;default=[]&lt;/code&gt;: Define uma lista vazia como valor padrão.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nargs=&amp;quot;*&amp;quot;&lt;/code&gt;: Aceita zero ou mais argumentos.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dest=&amp;quot;tags&amp;quot;&lt;/code&gt;: Salva os valores na chave &lt;code&gt;tags&lt;/code&gt; do dicionário de
argumentos.&lt;/li&gt;
&lt;li&gt;Exemplo de uso: &lt;code&gt;--tag valor1 valor2 valor3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Observação: Valores repetidos são adicionados múltiplas vezes. Sugestão de
remover duplicatas convertendo para &lt;code&gt;set&lt;/code&gt; e de volta para &lt;code&gt;list&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configurando o argumento &lt;code&gt;--priority&lt;/code&gt;:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;choices=[&amp;quot;low&amp;quot;, &amp;quot;medium&amp;quot;, &amp;quot;high&amp;quot;]&lt;/code&gt;: Restringe os valores possíveis.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;default=&amp;quot;medium&amp;quot;&lt;/code&gt;: Define &amp;quot;medium&amp;quot; como valor padrão.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;help=&amp;quot;Define a prioridade&amp;quot;&lt;/code&gt;: Define o texto de ajuda para o argumento.&lt;/li&gt;
&lt;li&gt;Exemplo de uso: &lt;code&gt;-p low&lt;/code&gt; ou &lt;code&gt;--priority high&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Corrigindo o problema de aliases com &lt;code&gt;set_defaults&lt;/code&gt;:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;set_defaults(comando=&amp;quot;create&amp;quot;)&lt;/code&gt;: Define o valor &amp;quot;create&amp;quot; para a chave
&amp;quot;comando&amp;quot;, independentemente do alias usado (ex: &lt;code&gt;new&lt;/code&gt;, &lt;code&gt;add&lt;/code&gt; ou
&lt;code&gt;create&lt;/code&gt;). Isso evita a necessidade de condicionais (ifs) posteriores para
determinar o comando a ser executado.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos Teóricos Importantes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uso de &lt;code&gt;action=&amp;quot;extend&amp;quot;&lt;/code&gt; para adicionar múltiplos valores a uma lista em
&lt;code&gt;argparse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Utilização de &lt;code&gt;choices&lt;/code&gt; para restringir valores de entrada.&lt;/li&gt;
&lt;li&gt;Definir valores padrão com &lt;code&gt;default&lt;/code&gt; para evitar verificações posteriores no
código.&lt;/li&gt;
&lt;li&gt;Uso de &lt;code&gt;dest&lt;/code&gt; para controlar o nome da chave no dicionário de argumentos.&lt;/li&gt;
&lt;li&gt;Importância e utilização de &lt;code&gt;set_defaults&lt;/code&gt; para definir um valor padrão para
uma chave, simplificando a lógica de tratamento de aliases de comandos.&lt;/li&gt;
&lt;li&gt;Menciona a possibilidade de converter uma lista para um conjunto (&lt;code&gt;set&lt;/code&gt;) e de
volta para lista (&lt;code&gt;list&lt;/code&gt;) para remover valores duplicados, mas alerta para a
possível mudança na ordem dos elementos.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trecho iniciando em 00:49:06&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Objetivo Principal:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Implementar a validação de entrada do usuário para tags (impedindo tags vazias)
e integrar o comando &lt;code&gt;create&lt;/code&gt; com a classe &lt;code&gt;DefaultRunner&lt;/code&gt; para processar os
argumentos fornecidos via linha de comando.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Linguagens/Ferramentas:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Argparse (biblioteca Python para parsing de argumentos de linha de comando)&lt;/li&gt;
&lt;li&gt;Namedtuple (estrutura de dados Python)&lt;/li&gt;
&lt;li&gt;TypedDict (para tipagem de dicionários em Python)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos Práticos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Validação de Tags:&lt;/strong&gt; Adiciona validação para impedir a inclusão de tags
vazias, semelhante à validação já existente para a tarefa (&lt;code&gt;task&lt;/code&gt;).
Demonstra o erro gerado ao tentar inserir uma tag vazia e o sucesso ao
inserir uma tag com valor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uso de Aspas:&lt;/strong&gt; Explica a necessidade de usar aspas simples ou duplas para
valores com espaços em comandos de linha de comando, para que sejam tratados
como um único valor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integração com &lt;code&gt;DefaultRunner&lt;/code&gt;:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Comenta os comandos de exemplo anteriores no &lt;code&gt;runner&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Define o &lt;code&gt;default_runner&lt;/code&gt; como uma instância da classe &lt;code&gt;DefaultRunner&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Importa a classe &lt;code&gt;DefaultRunner&lt;/code&gt; do módulo &lt;code&gt;tasks.runners&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Remove a verificação condicional &lt;code&gt;if args.command&lt;/code&gt; por ser redundante (um
comando sempre é fornecido).&lt;/li&gt;
&lt;li&gt;Usa &lt;code&gt;getattr&lt;/code&gt; para obter o método correspondente ao comando fornecido
(&lt;code&gt;args.command&lt;/code&gt;) da instância &lt;code&gt;DefaultRunner&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Executa o método obtido, passando os argumentos (&lt;code&gt;args&lt;/code&gt;) convertidos em um
dicionário.&lt;/li&gt;
&lt;li&gt;Imprime o dicionário de argumentos (&lt;code&gt;arguments&lt;/code&gt;) para demonstração.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conversão para Dicionário:&lt;/strong&gt; Utiliza &lt;code&gt;vars()&lt;/code&gt; para converter o objeto
&lt;code&gt;Namespace&lt;/code&gt; do &lt;code&gt;argparse&lt;/code&gt; em um dicionário.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;mapDictTrueTaskParams&lt;/code&gt;:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Explica a função &lt;code&gt;mapDictTrueTaskParams&lt;/code&gt;, que recebe um dicionário e
retorna um &lt;code&gt;TypedDict&lt;/code&gt; chamado &lt;code&gt;TaskParamData&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Detalhes do &lt;code&gt;TaskParamData&lt;/code&gt;:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;task&lt;/code&gt;: obrigatório.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;done&lt;/code&gt;: booleano, não obrigatório.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tags&lt;/code&gt;: tupla de strings, não obrigatório.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;priority&lt;/code&gt;: tipo &lt;code&gt;Priority&lt;/code&gt; (literal &amp;#39;low&amp;#39;, &amp;#39;medium&amp;#39; ou &amp;#39;high&amp;#39;), não
obrigatório.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A função filtra o dicionário de entrada, mantendo apenas as chaves
presentes em &lt;code&gt;task.fields&lt;/code&gt; (obtidas de uma &lt;code&gt;Namedtuple&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Usa um &lt;em&gt;dictionary comprehension&lt;/em&gt; para criar o novo dicionário filtrado.&lt;/li&gt;
&lt;li&gt;Realiza um cast para garantir a tipagem correta.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos Teóricos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uso de aspas em argumentos de linha de comando com espaços.&lt;/li&gt;
&lt;li&gt;Utilização de &lt;code&gt;getattr&lt;/code&gt; para acessar atributos dinamicamente.&lt;/li&gt;
&lt;li&gt;Conversão de &lt;code&gt;Namespace&lt;/code&gt; do &lt;code&gt;argparse&lt;/code&gt; para dicionário usando &lt;code&gt;vars()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Uso de &lt;code&gt;Namedtuple&lt;/code&gt; e &lt;code&gt;TypedDict&lt;/code&gt; para estruturar e tipar dados.&lt;/li&gt;
&lt;li&gt;Filtragem de dicionários com &lt;em&gt;dictionary comprehension&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Importância da validação de entradas do usuário para evitar problemas no
software.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trecho iniciando em 00:56:07&lt;/h2&gt;
&lt;p&gt;Resumo detalhado do trecho (começando em 00:56:07):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Objetivo Principal:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Demonstrar e explicar a criação de uma task (tarefa) utilizando um padrão de
projeto Repository e persistindo os dados em um banco de dados TinyDB, mostrando
a integração entre diferentes módulos do projeto e o fluxo de execução.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias, Linguagens de Programação ou Ferramentas Mencionadas:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Python:&lt;/strong&gt; Linguagem principal utilizada no projeto.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TinyDB:&lt;/strong&gt; Banco de dados NoSQL orientado a documentos, utilizado para
persistir as tasks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON:&lt;/strong&gt; Formato de dados utilizado para armazenar as tasks no TinyDB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rich:&lt;/strong&gt; Biblioteca Python para formatação de texto e criação de interfaces
de linha de comando, utilizada para exibir tabelas e mensagens formatadas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Padrão de Projeto Repository:&lt;/strong&gt; Padrão de projeto que abstrai a lógica de
acesso a dados, permitindo a troca de diferentes tipos de bancos de dados sem
afetar a aplicação.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(Mencionado) Patterns of Enterprise Application Architecture, de Martin
Fowler:&lt;/strong&gt; Livro de referência sobre padrões de projeto.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos Práticos/Comandos Executados:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;repository.create()&lt;/code&gt;:&lt;/strong&gt; Chama o método &lt;code&gt;create&lt;/code&gt; do repositório para criar
uma nova task. O método recebe os dados da task.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verificação de Existência da Task:&lt;/strong&gt; Internamente, no método &lt;code&gt;create&lt;/code&gt;,
verifica se já existe uma task com a mesma descrição. Se existir, retorna um
erro e &lt;code&gt;None&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conversão de Tags:&lt;/strong&gt; Converte as tags da task em uma tupla.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inserção no TinyDB:&lt;/strong&gt; Insere a nova task no banco de dados TinyDB e
recupera o ID gerado automaticamente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retorno dos Dados:&lt;/strong&gt; Retorna um objeto &lt;code&gt;Task&lt;/code&gt; com os dados da task criada,
incluindo o ID gerado pelo TinyDB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;log_task_table.find_all()&lt;/code&gt;:&lt;/strong&gt; Chama o método &lt;code&gt;find_all&lt;/code&gt; do módulo
&lt;code&gt;log_task_table&lt;/code&gt;, que utiliza o repositório para buscar todas as tasks no
TinyDB e exibi-las em uma tabela formatada com Rich.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exibição de Mensagem de Sucesso:&lt;/strong&gt; Exibe uma mensagem de sucesso
utilizando a biblioteca Rich.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Demonstração de erro ao tentar criar task duplicada:&lt;/strong&gt; Mostra o erro
gerado ao tentar criar uma task com a mesma descrição de uma já existente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Criação de uma nova task com dados diferentes:&lt;/strong&gt; Demonstra a criação de
uma segunda task com uma descrição diferente e tags diferentes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Dicas ou Conceitos Teóricos Importantes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Padrão Repository:&lt;/strong&gt; O apresentador explica o conceito do padrão Repository
e sua importância para abstrair a lógica de acesso a dados, permitindo
flexibilidade na escolha do banco de dados.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TinyDB - Geração Automática de ID:&lt;/strong&gt; O TinyDB gera automaticamente o ID para
as tasks, simplificando o processo de criação.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uso de Tipagem em Python:&lt;/strong&gt; Menciona o uso de tipagem (type hinting) no
código para melhorar a legibilidade e facilitar a manutenção.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Módulos para Logs e Tabelas:&lt;/strong&gt; O apresentador demonstra a separação de
responsabilidades em diferentes módulos, como &lt;code&gt;log_task_table&lt;/code&gt;, para organizar
o código e facilitar a reutilização.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tratamento de Erros:&lt;/strong&gt; Mostra a verificação de existência da task antes da
criação e o tratamento do erro caso a task já exista.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trecho iniciando em 01:03:08&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Objetivo Principal:&lt;/strong&gt; Adicionar o subcomando &lt;code&gt;all&lt;/code&gt; e &lt;code&gt;search&lt;/code&gt; (ou &lt;code&gt;find&lt;/code&gt;) a um
parser de linha de comando para gerenciamento de tarefas (tasks).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Linguagens/Ferramentas:&lt;/strong&gt; Python (implicito pelo uso de termos
como &amp;quot;parser&amp;quot;, &amp;quot;add_parser&amp;quot;) e possivelmente uma biblioteca de parsing de
argumentos de linha de comando (como &lt;code&gt;argparse&lt;/code&gt;). Um chatbot (ChatGPT) foi usado
para gerar a descrição do subcomando &lt;code&gt;search&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passos Práticos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Subcomando &lt;code&gt;all&lt;/code&gt;:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Cria um parser para o subcomando &lt;code&gt;all&lt;/code&gt; usando &lt;code&gt;add_parser(&amp;quot;all&amp;quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Define a descrição (&lt;code&gt;description&lt;/code&gt;) como &amp;quot;show all tasks&amp;quot;.&lt;/li&gt;
&lt;li&gt;Define o texto de ajuda (&lt;code&gt;help&lt;/code&gt;) com a mesma descrição.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subcomando &lt;code&gt;search&lt;/code&gt;:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Copia o código do subcomando &lt;code&gt;create&lt;/code&gt; como base.&lt;/li&gt;
&lt;li&gt;Renomeia variáveis e ajusta o código para o subcomando &lt;code&gt;search&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Adiciona o alias &lt;code&gt;find&lt;/code&gt; usando &lt;code&gt;aliases=[&amp;quot;find&amp;quot;]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Gera a descrição (&lt;code&gt;epílogo&lt;/code&gt;) usando o ChatGPT.&lt;/li&gt;
&lt;li&gt;Define os argumentos para o subcomando &lt;code&gt;search&lt;/code&gt;:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;task&lt;/code&gt;: não requerido (&lt;code&gt;required=False&lt;/code&gt;), valor padrão &lt;code&gt;known&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;destination&lt;/code&gt;: mantém o comportamento anterior (sem modificações
explícitas no trecho).&lt;/li&gt;
&lt;li&gt;Mantém outros argumentos presentes no subcomando &lt;code&gt;create&lt;/code&gt;, ajustando
descrições e valores padrão (&lt;code&gt;default&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Adiciona argumento &lt;code&gt;limit&lt;/code&gt; (não detalhado no trecho, mas mencionado como
diferencial em relação ao &lt;code&gt;create&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos Teóricos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uso de &lt;code&gt;known&lt;/code&gt; como valor padrão para argumentos não fornecidos pelo usuário,
permitindo tratar esses casos na lógica de busca.&lt;/li&gt;
&lt;li&gt;Diferenciação entre &lt;code&gt;help&lt;/code&gt; (texto curto e sem quebras de linha) e
&lt;code&gt;description&lt;/code&gt;/&lt;code&gt;epílogo&lt;/code&gt; (textos mais detalhados, permitindo quebras de linha).&lt;/li&gt;
&lt;li&gt;Uso de &lt;code&gt;metavar&lt;/code&gt; para descrever o valor do argumento na ajuda exibida ao
usuário.&lt;/li&gt;
&lt;li&gt;Uso de expressões regulares permitidas na busca de tarefas (mencionado na
descrição do argumento &lt;code&gt;task&lt;/code&gt; do subcomando &lt;code&gt;search&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Limitação da quantidade de elementos exibidos na tela através do parâmetro
&lt;code&gt;limit&lt;/code&gt; no subcomando &lt;code&gt;search&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trecho iniciando em 01:10:09&lt;/h2&gt;
&lt;p&gt;Resumo detalhado do trecho (começando em 01:10:09):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Objetivo principal:&lt;/strong&gt; Descrever a implementação de argumentos de linha de
comando para as funcionalidades de busca (search), listagem (all) e deleção
(delete) de tarefas (tasks) em uma aplicação.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Linguagens/Ferramentas:&lt;/strong&gt; Python (argparse).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Busca (search):&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Argumentos opcionais: &lt;code&gt;down&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt;, &lt;code&gt;priority&lt;/code&gt;, &lt;code&gt;limit&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;down&lt;/code&gt;: Se não fornecido, o valor padrão é &lt;code&gt;None&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tags&lt;/code&gt;: Se não fornecido, o valor padrão é &lt;code&gt;None&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;priority&lt;/code&gt;: Se não fornecido, o valor padrão é &lt;code&gt;None&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;limit&lt;/code&gt; (com flag &lt;code&gt;-L&lt;/code&gt; ou &lt;code&gt;--limit&lt;/code&gt;): Limita o número de tarefas retornadas
pela busca. O valor padrão é 10. Há validação para garantir que o valor seja
um inteiro positivo. Um erro é lançado se o valor for inválido.&lt;/li&gt;
&lt;li&gt;Se nenhum argumento for fornecido para a busca, o comportamento é
equivalente à listagem de todas as tarefas (all).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deleção (delete):&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Argumentos: &lt;code&gt;id&lt;/code&gt; (obrigatório, com flag &lt;code&gt;-i&lt;/code&gt; ou &lt;code&gt;--task-id&lt;/code&gt;), &lt;code&gt;force&lt;/code&gt;
(opcional).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt;: ID da tarefa a ser deletada. Tipo inteiro.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;force&lt;/code&gt; (com flag &lt;code&gt;--force&lt;/code&gt;): Força a deleção sem pedir confirmação. Se não
fornecido, o valor padrão é &lt;code&gt;false&lt;/code&gt;. A ação &lt;code&gt;store_true&lt;/code&gt; é usada, o que
significa que se a flag &lt;code&gt;--force&lt;/code&gt; estiver presente, o valor será &lt;code&gt;true&lt;/code&gt;,
caso contrário, será &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Se o &lt;code&gt;id&lt;/code&gt; não for fornecido, um erro é exibido.&lt;/li&gt;
&lt;li&gt;Se &lt;code&gt;force&lt;/code&gt; não for fornecido, o usuário é solicitado a confirmar a deleção
com &amp;quot;yes&amp;quot; ou &amp;quot;no&amp;quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uso de &lt;code&gt;None&lt;/code&gt; como valor padrão para argumentos opcionais quando a ausência de
um valor precisa ser diferenciada de um valor vazio.&lt;/li&gt;
&lt;li&gt;Validação de entrada do usuário para garantir que os valores fornecidos sejam
do tipo correto e estejam dentro dos limites esperados (exemplo: &lt;code&gt;limit&lt;/code&gt; deve
ser um inteiro positivo).&lt;/li&gt;
&lt;li&gt;Uso de &lt;code&gt;argparse&lt;/code&gt; para lidar com argumentos de linha de comando em Python.&lt;/li&gt;
&lt;li&gt;Uso de &lt;code&gt;store_true&lt;/code&gt; para criar uma flag booleana em &lt;code&gt;argparse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Implementação de um mecanismo de confirmação antes de executar ações
destrutivas, como a deleção de dados.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trecho iniciando em 01:17:09&lt;/h2&gt;
&lt;p&gt;Resumo detalhado do trecho (começando em 01:17:09):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Objetivo Principal:&lt;/strong&gt; Demonstrar o funcionamento do comando &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;one&lt;/code&gt;
(find one) e &lt;code&gt;search&lt;/code&gt; de uma ferramenta de linha de comando, provavelmente para
gerenciamento de tarefas, e explicar a lógica por trás da busca com múltiplos
critérios.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Linguagens/Ferramentas:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Python:&lt;/strong&gt; Linguagem usada para implementar a ferramenta.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TinyDB:&lt;/strong&gt; Banco de dados NoSQL usado para armazenar as tarefas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expressões regulares:&lt;/strong&gt; Usadas para realizar buscas mais complexas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Terminal/Linha de Comando:&lt;/strong&gt; Interface utilizada para interagir com a
ferramenta.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git:&lt;/strong&gt; Mencionado indiretamente através de referências a branches e ao
arquivo README.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Teste do comando &lt;code&gt;delete&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Executa o comando &lt;code&gt;getttr&lt;/code&gt; (provavelmente para obter as tags existentes).&lt;/li&gt;
&lt;li&gt;Executa o comando &lt;code&gt;delete 1&lt;/code&gt; (para deletar a tarefa com ID 1), confirmando
a operação.&lt;/li&gt;
&lt;li&gt;Executa o comando &lt;code&gt;delete 2 -f&lt;/code&gt; (ou &lt;code&gt;delete 2 --force&lt;/code&gt;) para deletar a
tarefa com ID 2 sem confirmação.&lt;/li&gt;
&lt;li&gt;Executa o comando &lt;code&gt;delete 1&lt;/code&gt; novamente (para demonstrar a mensagem de erro
quando a tarefa não existe).&lt;/li&gt;
&lt;li&gt;Executa &lt;code&gt;task all&lt;/code&gt; para mostrar que não há mais tarefas.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Criação de Tarefas de Teste:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Executa um comando longo (não mostrado completamente) para criar várias
tarefas de teste.&lt;/li&gt;
&lt;li&gt;Executa &lt;code&gt;task all&lt;/code&gt; para mostrar as tarefas criadas.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implementação e Teste do comando &lt;code&gt;one&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copia código do comando &lt;code&gt;delete&lt;/code&gt; para criar o comando &lt;code&gt;one&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Adapta o código para buscar uma única tarefa pelo ID.&lt;/li&gt;
&lt;li&gt;Executa comandos como &lt;code&gt;task one -i 2&lt;/code&gt;, &lt;code&gt;task one -i 3&lt;/code&gt;, etc., para testar
a busca por ID.&lt;/li&gt;
&lt;li&gt;Testa com um ID inexistente (100) para demonstrar a mensagem de erro.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Explicação e Teste do comando &lt;code&gt;search&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Explica a lógica de construção de consultas com o TinyDB, utilizando
operadores lógicos (AND, OR, NOT) e expressões regulares.&lt;/li&gt;
&lt;li&gt;Menciona o uso de um método &lt;code&gt;NOP&lt;/code&gt; (No Operation) para lidar com consultas
vazias.&lt;/li&gt;
&lt;li&gt;Mostra como combinar critérios de busca (task, done, tags, priority).&lt;/li&gt;
&lt;li&gt;Executa o comando &lt;code&gt;search&lt;/code&gt; sem parâmetros para mostrar as 10 primeiras
tarefas.&lt;/li&gt;
&lt;li&gt;Executa o comando &lt;code&gt;search -t&lt;/code&gt; com uma expressão regular para demonstrar a
busca por texto na descrição da tarefa.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos Teóricos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uso de expressões regulares para buscas flexíveis.&lt;/li&gt;
&lt;li&gt;Construção de consultas complexas com o TinyDB, combinando múltiplos critérios
com operadores lógicos.&lt;/li&gt;
&lt;li&gt;Uso da flag &lt;code&gt;-f&lt;/code&gt; ou &lt;code&gt;--force&lt;/code&gt; para suprimir a confirmação em comandos de
deleção.&lt;/li&gt;
&lt;li&gt;Demonstração de mensagens de erro apropriadas para diferentes situações
(tarefa não encontrada, etc.).&lt;/li&gt;
&lt;li&gt;Importância de testes com diferentes cenários (sucesso, erro, etc.).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trecho iniciando em 01:24:10&lt;/h2&gt;
&lt;p&gt;Resumo detalhado do trecho (começando em 01:24:10):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Objetivo Principal:&lt;/strong&gt; Demonstrar a integração da biblioteca &lt;code&gt;argparse&lt;/code&gt; em um
programa Python já funcional para criar uma interface de linha de comando,
permitindo filtrar tarefas com diferentes critérios. Após a demonstração, o
apresentador discute brevemente sobre o empacotamento do projeto usando
&lt;code&gt;pi project.tomo&lt;/code&gt; e &lt;code&gt;v sync&lt;/code&gt; ou &lt;code&gt;v pip install -e .&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Linguagens/Ferramentas:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;&lt;code&gt;argparse&lt;/code&gt; (biblioteca Python para parsing de argumentos de linha de comando)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pi project.tomo&lt;/code&gt; (ferramenta para gerenciamento de projetos Python)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v sync&lt;/code&gt; e &lt;code&gt;v pip install -e .&lt;/code&gt; (comandos para build e instalação de pacotes
Python no modo editável, provavelmente relacionados a um ambiente virtual)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos Práticos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;O apresentador demonstra diversos comandos de linha de comando usando o programa
modificado com &lt;code&gt;argparse&lt;/code&gt;. Os exemplos incluem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Busca por texto na descrição da tarefa (ex: &lt;code&gt;python&lt;/code&gt;): Inicialmente, apresenta
problemas por não incluir o argumento &lt;code&gt;search&lt;/code&gt;. Posteriormente, corrige o
comando.&lt;/li&gt;
&lt;li&gt;Filtrar tarefas concluídas: &lt;code&gt;--done&lt;/code&gt; e &lt;code&gt;--no-done&lt;/code&gt; (ou &lt;code&gt;-d&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Filtrar por prioridade: &lt;code&gt;-p high&lt;/code&gt;, &lt;code&gt;-p medium&lt;/code&gt;, &lt;code&gt;-p low&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Filtrar por tags: &lt;code&gt;--tag trabalho&lt;/code&gt;, &lt;code&gt;--tag slides&lt;/code&gt;, &lt;code&gt;--tag python&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Combinar filtros: &lt;code&gt;-t python --no-done -p high --tag &amp;quot;python&amp;quot;&lt;/code&gt; (usando aspas
para evitar problemas com o argumento &lt;code&gt;python&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Limitar o número de resultados: &lt;code&gt;limit 1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos Teóricos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uso de aspas em argumentos que contêm espaços.&lt;/li&gt;
&lt;li&gt;Menciona a possibilidade de customização da formatação da saída com classes e
&lt;code&gt;formatter&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Menciona o conceito de &amp;quot;parents&amp;quot; no &lt;code&gt;argparse&lt;/code&gt; para evitar repetição de
argumentos em subparsers.&lt;/li&gt;
&lt;li&gt;Recomenda a leitura da documentação oficial do &lt;code&gt;argparse&lt;/code&gt; e do tutorial da
própria linguagem Python para aprofundamento.&lt;/li&gt;
&lt;li&gt;Discute brevemente a criação de um pacote distribuível a partir do projeto,
utilizando &lt;code&gt;pi project.tomo&lt;/code&gt;, &lt;code&gt;v-sync&lt;/code&gt; e &lt;code&gt;v pip install&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Finalizando o Processo&lt;/h2&gt;
&lt;p&gt;Depois de receber a análise técnica detalhada (que você viu acima), eu passo
esse conteúdo para o Gemini novamente, com um prompt pedindo para ele extrair as
informações mais importantes para o SEO do meu vídeo no Youtube. O resultado é
exatamente este que mostro abaixo, sem edições:&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Resultado Final (Conteúdo para o YouTube)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Capítulos para o YouTube:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;00:00:00 Introdução e Configuração do Projeto&lt;/li&gt;
&lt;li&gt;00:07:02 Criando a Estrutura Básica do CLI com Argparse&lt;/li&gt;
&lt;li&gt;00:14:02 Customizando o Help e Adicionando Subcomandos (subparsers)&lt;/li&gt;
&lt;li&gt;00:21:03 Criando o subparser &amp;quot;create&amp;quot; com Argparse&lt;/li&gt;
&lt;li&gt;00:28:03 Validação de Argumentos com Argparse&lt;/li&gt;
&lt;li&gt;00:35:04 Usando Actions e nargs no Argparse (BooleanOptionalAction)&lt;/li&gt;
&lt;li&gt;00:42:05 Múltiplos Valores (listas) e Choices no Argparse (action extend)&lt;/li&gt;
&lt;li&gt;00:49:06 Validação de Tags (lista de strings) e Integração com o app&lt;/li&gt;
&lt;li&gt;00:56:07 Criando uma Task e Persistindo com TinyDB&lt;/li&gt;
&lt;li&gt;01:03:08 Adicionando os Subcomandos (subparsers) all e search&lt;/li&gt;
&lt;li&gt;01:10:09 Implementando Argumentos para Busca, Listagem e Deleção&lt;/li&gt;
&lt;li&gt;01:17:09 Testando os Comandos delete, one e search&lt;/li&gt;
&lt;li&gt;01:24:10 Demonstração Final e Empacotamento do Projeto&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Descrição para o YouTube (SEO Otimizado):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Domine a arte de criar interfaces de linha de comando (CLIs) profissionais e
robustas em Python usando o poderoso módulo &lt;code&gt;argparse&lt;/code&gt;! Neste tutorial completo,
vamos construir uma CLI do zero para um projeto real, desde a configuração
inicial até a implementação de recursos avançados como subcomandos, validação de
entrada, e persistência de dados com TinyDB. Aprenda as melhores práticas e
dicas para tornar suas ferramentas de linha de comando mais intuitivas,
eficientes e fáceis de usar.&lt;/p&gt;
&lt;p&gt;Após assistir a este vídeo, você será capaz de:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Criar CLIs interativas em Python com &lt;code&gt;argparse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Implementar subcomandos para organizar a estrutura do seu CLI.&lt;/li&gt;
&lt;li&gt;Validar entradas do usuário para garantir a robustez do seu programa.&lt;/li&gt;
&lt;li&gt;Persistir dados em um banco de dados TinyDB.&lt;/li&gt;
&lt;li&gt;Utilizar bibliotecas como &lt;code&gt;rich&lt;/code&gt; e &lt;code&gt;rich-argparse&lt;/code&gt; para aprimorar a aparência
do seu CLI.&lt;/li&gt;
&lt;li&gt;Entender e aplicar conceitos como &lt;code&gt;actions&lt;/code&gt;, &lt;code&gt;nargs&lt;/code&gt;, &lt;code&gt;choices&lt;/code&gt; e &lt;code&gt;metavar&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Empacotar seu projeto Python para distribuição.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Principais tópicos abordados:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Introdução ao &lt;code&gt;argparse&lt;/code&gt; e configuração do projeto.&lt;/li&gt;
&lt;li&gt;Criação da estrutura básica da CLI.&lt;/li&gt;
&lt;li&gt;Customização da ajuda (help) e adição de subcomandos.&lt;/li&gt;
&lt;li&gt;Validação de argumentos de entrada.&lt;/li&gt;
&lt;li&gt;Uso de &lt;code&gt;actions&lt;/code&gt; para opções booleanas e listas.&lt;/li&gt;
&lt;li&gt;Lidando com múltiplos valores e choices.&lt;/li&gt;
&lt;li&gt;Integração com TinyDB para persistência de dados.&lt;/li&gt;
&lt;li&gt;Implementação dos comandos &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;all&lt;/code&gt; e &lt;code&gt;delete&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Testes e demonstração final.&lt;/li&gt;
&lt;li&gt;Empacotamento do projeto com &lt;code&gt;uv&lt;/code&gt; e &lt;code&gt;pyproject.toml&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Quer aprender a criar ferramentas de linha de comando incríveis em Python? Então
este vídeo é para você! Assista agora e leve suas habilidades de desenvolvimento
Python para o próximo nível.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keywords:&lt;/strong&gt; Python, argparse, CLI, interface de linha de comando, tutorial,
TinyDB, rich, rich-argparse, validação de dados, subcomandos, Python CLI, criar
CLI, desenvolvimento Python, ferramentas de linha de comando, empacotamento
Python, pyproject.toml, uv sync, uv pip install, programação, tutorial argparse,
argparse python, criar programa linha de comando, linha de comando python.&lt;/p&gt;
</content:encoded></item><item><title>Notas técnicas sobre o vídeo Whisper OpenAI CLI do Youtube</title><link>https://otaviomiranda.com.br/2025/notas-tecnicas-whisper-openai-guia-completo-de-transcricao-com-inteligencia-artificial-video-e-audio/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2025/notas-tecnicas-whisper-openai-guia-completo-de-transcricao-com-inteligencia-artificial-video-e-audio/</guid><description>Acesse as notas técnicas detalhadas sobre o vídeo Whisper OpenAI CLI do Youtube. São coisas gerados por IA através da legenda SRT do vídeo.</description><pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Fala aí! 👋 Este post mostra como usei a legenda &lt;code&gt;.srt&lt;/code&gt; do vídeo abaixo para
gerar resumos, descrições otimizadas para SEO, hashtags, capítulos e versões em
outros idiomas, tudo com o apoio de ferramentas de IA.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Guia Completo do OpenAI Whisper - &lt;a href=&quot;https://youtu.be/Ad6934NXn4A&quot;&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Se você caiu aqui de paraquedas, recomendo começar por esse vídeo. Também falo
sobre ele em mais detalhes no post principal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CLI educacional com OpenAI Whisper -
&lt;a href=&quot;/2025/python-sussu-cli-openai-whisper/&quot;&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;⚙️ Ferramentas e fluxo com IA (bastidores)&lt;/h2&gt;
&lt;p&gt;Para montar os conteúdos derivados do vídeo (resumos, descrições, títulos,
etc.), sigo um processo simples e replicável:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Gravação:&lt;/strong&gt; Câmera Sony A6100 + lente Yongnuo 50mm f/1.8 Microfone Shure
MV7 Gravação no OBS Studio&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edição leve:&lt;/strong&gt; Uso o &lt;code&gt;ffmpeg&lt;/code&gt; pra compactar o vídeo final Removo silêncios
automaticamente com o &lt;code&gt;auto-editor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transcrição com IA:&lt;/strong&gt; Rodo o &lt;code&gt;whisper&lt;/code&gt; da OpenAI para transcrever o vídeo&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Correções com LLMs:&lt;/strong&gt; A transcrição original tem erros técnicos (nomes de
libs, ferramentas, etc). Divido a legenda em &lt;strong&gt;chunks (~1000 caracteres)&lt;/strong&gt; e
envio para a &lt;strong&gt;API do Gemini&lt;/strong&gt; corrigir.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Esse chunking é necessário porque algumas legendas passam dos &lt;strong&gt;300 mil
caracteres&lt;/strong&gt;, e IAs como Gemini ou GPT têm limites de contexto.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;🧠 Output do Gemini a partir da legenda SRT&lt;/h2&gt;
&lt;p&gt;A partir daqui, &lt;strong&gt;eu não escrevi absolutamente nada&lt;/strong&gt;. Tudo gerado por IA com
base na legenda &lt;code&gt;.srt&lt;/code&gt; corrigida.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Trecho iniciando em 00:00:00&lt;/h2&gt;
&lt;p&gt;Este trecho introdutório de um tutorial tem como objetivo principal apresentar o
Whisper, um modelo de reconhecimento de fala de código aberto da OpenAI, focando
em seu uso como ferramenta de linha de comando. O apresentador explica que o
vídeo será dividido em duas partes: a primeira (presente neste trecho) aborda o
uso do Whisper via linha de comando, enquanto a segunda (futura) mostrará sua
integração em código (ex: Django).&lt;/p&gt;
&lt;p&gt;As tecnologias e ferramentas mencionadas são:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Whisper (OpenAI):&lt;/strong&gt; Modelo de reconhecimento de fala e transcrição de áudio,
utilizado como ferramenta principal do tutorial. É descrito como gratuito e
open source.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FFmpeg:&lt;/strong&gt; Ferramenta mencionada como sendo utilizada pelo Whisper para
processamento de áudio e vídeo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ChatGPT:&lt;/strong&gt; Usado pelo apresentador para confirmar se a OpenAI utiliza
internamente o Whisper (a resposta foi afirmativa). O ChatGPT é mencionado
como um produto que usa o Whisper para transcrição e compreensão de áudio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speech-to-Text da OpenAI:&lt;/strong&gt; API mencionada como utilizadora do Whisper.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python:&lt;/strong&gt; Linguagem de programação implícita, uma vez que o apresentador
menciona um &amp;quot;script Python&amp;quot; usado para cortar silêncios do áudio e a
integração do Whisper em código será mostrada em um vídeo futuro.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Django:&lt;/strong&gt; Framework Python mencionado como um exemplo de onde o Whisper
poderia ser integrado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gemini:&lt;/strong&gt; API de inteligência artificial utilizada pelo apresentador para
gerar resumos, traduções e otimização SEO a partir da transcrição do seu
próprio vídeo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;argparse (Python):&lt;/strong&gt; Mencionado como exemplo de aplicação do processamento
de transcrições, com um vídeo anterior do apresentador, sobre o tema, servindo
como exemplo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Passos práticos e comandos:&lt;/p&gt;
&lt;p&gt;Embora o vídeo ainda não mostre comandos específicos do Whisper, o apresentador
menciona o uso de um repositório chamado &amp;quot;sussu&amp;quot; (&amp;quot;sussu&amp;quot; (rro)) para executar
comandos do Whisper. Ele também indica a existência de um repositório oficial do
Whisper. O apresentador descreve como um script Python foi utilizado para
pré-processar um arquivo de vídeo (one-auto.mp4), cortando partes de silêncio.&lt;/p&gt;
&lt;p&gt;Conceitos teóricos importantes:&lt;/p&gt;
&lt;p&gt;O apresentador destaca o potencial de usar a transcrição gerada pelo Whisper
para outras tarefas, como gerar resumos, traduções e otimização de SEO usando
outras APIs de IA (como o Gemini). Ele também brevemente discute os pontos
fortes e fracos do Whisper, baseados em sua experiência de seis meses de uso. O
apresentador menciona que o Whisper aceita arquivos de vídeo diretamente, devido
ao uso do FFmpeg.&lt;/p&gt;
&lt;h2&gt;Trecho iniciando em 00:07:00&lt;/h2&gt;
&lt;p&gt;Este trecho do vídeo tutorial tem como objetivo principal explicar como usar o
modelo de transcrição de áudio Whisper, incluindo sua instalação, configuração e
uso básico. São apresentadas diversas funcionalidades do Whisper, além de
alternativas para tarefas relacionadas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias, linguagens de programação e ferramentas mencionadas:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Whisper:&lt;/strong&gt; Modelo de transcrição e tradução de áudio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FFmpeg:&lt;/strong&gt; Ferramenta usada pelo Whisper para manipulação de áudio e vídeo.
Sua instalação é um passo prévio essencial.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NLLB (&amp;quot;No Languages Left Behind&amp;quot;):&lt;/strong&gt; Sistema de tradução para múltiplos
idiomas, apresentado como alternativa ao Whisper para traduções além do
inglês.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gemini:&lt;/strong&gt; Mencionado como uma alternativa mais robusta (e paga) para
tradução.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python 3.11:&lt;/strong&gt; Versão específica do Python necessária para compatibilidade
com o Whisper.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;uv&lt;/code&gt; (provavelmente um gerenciador de pacotes):&lt;/strong&gt; Utilizado para gerenciar a
instalação e configuração do projeto Whisper.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;git&lt;/code&gt;:&lt;/strong&gt; Usado para clonar o repositório do projeto (&lt;code&gt;sussu&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sussu&lt;/code&gt; (repositório do apresentador):&lt;/strong&gt; Simplifica a instalação e
configuração do Whisper.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto-editor:&lt;/strong&gt; Ferramenta (não baseada em IA) para cortar silêncios em
vídeos, comparada com a capacidade de detecção de atividade de voz (VAD) do
Whisper.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos práticos e comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instalação do FFmpeg (comandos específicos para Debian, Arch Linux, MacOS e
Windows são fornecidos, mas não detalhados na transcrição).&lt;/li&gt;
&lt;li&gt;Clonagem do repositório &lt;code&gt;sussu&lt;/code&gt; usando &lt;code&gt;git clone&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Uso de &lt;code&gt;uv sync&lt;/code&gt; para instalar dependências, incluindo Python 3.11 e o próprio
Whisper.&lt;/li&gt;
&lt;li&gt;Uso de &lt;code&gt;uv run whisper&lt;/code&gt; ou execução direta do comando &lt;code&gt;whisper&lt;/code&gt; (após ativação
do ambiente virtual) para executar o programa.&lt;/li&gt;
&lt;li&gt;Execução do comando &lt;code&gt;whisper [caminho do arquivo]&lt;/code&gt; para transcrever um arquivo
de áudio ou vídeo (exemplo: &lt;code&gt;whisper one-auto.mp4&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Dicas e conceitos teóricos importantes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;O Whisper não processa áudio diretamente, mas sim um espectrograma log-mel,
representando o áudio como uma imagem.&lt;/li&gt;
&lt;li&gt;O Whisper processa o áudio em trechos de 30 segundos.&lt;/li&gt;
&lt;li&gt;O Whisper realiza transcrição multilíngue, mas tradução somente para inglês, a
menos que se utilize ferramentas complementares como NLLB ou Gemini.&lt;/li&gt;
&lt;li&gt;O Whisper possui a funcionalidade de detecção de atividade de voz (VAD), útil
para tarefas como corte de silêncios em vídeos, superando a precisão de
métodos baseados apenas em ondas sonoras.&lt;/li&gt;
&lt;li&gt;O Whisper utiliza o FFmpeg internamente.&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;uv&lt;/code&gt; instala e configura automaticamente o ambiente Python necessário,
incluindo a instalação e compilação do Whisper e do repositório &lt;code&gt;sussu&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A transcrição apresenta diversos argumentos e opções do comando &lt;code&gt;whisper&lt;/code&gt;, mas
não detalha todas as suas funcionalidades. A maior parte dos comandos e detalhes
de configuração são deixados para consulta na documentação do Whisper e no
repositório &lt;code&gt;sussu&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Trecho iniciando em 00:14:01&lt;/h2&gt;
&lt;p&gt;Este trecho do vídeo (00:14:01-00:15:31) demonstra o funcionamento do modelo de
transcrição de áudio Whisper da OpenAI. O objetivo principal é mostrar o
processo de transcrição de um arquivo de áudio utilizando o Whisper, explicando
o que acontece &amp;quot;por baixo dos panos&amp;quot; e as opções disponíveis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias/Ferramentas/Linguagens:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Whisper (OpenAI):&lt;/strong&gt; Modelo de reconhecimento de fala e transcrição.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python:&lt;/strong&gt; Linguagem de programação implícita, pois o apresentador menciona a
biblioteca &lt;code&gt;site-packages&lt;/code&gt; e a função &lt;code&gt;transcribe&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;argparse&lt;/code&gt;:&lt;/strong&gt; Biblioteca Python usada na interface de linha de comando (CLI)
do Whisper.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Espectrograma log-mel:&lt;/strong&gt; Técnica de processamento de sinal de áudio usada
pelo Whisper.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON:&lt;/strong&gt; Formato de saída utilizado pelo Whisper para apresentar os dados da
transcrição.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OBS Studio:&lt;/strong&gt; Software de captura de tela e streaming mencionado para o
processamento de áudio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto-editor:&lt;/strong&gt; Editor de texto mencionado para formatar o arquivo JSON de
saída.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos/Comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;O apresentador demonstra, principalmente, a execução de um comando de linha de
comando que utiliza o modelo &lt;code&gt;turbo&lt;/code&gt; do Whisper para transcrever um arquivo de
áudio (&amp;quot;oneauto.json&amp;quot;). Ele explica que este comando aciona a função
&lt;code&gt;transcribe&lt;/code&gt; interna do Whisper, a qual processa o áudio em janelas de 30
segundos, utilizando os 30 segundos anteriores para contexto. O comando gera
arquivos de saída em diferentes formatos (SRT, TSV, TXT, VTT, JSON). O
apresentador mostra a inspeção do arquivo JSON gerado, destacando a estrutura
com segmentos, IDs, timestamps, texto e tokens. Ele também menciona a
possibilidade de utilizar a API da OpenAI como alternativa para computadores sem
recursos suficientes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dicas/Conceitos Teóricos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;O Whisper não &amp;quot;escuta&amp;quot; o áudio diretamente, mas sim processa um espectrograma
log-mel.&lt;/li&gt;
&lt;li&gt;O modelo utiliza janelas de 30 segundos do áudio, com sobreposição, para a
transcrição, fornecendo contexto.&lt;/li&gt;
&lt;li&gt;A opção de usar FP16 ou FP32 para processamento é mencionada, dependendo da
capacidade da CPU.&lt;/li&gt;
&lt;li&gt;A qualidade do áudio impacta no resultado da transcrição.&lt;/li&gt;
&lt;li&gt;O modelo &lt;code&gt;turbo&lt;/code&gt; do Whisper é rápido, mas requer 6 GB de VRAM.&lt;/li&gt;
&lt;li&gt;A API da OpenAI oferece uma alternativa para usuários sem hardware adequado
para rodar o Whisper localmente.&lt;/li&gt;
&lt;li&gt;A saída JSON contém informações detalhadas sobre os segmentos da transcrição,
incluindo timestamps, texto e tokens.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Em resumo, a seção do vídeo demonstra e explica a utilização do modelo Whisper
para transcrição de áudio, detalhando o processo, a interface de linha de
comando, os formatos de saída e as implicações em termos de recursos
computacionais.&lt;/p&gt;
&lt;h2&gt;Trecho iniciando em 00:21:01&lt;/h2&gt;
&lt;p&gt;O objetivo principal deste trecho do vídeo é explicar como escolher e utilizar
diferentes modelos do Whisper (um modelo de transcrição de áudio para IA)
baseado nos recursos do computador, principalmente a VRAM.&lt;/p&gt;
&lt;p&gt;São mencionadas as seguintes tecnologias/ferramentas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Whisper:&lt;/strong&gt; Modelo de transcrição de áudio da OpenAI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPU (Placa de vídeo):&lt;/strong&gt; Com foco na VRAM (memória da placa de vídeo)
necessária para rodar diferentes modelos do Whisper.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mac M1 Max:&lt;/strong&gt; Um computador que utiliza memória compartilhada entre CPU e
GPU.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python:&lt;/strong&gt; Linguagem de programação utilizada para executar o Whisper.
Menciona-se também o uso do arquivo &lt;code&gt;pyproject.toml&lt;/code&gt; para configuração do
Python.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PyTorch:&lt;/strong&gt; Framework de aprendizado de máquina, mencionado em relação à
compatibilidade com placas Nvidia e CUDA.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CUDA:&lt;/strong&gt; Tecnologia da Nvidia para computação paralela em GPUs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gemini:&lt;/strong&gt; Uma ferramenta (provavelmente uma IA) usada para corrigir e
melhorar as legendas geradas pelo Whisper, com foco na correção de termos
técnicos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modelos do Whisper:&lt;/strong&gt; &lt;code&gt;tiny&lt;/code&gt;, &lt;code&gt;base&lt;/code&gt;, &lt;code&gt;small&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;large&lt;/code&gt;, &lt;code&gt;large-v2&lt;/code&gt;
e &lt;code&gt;turbo&lt;/code&gt;, cada um com diferentes requisitos de VRAM e precisão.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Passos práticos e comandos:&lt;/p&gt;
&lt;p&gt;O apresentador demonstra como selecionar diferentes modelos do Whisper usando o
argumento &lt;code&gt;model&lt;/code&gt; (ex: &lt;code&gt;model tiny&lt;/code&gt;). Ele também mostra como configurar o
&lt;code&gt;device&lt;/code&gt; para usar CPU ou CUDA, e o &lt;code&gt;output_dir&lt;/code&gt; para definir a pasta de saída
das transcrições, usando os argumentos &lt;code&gt;device&lt;/code&gt; e &lt;code&gt;--output_dir&lt;/code&gt;
respectivamente. Ele executa o código do Whisper e mostra como o uso da memória
varia entre diferentes modelos. Ele explica que passa a saída do Whisper para o
Gemini para correção de erros, fornecendo um exemplo de prompt usado para essa
tarefa.&lt;/p&gt;
&lt;p&gt;Conceitos teóricos importantes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;VRAM:&lt;/strong&gt; Memória dedicada da placa de vídeo, crucial para o desempenho do
Whisper. A quantidade de VRAM disponível afeta a escolha do modelo que pode
ser utilizado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memória compartilhada (no Mac M1 Max):&lt;/strong&gt; O sistema operacional compartilha a
memória RAM entre CPU e GPU, permitindo que modelos maiores sejam usados,
embora com possível lentidão.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modelos diferentes do Whisper:&lt;/strong&gt; Existem vários modelos, cada um com um
equilíbrio diferente entre velocidade, precisão e consumo de recursos. Modelos
menores (como &lt;code&gt;tiny&lt;/code&gt;) são mais rápidos e usam menos recursos, mas são menos
precisos, enquanto modelos maiores (como &lt;code&gt;large-v2&lt;/code&gt; e &lt;code&gt;turbo&lt;/code&gt;) são mais
precisos, mas demandam mais recursos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Correção de legendas com Gemini:&lt;/strong&gt; O apresentador utiliza outra IA (Gemini)
para aprimorar a saída do Whisper, focando na correção de termos técnicos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Importância de dar engajamento:&lt;/strong&gt; O apresentador enfatiza a importância de
comentários e curtidas em seus vídeos para motivar a continuidade da produção
de conteúdo.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trecho iniciando em 00:28:05&lt;/h2&gt;
&lt;p&gt;Este trecho do tutorial visa explicar como refinar o uso do Whisper para
transcrição de áudio, focando principalmente nas opções de configuração para
otimizar a precisão e velocidade do processo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias, linguagens e ferramentas:&lt;/strong&gt; O tutorial utiliza o modelo Whisper
para transcrição de áudio, especificamente via linha de comando. A linguagem
utilizada é o inglês, com alguns comandos em português (PT-BR). Menciona-se
também APIs de tradução como Gemini e GPT, embora não sejam utilizadas
diretamente no exemplo. O sistema operacional do apresentador é um Mac.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passos práticos e comandos:&lt;/strong&gt; O apresentador demonstra a construção de um
comando para o Whisper, explicando cada parâmetro:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-o&lt;/code&gt; ou &lt;code&gt;--output_dir&lt;/code&gt;: Define o diretório de saída para os arquivos de
transcrição (no exemplo, &amp;quot;transcriptions&amp;quot;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--device CPU&lt;/code&gt;: Especifica o uso da CPU para processamento.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--FP16&lt;/code&gt;: Controla o uso de precisão de ponto flutuante (FP16, mais rápido,
mas com suporte dependente da máquina; FP32 usado como alternativa).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--language PT&lt;/code&gt;: Define o idioma como Português Brasileiro.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-f&lt;/code&gt; (opcional): Permite escolher o formato de saída da transcrição (padrão
&amp;quot;all&amp;quot;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--task&lt;/code&gt;: Especifica a tarefa, podendo ser &amp;quot;transcribe&amp;quot; (transcrição no idioma
original) ou &amp;quot;translate&amp;quot; (tradução para inglês).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--temperature&lt;/code&gt;: Controla a criatividade do modelo (0 para rigor, 1 para maior
criatividade; o apresentador prefere 0).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--beam_size&lt;/code&gt;: Define o número de hipóteses mantidas em paralelo durante o
processo.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--patience&lt;/code&gt;: Define a paciência do modelo em explorar novas hipóteses após
achar uma aceitável. Multiplica o efeito do &lt;code&gt;--beam_size&lt;/code&gt; quando
&lt;code&gt;--temperature&lt;/code&gt; é 0.&lt;/li&gt;
&lt;li&gt;Modo &lt;code&gt;greedy&lt;/code&gt;: Uma opção para acelerar o processo, utilizando apenas a melhor
hipótese encontrada.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;O apresentador executa o comando com as opções configuradas, embora não mostre o
resultado da execução completa.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dicas e conceitos teóricos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FP16 vs. FP32:&lt;/strong&gt; O tutorial explica a diferença entre esses tipos de
precisão de ponto flutuante, mostrando que FP16 é mais rápido, mas nem sempre
compatível com todos os sistemas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--language&lt;/code&gt;:&lt;/strong&gt; Mostra como especificar o idioma corretamente, evitando
avisos do sistema. Inclui a opção de usar a forma curta (ex: PT) ou longa (ex:
português).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--temperature&lt;/code&gt;:&lt;/strong&gt; Explica o conceito de temperatura como um controle da
criatividade do modelo, relacionando-o com a qualidade do áudio e as opções
&lt;code&gt;--beam_size&lt;/code&gt; e &lt;code&gt;--patience&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--beam_size&lt;/code&gt; e &lt;code&gt;--patience&lt;/code&gt;:&lt;/strong&gt; Detalham a interação entre esses parâmetros
e a temperatura, mostrando como influenciam a velocidade e a precisão da
transcrição. A relação entre &lt;code&gt;--beam_size&lt;/code&gt; e &lt;code&gt;--patience&lt;/code&gt; é explicada, com
&lt;code&gt;--patience&lt;/code&gt; basicamente multiplicando &lt;code&gt;--beam_size&lt;/code&gt; quando &lt;code&gt;--temperature&lt;/code&gt;
é 0.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modo &lt;code&gt;greedy&lt;/code&gt;:&lt;/strong&gt; Apresenta uma forma mais rápida, mas possivelmente menos
precisa, de gerar a transcrição.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;O tutorial demonstra como encontrar a lista de idiomas suportados pelo Whisper,
acessando o código fonte em &lt;code&gt;lib/python/whisper/tokenizer/languages&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Trecho iniciando em 00:35:09&lt;/h2&gt;
&lt;p&gt;Este trecho do tutorial visa explicar como otimizar a geração de legendas usando
o modelo Whisper, focando nos parâmetros que controlam a precisão e o formato da
saída. As tecnologias mencionadas são o modelo de transcrição de áudio Whisper e
suas opções de linha de comando.&lt;/p&gt;
&lt;p&gt;O apresentador detalha o funcionamento dos parâmetros &lt;code&gt;--temperature&lt;/code&gt;,
&lt;code&gt;--beam_size&lt;/code&gt;, &lt;code&gt;--patience&lt;/code&gt;, e &lt;code&gt;--best_of&lt;/code&gt;. Ele explica que &lt;code&gt;--temperature&lt;/code&gt;
controla a criatividade (valores acima de 0 usam &lt;em&gt;sampling&lt;/em&gt;), enquanto
&lt;code&gt;--beam_size&lt;/code&gt; controla o número de hipóteses (Beam Search) consideradas.
&lt;code&gt;--patience&lt;/code&gt; multiplica o número de hipóteses quando &lt;code&gt;--beam_size&lt;/code&gt; é maior que 1
e &lt;code&gt;--temperature&lt;/code&gt; é 0. &lt;code&gt;--best_of&lt;/code&gt; seleciona entre várias amostras, funcionando
principalmente quando &lt;code&gt;--temperature&lt;/code&gt; &amp;gt; 0. O apresentador enfatiza que, na sua
experiência, os valores padrão geralmente são suficientes, exceto em casos de
áudios de baixa qualidade ou com muita gíria. A configuração &lt;code&gt;--temperature 0&lt;/code&gt; e
&lt;code&gt;--beam_size 1&lt;/code&gt; é chamada de &amp;quot;greedy&amp;quot; e tende a ser mais rápida, mas com maior
chance de erros.&lt;/p&gt;
&lt;p&gt;Os passos práticos envolvem a demonstração de como modificar os parâmetros na
linha de comando para gerar legendas. O apresentador mostra como alterar
&lt;code&gt;--temperature&lt;/code&gt; e &lt;code&gt;--beam_size&lt;/code&gt; para ajustar a velocidade e precisão.&lt;/p&gt;
&lt;p&gt;Conceitos teóricos importantes abordados são: o funcionamento do &lt;em&gt;sampling&lt;/em&gt; e
&lt;em&gt;Beam Search&lt;/em&gt;, a relação entre &lt;code&gt;--temperature&lt;/code&gt;, &lt;code&gt;--beam_size&lt;/code&gt; e &lt;code&gt;--patience&lt;/code&gt;, e
a influência da qualidade do áudio na escolha dos parâmetros. O apresentador
também explica que o modelo pode entrar em loop em certos casos, necessitando
ajustes nos parâmetros.&lt;/p&gt;
&lt;p&gt;Finalmente, o apresentador explica e demonstra o uso dos parâmetros
&lt;code&gt;--max_line_width&lt;/code&gt;, &lt;code&gt;--max_line_count&lt;/code&gt;, &lt;code&gt;--words_timestamps&lt;/code&gt;, e
&lt;code&gt;--highlight_words&lt;/code&gt; para controlar o formato e o estilo da legenda gerada. Ele
mostra como limitar o número de caracteres por linha, o número de linhas e como
gerar timestamps por palavra, destacando que &lt;code&gt;--max_line_width&lt;/code&gt; e
&lt;code&gt;--max_words_per_line&lt;/code&gt; são mutuamente exclusivos. A opção &lt;code&gt;--words_timestamps&lt;/code&gt; é
necessária para o &lt;code&gt;--max_line_width&lt;/code&gt; funcionar corretamente e permitir o
destaque de palavras (&lt;code&gt;--highlight_words&lt;/code&gt;). Ele observa que o parâmetro
&lt;code&gt;--max_line_count&lt;/code&gt; não impõe um limite máximo, mas sim define o número fixo de
linhas por legenda.&lt;/p&gt;
&lt;h2&gt;Trecho iniciando em 00:42:09&lt;/h2&gt;
&lt;p&gt;Este trecho do tutorial tem como objetivo principal demonstrar e explicar opções
avançadas do software Whisper para transcrição de áudio e vídeo, focando em
&lt;code&gt;--highlight_words&lt;/code&gt;, &lt;code&gt;--initial_prompt&lt;/code&gt;, e &lt;code&gt;clip_timestamps&lt;/code&gt;, além de como
utilizar o FFmpeg para pré-processamento de vídeos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias, linguagens e ferramentas:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Whisper:&lt;/strong&gt; O software de transcrição de áudio e vídeo da OpenAI, é o foco
central do tutorial. São exploradas várias opções de linha de comando do
Whisper: &lt;code&gt;--highlight_words&lt;/code&gt;, &lt;code&gt;--initial_prompt&lt;/code&gt;, &lt;code&gt;--clip_timestamps&lt;/code&gt;,
&lt;code&gt;words_timestamps&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FFmpeg:&lt;/strong&gt; Uma ferramenta de linha de comando usada para manipulação de
mídia, especificamente para cortar trechos de vídeo antes da transcrição com o
Whisper. O comando exemplificado é
&lt;code&gt;ffmpeg -i &amp;quot;entrada&amp;quot; -c:v copy -c:a copy -ss 00:05:00 -to 00:10:00 &amp;quot;saida&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Passos práticos e comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;O apresentador demonstra o uso do parâmetro &lt;code&gt;--highlight_words True&lt;/code&gt; no Whisper
para sublinhar as palavras faladas no vídeo correspondente ao tempo de fala,
criando um efeito semelhante a karaokê. Ele demonstra a geração de legendas com
o Whisper, incluindo a opção &lt;code&gt;--highlight_words&lt;/code&gt;. Explica o parâmetro
&lt;code&gt;--initial_prompt&lt;/code&gt;, que fornece um contexto inicial (primeiros 30 segundos) para
o Whisper, mas alerta sobre seu uso limitado e potencial para causar problemas
se usado incorretamente. Ele sugere duas formas de testar o &lt;code&gt;--initial_prompt&lt;/code&gt;:
cortar o vídeo com o FFmpeg antes de usar o Whisper ou usar o parâmetro
&lt;code&gt;--clip_timestamps&lt;/code&gt; do Whisper para especificar um intervalo de tempo. Também
explica como o Whisper, por padrão, utiliza os 30 segundos anteriores de áudio
como contexto para a transcrição, a partir do segundo 30.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dicas e conceitos teóricos importantes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--highlight_words&lt;/code&gt;:&lt;/strong&gt; Permite sublinhar as palavras no vídeo conforme são
faladas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--initial_prompt&lt;/code&gt;:&lt;/strong&gt; Permite fornecer um contexto inicial ao Whisper para
melhorar a precisão nos primeiros 30 segundos do vídeo. O apresentador adverte
que esta opção pode levar a resultados inesperados se usada incorretamente,
como legendas muito longas ou loops de repetição. Ele a compara a dar uma dica
para um cantor antes de um show.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;words_timestamps&lt;/code&gt;:&lt;/strong&gt; É um parâmetro necessário para algumas das opções mais
avançadas do Whisper e pode tornar o processo de transcrição mais lento.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uso do FFmpeg para pré-processamento:&lt;/strong&gt; Cortar um vídeo com o FFmpeg antes
de usar o Whisper pode ser útil para testar o parâmetro &lt;code&gt;--initial_prompt&lt;/code&gt; ou
para transcrever apenas uma parte do vídeo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contexto no Whisper:&lt;/strong&gt; O Whisper usa, por padrão, 30 segundos de contexto do
áudio anterior para melhorar a precisão da transcrição.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Em resumo, a seção do vídeo foca em aprimorar a precisão e adicionar recursos à
transcrição com o Whisper, utilizando opções avançadas e demonstrando como lidar
com possíveis problemas, incluindo o uso de ferramentas externas como o FFmpeg
para auxiliar no processo.&lt;/p&gt;
&lt;h2&gt;Trecho iniciando em 00:49:09&lt;/h2&gt;
&lt;p&gt;Este trecho do tutorial visa demonstrar e explicar parâmetros avançados do
modelo de transcrição Whisper, focando em como manipular a saída de texto
gerado. As tecnologias envolvidas são o modelo de transcrição de áudio Whisper
da OpenAI e a linguagem de programação Python.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Objetivo principal:&lt;/strong&gt; Mostrar como controlar a geração de legendas com
parâmetros específicos do Whisper, incluindo a supressão de tokens e o
tratamento de penalidades de comprimento. Além disso, o objetivo é demonstrar
como entender e manipular os tokens gerados pelo modelo usando a biblioteca
Python do Whisper.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias, linguagens e ferramentas:&lt;/strong&gt; O modelo de transcrição Whisper, a
linguagem Python e sua biblioteca associada. Especificamente, são utilizadas
funções como &lt;code&gt;get_tokenizer&lt;/code&gt;, &lt;code&gt;encode&lt;/code&gt; e &lt;code&gt;decode&lt;/code&gt; dentro da biblioteca do
Whisper. Também é demonstrado um uso básico da função &lt;code&gt;print&lt;/code&gt; em Python.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passos práticos e comandos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Manipulação de parâmetros do Whisper:&lt;/strong&gt; O apresentador explica e demonstra o
efeito de configurar &lt;code&gt;condition_on_previous_text&lt;/code&gt; como &lt;code&gt;false&lt;/code&gt; para
desabilitar o contexto anterior na transcrição. Ele também menciona
&lt;code&gt;length_penalty&lt;/code&gt; (embora não o teste), &lt;code&gt;suppress_tokens&lt;/code&gt; (demonstrando como
suprimir vírgulas, pontos e outras palavras específicas), e &lt;code&gt;--max_line_count&lt;/code&gt;
para controlar o comprimento das linhas de legenda. Ele executa comandos do
Whisper na linha de comando, mostrando mudanças na saída de legendas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manipulação de tokens via Python:&lt;/strong&gt; O apresentador demonstra como utilizar a
biblioteca do Whisper em Python para:&lt;ul&gt;
&lt;li&gt;Importar o &lt;code&gt;get_tokenizer&lt;/code&gt;: &lt;code&gt;from whisper.tokenizer import get_tokenizer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Obter um tokenizer: &lt;code&gt;tokenizer = get_tokenizer(True)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Codificar texto em tokens: &lt;code&gt;token.encode(&amp;quot;Olá mundo&amp;quot;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Decodificar tokens em texto: &lt;code&gt;tokenizer.decode([401, 842, 7968])&lt;/code&gt; Ele usa
isso para mostrar a correspondência (nem sempre exata) entre tokens
numéricos e palavras, examinando a saída tanto do comando Whisper quanto do
JSON gerado.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Dicas e conceitos teóricos importantes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;condition_on_previous_text&lt;/code&gt;:&lt;/strong&gt; Controlando se o modelo usa o contexto
anterior no áudio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;length_penalty&lt;/code&gt;:&lt;/strong&gt; Penaliza sequências de texto longas (valor típico entre
0.6 e 1).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;suppress_tokens&lt;/code&gt;:&lt;/strong&gt; Permite suprimir tokens específicos na saída de texto,
oferecendo mais controle sobre o resultado final.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tokens:&lt;/strong&gt; São representações numéricas de palavras ou partes de palavras
utilizadas internamente pelo modelo. A decodificação de tokens é fundamental
para entender a saída do modelo em detalhes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;FP16&lt;/code&gt; (float16) vs. &lt;code&gt;FP32&lt;/code&gt; (float32):&lt;/strong&gt; &lt;code&gt;FP16&lt;/code&gt; costuma ser mais rápido que
&lt;code&gt;FP32&lt;/code&gt;, mas o apresentador observa que a utilização de &lt;code&gt;FP16&lt;/code&gt; pode gerar
warnings. O modelo automaticamente tenta usar &lt;code&gt;FP16&lt;/code&gt; se disponível, mas cai
para &lt;code&gt;FP32&lt;/code&gt; se o dispositivo (CPU) não o suporta.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;O apresentador enfatiza a importância de testar os diferentes parâmetros para
entender seu impacto no resultado final da transcrição. Ele também nota a
imprecisão do modelo &amp;quot;Tiny&amp;quot; e sugere o uso de modelos maiores para melhores
resultados.&lt;/p&gt;
&lt;h2&gt;Trecho iniciando em 00:56:10&lt;/h2&gt;
&lt;p&gt;Este trecho do vídeo (00:56:10 em diante) foca em explicar e analisar parâmetros
de configuração avançados do modelo de transcrição de voz Whisper, da OpenAI. O
objetivo principal é demonstrar como ajustar esses parâmetros para resolver
problemas como loops (repetições) na transcrição e lidar com diferentes
comportamentos linguísticos em relação à pontuação.&lt;/p&gt;
&lt;p&gt;As tecnologias/ferramentas mencionadas são o modelo de transcrição Whisper, o
algoritmo de compressão Gzip e um arquivo JSON gerado pelo Whisper que contém
informações de métricas da transcrição. Não são mencionadas linguagens de
programação explicitamente, mas o apresentador analisa o código-fonte do Whisper
(em alguma linguagem não especificada) para explicar os parâmetros.&lt;/p&gt;
&lt;p&gt;Passos práticos/comandos: O apresentador não executa comandos específicos de
forma direta. Ele descreve como interpretar os dados do arquivo JSON gerado pelo
Whisper, focando em dois parâmetros principais: &lt;code&gt;Compression Ratio Threshold&lt;/code&gt; e
&lt;code&gt;AVG log probe&lt;/code&gt;. Ele explica como valores anormais nesses parâmetros (ex:
&lt;code&gt;Compression Ratio&lt;/code&gt; acima de 2.4 ou &lt;code&gt;AVG log probe&lt;/code&gt; abaixo de -1) podem indicar
problemas na transcrição e como ajustar o &lt;code&gt;Compression Ratio Threshold&lt;/code&gt; para 0
como um teste de resolução de problemas. Ele também discute o parâmetro
&lt;code&gt;prepend_punctuation&lt;/code&gt;, explicando seu funcionamento e impacto na transcrição de
idiomas com pontuação no início das palavras. O apresentador menciona que
consultou o &lt;em&gt;paper&lt;/em&gt; da OpenAI sobre o Whisper para entender esses parâmetros.&lt;/p&gt;
&lt;p&gt;Conceitos teóricos importantes: O apresentador explica o conceito de &amp;quot;razão de
compressão&amp;quot; (Compression Ratio) no contexto da transcrição de voz pelo Whisper.
Uma alta razão de compressão indica repetições no áudio, sugerindo um possível
loop no modelo. Ele também define e explica a métrica &lt;code&gt;AVG log probe&lt;/code&gt; (média do
logaritmo de probabilidade), que representa a confiança do modelo na
transcrição. Valores baixos indicam baixa confiança e possíveis erros.
Adicionalmente, ele detalha o funcionamento do parâmetro &lt;code&gt;prepend_punctuation&lt;/code&gt;,
que controla o processamento de pontuação que pode aparecer antes das palavras
em alguns idiomas. Por fim, ele menciona o conceito de &amp;quot;no speech threshold&amp;quot; que
ajuda a detectar e eliminar silêncios e outros ruídos da transcrição,
relacionando-o a um sistema de detecção de voz (VAD).&lt;/p&gt;
&lt;h2&gt;Trecho iniciando em 01:03:13&lt;/h2&gt;
&lt;p&gt;O objetivo principal desta parte do vídeo é explicar os argumentos de linha de
comando do modelo de transcrição de áudio Whisper, focando principalmente nos
argumentos relacionados à manipulação de pontuação e ao recorte de trechos de
áudio para transcrição.&lt;/p&gt;
&lt;p&gt;As tecnologias e ferramentas mencionadas são o modelo de transcrição Whisper e o
FFmpeg (mencionado brevemente). A linguagem de programação não é especificamente
mencionada, mas o apresentador se refere ao código-fonte do Whisper,
particularmente a função &lt;code&gt;merge_punctuations&lt;/code&gt; localizada dentro do módulo
&lt;code&gt;timing&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Passos práticos e comandos demonstrados: O apresentador explica como os
argumentos de pontuação (&lt;code&gt;prepend&lt;/code&gt; e &lt;code&gt;append&lt;/code&gt;) funcionam no Whisper, mostrando
como eles afetam a junção ou separação da pontuação com as palavras transcritas.
Ele também demonstra o uso do argumento &lt;code&gt;--clip_timestamps&lt;/code&gt;, mostrando como
especificar intervalos de tempo para transcrever apenas trechos específicos de
um vídeo. Ele detalha diferentes cenários de uso, incluindo a especificação de
múltiplos intervalos e casos de uso inesperados, como o retorno ao início da
transcrição após um intervalo definido. O apresentador também menciona o
argumento &lt;code&gt;threads&lt;/code&gt; para controlar o número de threads usadas durante a
transcrição, e &lt;code&gt;hallucination silence threshold&lt;/code&gt;, para lidar com silêncios
longos que podem resultar em texto alucinado.&lt;/p&gt;
&lt;p&gt;Conceitos teóricos importantes: O apresentador explica como a função
&lt;code&gt;merge_punctuations&lt;/code&gt; do Whisper funciona, analisando sua lógica interna para a
manipulação da pontuação. Ele destaca que o comportamento da função em relação
ao espaço em branco antes dos sinais de pontuação é peculiar. Ele também discute
a utilidade dos argumentos &lt;code&gt;--clip_timestamps&lt;/code&gt; para transcrever partes
específicas de vídeos, incluindo a possibilidade de processar trechos em idiomas
diferentes ou testar partes curtas de um vídeo. Por fim, o apresentador ressalta
que os argumentos de pontuação só são relevantes se o carimbo de tempo das
palavras for usado para analisar a colagem de pontuação, e que o argumento
&lt;code&gt;hallucination silence threshold&lt;/code&gt; ajuda a lidar com texto inventado pelo modelo
em trechos de silêncio.&lt;/p&gt;
&lt;h2&gt;Trecho iniciando em 01:10:16&lt;/h2&gt;
&lt;p&gt;Resumo detalhado do trecho (começando em 01:10:16):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Objetivo principal:&lt;/strong&gt; O objetivo principal desta parte do vídeo é encerrar o
tutorial atual e adiar a demonstração da integração do modelo de transcrição
Whisper ao código para um vídeo subsequente. O apresentador justifica a decisão
pela duração esperada da tarefa.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tecnologias, linguagens de programação ou ferramentas mencionadas:&lt;/strong&gt; A
tecnologia mencionada é o modelo de transcrição Whisper. Não há menção de
linguagens de programação específicas ou outras ferramentas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passos práticos ou comandos executados:&lt;/strong&gt; Nenhum passo prático ou comando é
executado neste trecho. A ação principal é a decisão do apresentador de adiar a
demonstração para um vídeo futuro.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dicas ou conceitos teóricos importantes:&lt;/strong&gt; Não há dicas ou conceitos teóricos
explicados neste curto segmento. A única informação relevante é a intenção de
usar o Whisper para transcrição em um tutorial futuro.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Resumo final gerado para o Youtube&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Capítulos para o YouTube:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;00:00:00 Introdução ao Whisper: Transcrição via Linha de Comando&lt;/li&gt;
&lt;li&gt;00:07:00 Instalando e Configurando o Whisper&lt;/li&gt;
&lt;li&gt;00:14:01 Transcrição de Áudio com o Whisper: Demonstração Prática&lt;/li&gt;
&lt;li&gt;00:21:01 Escolhendo o Modelo do Whisper: Recursos de Computador&lt;/li&gt;
&lt;li&gt;00:28:05 Refinando a Transcrição: Parâmetros de Configuração&lt;/li&gt;
&lt;li&gt;00:35:09 Otimizando a Geração de Legendas: Parâmetros Avançados&lt;/li&gt;
&lt;li&gt;00:42:09 Opções Avançadas: &lt;code&gt;--highlight_words&lt;/code&gt;, &lt;code&gt;--initial_prompt&lt;/code&gt;, FFmpeg&lt;/li&gt;
&lt;li&gt;00:49:09 Manipulando a Saída de Texto: Tokens e Parâmetros em Python&lt;/li&gt;
&lt;li&gt;00:56:10 Parâmetros Avançados: Resolvendo Loops e Pontuação&lt;/li&gt;
&lt;li&gt;01:03:13 Argumentos de Linha de Comando: Pontuação e Recorte de Áudio&lt;/li&gt;
&lt;li&gt;01:10:16 Encerramento e Prévia do Próximo Vídeo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Descrição para o YouTube (SEO Otimizado):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Domine a transcrição de áudio e vídeo com o Whisper da OpenAI! Neste tutorial
completo, aprenda a usar o poderoso modelo de reconhecimento de fala da OpenAI,
diretamente na linha de comando, para gerar legendas precisas e otimizadas.&lt;/strong&gt;
Após assistir, você será capaz de instalar, configurar e usar o Whisper para
transcrever seus arquivos de áudio e vídeo, otimizando parâmetros para
diferentes necessidades e resolvendo problemas comuns. Você também aprenderá a
manipular a saída do Whisper usando Python, obtendo total controle sobre o
processo.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Introdução ao Whisper e sua utilização via linha de comando.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instalação e configuração detalhada do Whisper, incluindo o uso do
repositório &lt;code&gt;sussu&lt;/code&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Demonstração prática de transcrição de áudio e vídeo com diferentes modelos
do Whisper.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Otimização de parâmetros para melhorar a precisão e velocidade da
transcrição.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manipulação de tokens e parâmetros avançados usando a biblioteca Python do
Whisper.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resolução de problemas comuns, como loops e erros de pontuação.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uso do FFmpeg para pré-processamento de vídeos.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicação detalhada dos argumentos de linha de comando do Whisper.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prévia da integração do Whisper com código (próximo vídeo).&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Este tutorial abrange todos os aspectos do uso do Whisper, desde a instalação
até a otimização de parâmetros avançados.&lt;/p&gt;
&lt;p&gt;Se você busca aprender sobre transcrição de áudio, reconhecimento de fala,
processamento de linguagem natural, legendas automáticas, OpenAI Whisper, linha
de comando, Python, FFmpeg, ou como melhorar a precisão das suas legendas, este
vídeo é para você! Palavras-chave relevantes incluem:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Whisper tutorial&lt;/code&gt;, &lt;code&gt;transcrição de áudio&lt;/code&gt;, &lt;code&gt;reconhecimento de fala&lt;/code&gt;,
&lt;code&gt;OpenAI Whisper&lt;/code&gt;, &lt;code&gt;linha de comando&lt;/code&gt;, &lt;code&gt;legendas automáticas&lt;/code&gt;, &lt;code&gt;Python Whisper&lt;/code&gt;,
&lt;code&gt;FFmpeg&lt;/code&gt;, &lt;code&gt;processamento de áudio&lt;/code&gt;, &lt;code&gt;modelo de linguagem&lt;/code&gt;,
&lt;code&gt;transcrição de vídeo&lt;/code&gt;, &lt;code&gt;otimização de legendas&lt;/code&gt;, &lt;code&gt;parâmetros Whisper&lt;/code&gt;,
&lt;code&gt;resolução de problemas Whisper&lt;/code&gt;, &lt;code&gt;tokens Whisper&lt;/code&gt;, &lt;code&gt;beam search&lt;/code&gt;,
&lt;code&gt;greedy decoding&lt;/code&gt;, &lt;code&gt;modelos de transcrição&lt;/code&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Tentei usar o whisper live via microfone</title><link>https://otaviomiranda.com.br/2025/whisper-live-sera-que-deu-certo/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2025/whisper-live-sera-que-deu-certo/</guid><description>Criei essa página para documentar minhas tentativas de usar o whisper live via microfone. Já quero deixar claro que isso não funcionou conforme eu esperava e cabem mais testes.</description><pubDate>Thu, 02 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Criei esse arquivo pra ir anotando minhas tentativas de rodar o &lt;code&gt;whisper&lt;/code&gt; em
tempo real, puxando o áudio direto do microfone. Já vou &lt;strong&gt;deixar claro que isso
não funcionou&lt;/strong&gt; do jeito que eu esperava, e ainda tem coisa pra testar.&lt;/p&gt;
&lt;p&gt;Na minha cabeça, talvez o certo fosse trabalhar direto com o modelo do
&lt;code&gt;whisper&lt;/code&gt;, sem usar o código oficial da OpenAI. Aquela janela fixa de 30
segundos pode ter atrapalhado real. Pode até ser que precise refinar o modelo
pra rodar liso num esquema &lt;em&gt;live&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Mas enfim... isso é só uma ideia. Não fui muito além do que tá descrito aqui
porque quero ver primeiro se esse tipo de conteúdo realmente chama atenção do
pessoal pra eu focar mais tempo pra gerar conteúdo (vídeos, posts, aulas, etc).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/whisper-failed-1.webp&quot; alt=&quot;Imagem de um robô triste&quot;&gt;&lt;/p&gt;
&lt;h2&gt;(🚫 FAILED) Captura do microfone via &lt;code&gt;ffmpeg&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Minha primeira tentativa foi capturar o áudio do computador usando o &lt;code&gt;ffmpeg&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pra deixar claro: tô usando o ChatGPT e o Gemini pra me ajudar nas partes que eu
manjo menos, principalmente as coisas mais técnicas de áudio. Além disso, tô
rodando tudo com esse setup aqui (não sei se faz diferença, mas vai que...):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MacBook Pro 2021&lt;/li&gt;
&lt;li&gt;Chip Apple M1 Max&lt;/li&gt;
&lt;li&gt;32 GB de RAM&lt;/li&gt;
&lt;li&gt;SSD de 1 TB&lt;/li&gt;
&lt;li&gt;macOS Sequoia 15.5&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Listando os dispositivos de áudio&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Obs:&lt;/strong&gt; tô testando só no macOS por enquanto.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# macOS
ffmpeg -hide_banner -f avfoundation -list_devices true -i &amp;quot;&amp;quot;

# Linux
ffmpeg -hide_banner -f alsa -list_devices true -i &amp;quot;&amp;quot;
# ou (às vezes mais confiável)
arecord -l
# ou
arecord --list-devices

# Windows
ffmpeg -hide_banner -list_devices true -f dshow -i dummy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No meu caso, a saída foi mais ou menos como a que mostro logo abaixo. Só dei uma
enxugada, tirando os dispositivos que não interessavam (tipo fone, celular,
etc). Também cortei aquele trecho inicial que o ffmpeg cospe sempre.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;➜  Desktop
ffmpeg -hide_banner -f avfoundation -list_devices true -i &amp;quot;&amp;quot; 2&amp;gt;&amp;amp;1

2025-06-18 09:15:21.567 ffmpeg[37312:1839628] WARNING:
Add NSCameraUseContinuityCameraDeviceType to your Info.plist to use AVCaptureDeviceTypeContinuityCamera.

2025-06-18 09:15:21.708 ffmpeg[37312:1839628] WARNING:
AVCaptureDeviceTypeExternal is deprecated for Continuity Cameras.

Please use AVCaptureDeviceTypeContinuityCamera and add NSCameraUseContinuityCameraDeviceType to your Info.plist.

[AVFoundation indev @ 0x153706960] AVFoundation video devices:
[AVFoundation indev @ 0x153706960] [0] Câmera FaceTime HD
[AVFoundation indev @ 0x153706960] [5] Capture screen 0
[AVFoundation indev @ 0x153706960] [6] Capture screen 1
[AVFoundation indev @ 0x153706960] AVFoundation audio devices:
[AVFoundation indev @ 0x153706960] [0] MOTIV Mix Virtual
[AVFoundation indev @ 0x153706960] [1] JBL TUNE125BT FOREBA
[AVFoundation indev @ 0x153706960] [2] Microfone (MacBook Pro)

[in#0 @ 0x600001f5c600] Error opening input: Input/output error

Error opening input file .
Error opening input files: Input/output error
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A parte importante dessa saída é saber duas coisas: o &lt;code&gt;[ID]&lt;/code&gt; e o nome do
dispositivo.&lt;/p&gt;
&lt;p&gt;Por exemplo, se eu quiser usar o &lt;strong&gt;Microfone (MacBook Pro)&lt;/strong&gt;, isso quer dizer
que o ID do dispositivo de áudio que eu vou usar é o &lt;code&gt;2&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;(🚫 FAILED) Testando a captura de áudio em um &lt;code&gt;.wav&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Antes de tudo, bora garantir que tá tudo funcionando bonitinho. A ideia aqui é
só gravar um &lt;code&gt;.wav&lt;/code&gt; com o áudio capturado direto do microfone. Ainda não é o
comando final, é só pra testar mesmo.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Lembra do meu [2] Microfone (MacBook Pro) -&amp;gt; ID 2
# No comando abaixo, o &amp;quot;:2&amp;quot; representa o dispositivo que eu tô usando
# Resultado: grava um arquivo out.wav com 10 segundos de áudio
ffmpeg -f avfoundation -i &amp;quot;:2&amp;quot; -t 10 -ac 1 -ar 16000 out.wav
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ou seja, pra capturar só o &lt;strong&gt;[2] Microfone (MacBook Pro)&lt;/strong&gt;, é só passar &lt;code&gt;:2&lt;/code&gt; no
argumento &lt;code&gt;-i&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Se tu quiser capturar &lt;strong&gt;vídeo e áudio ao mesmo tempo&lt;/strong&gt;, o formato vira &lt;code&gt;V:A&lt;/code&gt;,
onde &lt;code&gt;V&lt;/code&gt; é o ID do vídeo e &lt;code&gt;A&lt;/code&gt; o do áudio.&lt;/p&gt;
&lt;p&gt;Tipo assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Captura da Câmera FaceTime HD (ID 0) + Microfone (MacBook Pro) (ID 2)
# Grava um out.mp4 com 10 segundos de vídeo e áudio dos dispositivos 0 e 2
ffmpeg -f avfoundation -framerate 30 -i &amp;quot;0:2&amp;quot; -t 10 out.mp4
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;(🚫 FAILED) O que rolou com o &lt;code&gt;ffmpeg&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;No meu caso, o &lt;code&gt;ffmpeg&lt;/code&gt; não funcionou do jeito que eu queria. O áudio ficou todo
picotado, com umas falhas bem esquisitas. Daí pra frente, passei praticamente o
dia inteiro testando várias coisas: mudei o &lt;em&gt;sample rate&lt;/em&gt;, tentei outros
&lt;em&gt;codecs&lt;/em&gt;, troquei de microfone, testei outras fontes (até tentei capturar o som
direto do sistema). Nada deu certo.&lt;/p&gt;
&lt;p&gt;Minha conclusão: o &lt;code&gt;avfoundation&lt;/code&gt; no meu macOS não tá se comportando direito.&lt;/p&gt;
&lt;p&gt;Testei comandos como esse aqui:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;ffmpeg \
    -thread_queue_size 512 \
    -loglevel debug \
    -f avfoundation \
    -i &amp;quot;:2&amp;quot; \
    -c:a copy \
    -t 10 \
    out.wav \
    -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse &lt;code&gt;-c:a copy&lt;/code&gt; (pra manter o codec de áudio original) foi uma das últimas
tentativas, depois de trocar um monte de outras coisas. E com isso o ffmpeg
revelou que o áudio tava vindo com essas specs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Stream #0:0, 1, 1/1000000: Audio: pcm_f32le, 48000 Hz, mono, flt, 1536 kb/s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eu já tinha testado &lt;code&gt;pcm_f32le&lt;/code&gt; com &lt;code&gt;48000 Hz&lt;/code&gt; antes, então... nenhuma surpresa.&lt;/p&gt;
&lt;p&gt;Enfim, deixo isso aqui documentado porque, se acontecer contigo também, já te
poupo umas horas de frustração. A única coisa que resolveu o problema de áudio
tremido/picotado foi &lt;strong&gt;usar o &lt;code&gt;sox&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;(✅ SUCCESS) &lt;code&gt;sox&lt;/code&gt; pra capturar o áudio&lt;/h2&gt;
&lt;p&gt;Na primeira tentativa com &lt;code&gt;sox&lt;/code&gt;, já rolou de boa, &lt;strong&gt;zero esforço&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sox \
    -b 32 \
    -e float \
    -r 16000 \
    -c 1 \
    -d \
    --buffer $((16000*4*10)) \
    out.wav \
    trim 0 10 \
    fade t 1 -0 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse comando foi gerado pelo Gemini ou ChatGPT (não lembro). Só pedi um exemplo
que gravasse 10 segundos de áudio com fade-in e fade-out, só pra teste mesmo.&lt;/p&gt;
&lt;p&gt;Depois fui testando outras paradas, mas o comando que pretendo usar com o
&lt;code&gt;whisper&lt;/code&gt; é esse aqui:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# 🚫 não executa ainda
sox -t \
    coreaudio &amp;quot;Microfone (MacBook Pro)&amp;quot; \
    -b 16 \
    -e signed-integer \
    -r 16000 -c 1 \
    -t raw \
    -
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Repara que a saída tá indo pro &lt;code&gt;stdout&lt;/code&gt; (por isso tem esse &lt;code&gt;-&lt;/code&gt; no fim), não pra
um arquivo. Ou seja, isso aqui é só a base pra integrar com outro processo, tipo
jogar direto no &lt;code&gt;whisper&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Eu não entendo muito do &lt;code&gt;sox&lt;/code&gt; ainda (primeiro uso), mas usei o &lt;code&gt;ffmpeg&lt;/code&gt; (lá em
cima) pra listar os dispositivos. E aqui é diferente: em vez de usar o ID como
no &lt;code&gt;ffmpeg&lt;/code&gt;, tu passa o nome do dispositivo, tipo, meu caso:
&lt;code&gt;&amp;quot;Microfone (MacBook Pro)&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;(🤔 SUCCESS?) &lt;code&gt;whisper&lt;/code&gt; live, ou quase&lt;/h2&gt;
&lt;p&gt;Aqui é onde começo a achar que talvez eu precise refinar o modelo ou até usar
ele &amp;quot;cru&amp;quot;, sem passar pelo código oficial da OpenAI (&lt;code&gt;whisper&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Fiz um código bem porquinho, só pra &lt;strong&gt;ver se ia funcionar&lt;/strong&gt;. A ideia era: se
desse certo, aí sim eu refinava tudo com calma.&lt;/p&gt;
&lt;p&gt;Considera isso aqui como um &lt;em&gt;MVP&lt;/em&gt; na versão &lt;code&gt;0.0.0alpha&lt;/code&gt; mesmo.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# pyright: basic
import asyncio

import numpy as np
import whisper
from scipy.io import wavfile

MODEL = whisper.load_model(&amp;quot;turbo&amp;quot;)


async def recorder(queue, counter, buffer, chunk_size, sr=16000, duration=5):
    print(&amp;quot;🗣️ Recording started&amp;quot;)

    proc = await asyncio.create_subprocess_exec(
        &amp;quot;sox&amp;quot;,
        &amp;quot;-t&amp;quot;,
        &amp;quot;coreaudio&amp;quot;,
        &amp;quot;Microfone (MacBook Pro)&amp;quot;,
        &amp;quot;-b&amp;quot;,
        &amp;quot;16&amp;quot;,
        &amp;quot;-e&amp;quot;,
        &amp;quot;signed-integer&amp;quot;,
        &amp;quot;-r&amp;quot;,
        &amp;quot;16000&amp;quot;,
        &amp;quot;-c&amp;quot;,
        &amp;quot;1&amp;quot;,
        &amp;quot;-t&amp;quot;,
        &amp;quot;raw&amp;quot;,
        &amp;quot;-&amp;quot;,  # raw para facilitar o corte
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.DEVNULL,
    )

    while True:
        data = await proc.stdout.read(chunk_size)
        if not data:
            break
        buffer += data

        chunk_byte_size = sr * 2 * duration

        if len(buffer) &amp;gt;= chunk_byte_size:
            audio_chunk = buffer[:chunk_byte_size]
            buffer = buffer[chunk_byte_size:]

            audio_np = np.frombuffer(audio_chunk, np.int16).astype(np.float32) / 32768.0
            await queue.put(audio_np)

            wavfile.write(
                f&amp;quot;media/debug_{counter}.wav&amp;quot;,
                16000,
                audio_np,
            )

            print(f&amp;quot;🗣️ Chunk {counter} ends \n&amp;quot;)
            counter += 1


async def transcriber(queue):
    prefix = &amp;quot;&amp;quot;
    while True:
        print(&amp;quot;💬 Whisper started&amp;quot;)

        audio = await queue.get()
        audio = whisper.pad_or_trim(audio)

        mel = whisper.log_mel_spectrogram(audio, n_mels=MODEL.dims.n_mels).to(
            MODEL.device
        )

        options = whisper.DecodingOptions(
            language=&amp;quot;en&amp;quot;,
            temperature=0,
            beam_size=1,
            patience=0,
            without_timestamps=True,
            prompt=None,
            prefix=None,
        )
        result = whisper.decode(
            MODEL,
            mel,
            options,
        )

        prefix = result.text
        print(&amp;quot;💬&amp;quot;, result.text, &amp;quot;\n&amp;quot;)
        queue.task_done()


async def main():
    from pathlib import Path
    from shutil import rmtree

    media = Path(&amp;quot;media&amp;quot;)
    rmtree(media)
    media.mkdir(exist_ok=True)

    queue = asyncio.Queue()
    counter = 0
    buffer = b&amp;quot;&amp;quot;
    chunk_size = 4096

    await asyncio.gather(
        recorder(queue, counter, buffer, chunk_size, 16000, 30), transcriber(queue)
    )


if __name__ == &amp;quot;__main__&amp;quot;:
    asyncio.run(main())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nesse código eu uso &lt;code&gt;asyncio&lt;/code&gt; justamente pra não travar tudo enquanto o
&lt;code&gt;whisper&lt;/code&gt; tá transcrevendo. A ideia é deixar o processo rodando em paralelo:
enquanto uma parte grava, a outra já vai transcrevendo em tempo real.&lt;/p&gt;
&lt;p&gt;Também uso o &lt;code&gt;scipy.io.wavfile&lt;/code&gt; pra salvar os áudios em &lt;em&gt;chunks&lt;/em&gt;, do mesmo
jeitinho que o &lt;code&gt;whisper&lt;/code&gt; recebe. Isso ajuda no debug: depois eu junto esses
arquivos e escuto como ficou o áudio real que foi processado.&lt;/p&gt;
&lt;p&gt;A função &lt;code&gt;recorder&lt;/code&gt; é quem inicia o &lt;code&gt;sox&lt;/code&gt; com o microfone que escolhi e roda um
loop que fica monitorando a quantidade de bytes acumulados. Essa contagem é
baseada no tamanho que cada trecho de áudio teria, considerando a duração que
defini.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Sample rate (16000) * Sample Width (2 bytes) * duração (ex: 5s)
chunk_byte_size = sr * 2 * duration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quando o tamanho do &lt;code&gt;buffer&lt;/code&gt; atinge esse &lt;code&gt;chunk_byte_size&lt;/code&gt;, a gente corta
exatamente até ali. Esses são os bytes crus do trecho de áudio que vai pra
transcrição. Em seguida, o &lt;code&gt;buffer&lt;/code&gt; é atualizado pra remover esse pedaço
processado e guardar qualquer sobra, que vai ser usada no próximo ciclo.&lt;/p&gt;
&lt;p&gt;Esse loop aqui embaixo é o coração da gravação. Ele fica rodando enquanto o
&lt;code&gt;sox&lt;/code&gt; tá mandando dados pela &lt;code&gt;stdout&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;while True:
    # captura o que vem do sox
    data = await proc.stdout.read(chunk_size)
    if not data:
        break
    # joga no buffer
    buffer += data

    # calcula quantos bytes correspondem à duration configurada
    chunk_byte_size = sr * 2 * duration

    # se o buffer já tiver o tamanho certo pra um trecho de áudio completo
    if len(buffer) &amp;gt;= chunk_byte_size:
        # pegamos só o pedaço que interessa
        audio_chunk = buffer[:chunk_byte_size]
        # e atualizamos o buffer, removendo o que já usamos
        buffer = buffer[chunk_byte_size:]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aí, com esse &lt;code&gt;audio_chunk&lt;/code&gt; em mãos, é só converter pra um formato que o
&lt;code&gt;whisper&lt;/code&gt; entenda e jogar isso na &lt;code&gt;queue&lt;/code&gt;, que o &lt;code&gt;transcriber&lt;/code&gt; vai processar
depois:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;audio_np = np.frombuffer(audio_chunk, np.int16).astype(np.float32) / 32768.0
await queue.put(audio_np)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse &lt;code&gt;.frombuffer(...).astype(...) / 32768.0&lt;/code&gt; basicamente transforma os bytes
crus em um array de &lt;code&gt;float32&lt;/code&gt; com valores normalizados entre -1 e 1, que é o
formato que o &lt;code&gt;whisper&lt;/code&gt; espera.&lt;/p&gt;
&lt;p&gt;Como eu queria ver exatamente o que o &lt;code&gt;whisper&lt;/code&gt; também &amp;quot;tava enxergando&amp;quot;, salvei
cada trecho de áudio assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;wavfile.write(
    f&amp;quot;media/debug_{counter}.wav&amp;quot;,
    16000,
    audio_np,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Usei esse &lt;code&gt;counter&lt;/code&gt; só pra gerar arquivos separados, tipo: &lt;code&gt;debug_0.wav&lt;/code&gt;,
&lt;code&gt;debug_1.wav&lt;/code&gt;, &lt;code&gt;debug_2.wav&lt;/code&gt;, e por aí vai.&lt;/p&gt;
&lt;p&gt;Depois juntei tudo com o &lt;code&gt;ffmpeg&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;ffmpeg -f concat -safe 0 -i input.txt -c copy out.wav
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E o arquivo &lt;code&gt;input.txt&lt;/code&gt; fica assim:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;file &amp;#39;debug_0.wav&amp;#39;
file &amp;#39;debug_1.wav&amp;#39;
file &amp;#39;debug_2.wav&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Até esse ponto, tava tudo fluindo lindamente. Eu já tava achando que ia dar bom.
Mas... o resultado final foi bem mais ou menos.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;(🤔 SUCCESS?) os problemas!&lt;/h3&gt;
&lt;p&gt;É aqui que entra o &lt;code&gt;whisper&lt;/code&gt; de verdade. Testei um monte de coisas pra tentar
deixar tudo o mais rápido possível, e, claro, sem perder precisão na
transcrição.&lt;/p&gt;
&lt;p&gt;Dá uma olhada nos comentários que deixei no código. Logo mais abaixo eu explico
outras coisas que descobri no caminho.&lt;/p&gt;
&lt;p&gt;Essa é a função &lt;code&gt;transcriber&lt;/code&gt;, que fica rodando enquanto tem coisa na fila
(&lt;code&gt;queue&lt;/code&gt;). Comentei bastante no código, mas aqui vai uma visão geral com uns
complementos:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;async def transcriber(queue):
    prefix = &amp;quot;&amp;quot;  # USEI ISSO DE DUAS FORMAS DIFERENTES, SEM SUCESSO
    while True:
        print(&amp;quot;💬 Whisper started&amp;quot;)

        # Pega o áudio cru da fila
        audio = await queue.get()

        # Isso aqui é um pé no saco: não importa o tamanho real do áudio,
        # o whisper sempre espera cerca de 30s.
        #
        # Se for menor, ele preenche o resto com zeros (silêncio).
        # Se for maior, ele corta o que passa dos 30s.
        #
        # IMPORTANTE: não tem como passar áudios de durações diferentes
        # usando o código da OpenAI, porque o modelo foi treinado assim.
        audio = whisper.pad_or_trim(audio)

        # O whisper não &amp;quot;ouve&amp;quot; o som, ele transforma em imagem.
        # É um gráfico do som chamado espectrograma (Mel logarítmico).
        # Já expliquei isso num vídeo, é bem de boa de entender.
        mel = whisper.log_mel_spectrogram(audio, n_mels=MODEL.dims.n_mels).to(
            MODEL.device
        )

        options = whisper.DecodingOptions(
            # Último teste: inglês (en)
            # Mas 90% dos testes foram em português (pt), com áudios limpos,
            # de vídeos do YouTube com gente falando claro, sem gíria nem ruído.
            language=&amp;quot;en&amp;quot;,

            # Tentei deixar mais rápido usando o modo &amp;quot;greedy&amp;quot;
            # Também expliquei isso no outro vídeo
            temperature=0,
            beam_size=1,
            patience=0,  # isso aqui nem faz nada, foi mais no desespero

            # Desativei os timestamps pra ver se o modelo ficava mais leve,
            # focando só na transcrição mesmo
            without_timestamps=True,

            # Aquele prefixo lá de cima... tentei usar nessas duas opções aqui
            # (prompt e prefix). O modelo entrou em loop e fiquei bolado.
            # Desisti de usar.
            prompt=None,
            prefix=None,
        )

        # E aqui é onde rola a transcrição de fato
        result = whisper.decode(MODEL, mel, options)

        prefix = result.text
        print(&amp;quot;💬&amp;quot;, result.text, &amp;quot;\n&amp;quot;)
        queue.task_done()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tentei detalhar bastante nos comentários do código acima, mas ainda tem umas
coisas importantes que vale comentar.&lt;/p&gt;
&lt;p&gt;Primeiro: &lt;strong&gt;não consegui usar nenhum modelo acima do &lt;code&gt;medium&lt;/code&gt;&lt;/strong&gt;. O motivo é
simples, o tempo de transcrição ficava maior do que a janela de tempo que eu
escolhi.&lt;/p&gt;
&lt;p&gt;Tipo assim: com o modelo &lt;code&gt;turbo&lt;/code&gt; e uma janela de 2 segundos, o &lt;code&gt;whisper&lt;/code&gt; levava
uns 10 segundos (ou mais!) pra cuspir a transcrição. Ou seja, &lt;strong&gt;não dava
tempo&lt;/strong&gt;. Não tinha como rodar em tempo real desse jeito. Pode ser limitação do
meu hardware também? Pode. Mas o fato é que não rolou.&lt;/p&gt;
&lt;p&gt;Os modelos que &lt;strong&gt;se saíram melhor&lt;/strong&gt; nesse esquema foram o &lt;code&gt;tiny&lt;/code&gt; e o &lt;code&gt;base&lt;/code&gt;.
Ambos ainda são imprecisos, mas se eu tivesse que escolher um, ficaria com o
&lt;code&gt;base&lt;/code&gt;. O &lt;code&gt;tiny&lt;/code&gt;, cara... entra em loop com uma facilidade absurda, qualquer
coisa repete, embanana, e vira um samba do sussurrador doido.&lt;/p&gt;
&lt;p&gt;Sobre os tempos, testei com várias janelas diferentes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⚠️ &lt;strong&gt;1s, 2s, 3s, 4s:&lt;/strong&gt; sem chance. O modelo não entende nada, e em poucos
ciclos já começa a repetir as coisas.&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;5s a 9s:&lt;/strong&gt; até vai, mas ainda rola loop e perda de qualidade em alguns
momentos.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;10s a 30s:&lt;/strong&gt; aqui a coisa começou a funcionar de verdade. Foi a faixa que
deu o melhor resultado.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Percebeu o paradoxo? Eu queria uma parada em &lt;em&gt;tempo real&lt;/em&gt;, mas só começou a
ficar usável a partir dos 10 segundos de áudio por vez. Foi aí que me bateu a
real: talvez só role se eu refinar o modelo e usar um código customizado, sem
depender da implementação padrão do &lt;code&gt;whisper&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Todos esses testes foram feitos usando exatamente o código oficial da OpenAI.
Nada modificado no core do modelo.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Conclusão geral&lt;/h2&gt;
&lt;p&gt;Desse jeito que mostrei até aqui, pra mim &lt;strong&gt;não rola usar o &lt;code&gt;whisper&lt;/code&gt; com
precisão em tempo real&lt;/strong&gt;. Pelo menos foi essa a conclusão que cheguei depois de
tudo isso.&lt;/p&gt;
&lt;p&gt;Mas ó: foi uma experiência massa. Aprendi muita coisa que eu nem fazia ideia,
principalmente sobre áudio, e me diverti bastante fuçando esse modelo.&lt;/p&gt;
&lt;p&gt;Tô deixando tudo documentado aqui porque pode ser que te ajude a ir mais fundo
no modelo, ou até mexer no código do &lt;code&gt;whisper&lt;/code&gt; pra tentar um resultado melhor
que o meu.&lt;/p&gt;
&lt;p&gt;Outra opção que eu &lt;em&gt;não testei&lt;/em&gt;, mas pode valer muito a pena, seria usar o
&lt;a href=&quot;https://github.com/ggerganov/whisper.cpp&quot;&gt;&lt;code&gt;whisper.cpp&lt;/code&gt;&lt;/a&gt; (versão C++) ou o
&lt;a href=&quot;https://github.com/guillaumekln/faster-whisper&quot;&gt;&lt;code&gt;faster-whisper&lt;/code&gt;&lt;/a&gt;, que é uma
implementação otimizada em Python com base no CTranslate2.&lt;/p&gt;
&lt;p&gt;Valeu demais se tu leu até aqui! Tomara que eu tenha te ajudado ou feito você
perder menos tempo se fosse testar tudo isso que testei.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Obs:&lt;/strong&gt; escrevi esse texto com &lt;code&gt;markdown&lt;/code&gt;, se quiser baixar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;TEXT.md&quot;&gt;Baixar esse texto em &lt;code&gt;markdown&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bye 👋!!!&lt;/p&gt;
&lt;hr&gt;
</content:encoded></item><item><title>Liskov Substitution Principle ou Princípio da Substituição de Liskov</title><link>https://otaviomiranda.com.br/2025/liskov-substitution-principle-lsp-solid/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2025/liskov-substitution-principle-lsp-solid/</guid><description>Liskov Substitution Principle explicado: valide pré‑condições, pós‑condições e invariantes com casos reais e código Python.</description><pubDate>Fri, 12 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Quem já trabalhou com herança na programação orientada a objetos, muito
provavelmente também já ouviu falar sobre o &lt;strong&gt;Princípio da Substituição de
Liskov&lt;/strong&gt;, ou até já sofreu na prática quando ele foi quebrado no seu programa.&lt;/p&gt;
&lt;p&gt;Introduzido
&lt;a href=&quot;https://en.wikipedia.org/wiki/Liskov_substitution_principle&quot;&gt;por Barbara Liskov em 1987&lt;/a&gt;,
o LSP é um dos cinco princípios do famoso &lt;strong&gt;SOLID&lt;/strong&gt; e define uma regra simples,
mas muito poderosa: &lt;em&gt;um subtipo deve poder substituir seu tipo base sem que o
comportamento esperado do sistema mude&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Parece simple e extremamente óbvio, mas não é. O que muita gente esquece é que
não basta a tipagem funcionar, o &lt;strong&gt;comportamento&lt;/strong&gt; (a sua lógica) também precisa
ser compatível. É aqui que entram conceitos como &lt;strong&gt;pré-condições&lt;/strong&gt;,
&lt;strong&gt;pós-condições&lt;/strong&gt; e &lt;strong&gt;invariantes&lt;/strong&gt;: três pilares que ajudam a identificar se um
subtipo realmente respeita o contrato definido pela superclasse.&lt;/p&gt;
&lt;p&gt;Neste artigo, vamos entender a definição formal do LSP, traduzi-la para um
contexto mais prático e analisar exemplos, desde os clássicos, até situações
sutis que passam despercebidas por type checkers, mas que podem quebrar seu
sistema no pior momento possível.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;A Definição Formal de Barbara Liskov&lt;/h2&gt;
&lt;p&gt;Se você for como eu, precisará ler o que ela disse algumas vezes até entender o
que ela quis dizer. Mas vamos para a definição forma em inglês e português.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;What is wanted here is something like the following substitution property: If
for each object &lt;code&gt;o1&lt;/code&gt; of type &lt;code&gt;S&lt;/code&gt; there is an object &lt;code&gt;o2&lt;/code&gt; of type &lt;code&gt;T&lt;/code&gt; such that
for all programs &lt;code&gt;P&lt;/code&gt; defined in terms of &lt;code&gt;T&lt;/code&gt;, the behavior of &lt;code&gt;P&lt;/code&gt; is unchanged
when &lt;code&gt;o1&lt;/code&gt; is substituted for &lt;code&gt;o2&lt;/code&gt;, then &lt;code&gt;S&lt;/code&gt; is a subtype of &lt;code&gt;T&lt;/code&gt;.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Tradução literal:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;O que se quer aqui é algo como a seguinte propriedade de substituição: se,
para cada objeto &lt;code&gt;o1&lt;/code&gt; do tipo &lt;code&gt;S&lt;/code&gt;, existe um objeto &lt;code&gt;o2&lt;/code&gt; do tipo &lt;code&gt;T&lt;/code&gt;, de forma
que, para todos os programas &lt;code&gt;P&lt;/code&gt; definidos em termos de &lt;code&gt;T&lt;/code&gt;, o comportamento
de &lt;code&gt;P&lt;/code&gt; permanece inalterado quando &lt;code&gt;o1&lt;/code&gt; é substituído por &lt;code&gt;o2&lt;/code&gt;, então &lt;code&gt;S&lt;/code&gt; é um
subtipo de &lt;code&gt;T&lt;/code&gt;.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Entendeu? Ok, vamos decifrar isso juntos...&lt;/p&gt;
&lt;h3&gt;O que ela quis dizer?&lt;/h3&gt;
&lt;p&gt;Tomei a liberdade de traduzir o que ela disse de uma forma menos formal. E eu
não fiz isso enquanto escrevia este texto, foram anos quebrando a cabeça até
chegar em algo assim:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;S&lt;/code&gt; é subtipo de &lt;code&gt;T&lt;/code&gt; &lt;strong&gt;somente&lt;/strong&gt; se &lt;strong&gt;qualquer programa&lt;/strong&gt; escrito para
funcionar com objetos do tipo &lt;code&gt;T&lt;/code&gt; continuar se comportando &lt;strong&gt;exatamente da
mesma forma&lt;/strong&gt; quando receber um objeto do tipo &lt;code&gt;S&lt;/code&gt;, sem &amp;quot;perceber&amp;quot; a troca.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ou seja, &lt;strong&gt;não basta a tipagem bater com o Type Checker, o comportamento do
subtipo também precisa ser compatível&lt;/strong&gt; com o comportamento do supertipo.&lt;/p&gt;
&lt;p&gt;Você pode ter um código perfeito para o type checker e mesmo assim quebrar o LSP
se violar o contrato do tipo base.&lt;/p&gt;
&lt;p&gt;Se isso acontecer, com certeza terá bugs no seu programa no futuro.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Como verificar se o LSP está sendo respeitado&lt;/h2&gt;
&lt;p&gt;O checklist clássico é baseado em três pontos-chave: &lt;strong&gt;pré-condições&lt;/strong&gt;,
&lt;strong&gt;pós-condições&lt;/strong&gt; e &lt;strong&gt;invariantes&lt;/strong&gt;. Antes de analisarmos cada um, vale lembrar
que &lt;strong&gt;a compatibilidade começa na assinatura dos métodos&lt;/strong&gt;, ou seja, parâmetros,
retorno e exceções fazem parte do contrato que o subtipo precisa manter.&lt;/p&gt;
&lt;h3&gt;Compatibilidade de assinaturas&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Parâmetros:&lt;/strong&gt; No subtipo, devem ser &lt;strong&gt;iguais ou mais genéricos&lt;/strong&gt;
(&lt;strong&gt;contravariantes&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retorno:&lt;/strong&gt; No subtipo, devem ser &lt;strong&gt;iguais ou mais específicos&lt;/strong&gt;
(&lt;strong&gt;covariantes&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exceções:&lt;/strong&gt; O subtipo não deve lançar exceções que não sejam subtipos das
lançadas pela superclasse.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Em Python, o &lt;code&gt;Callable&lt;/code&gt; já é contravariante nos argumentos e covariante nos
retornos. Para saber qual exceção é subtipo de outra, veja a
&lt;a href=&quot;https://docs.python.org/3/library/exceptions.html#exception-hierarchy&quot;&gt;hierarquia de exceptions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Falamos sobre a variância no vídeo
&lt;a href=&quot;https://youtu.be/26BdcuNAlys&quot;&gt;Genéricos ABC, Covariância, Contravariância e Invariância no Python - Aula 5&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Pré-condições (inputs / parâmetros)&lt;/h3&gt;
&lt;p&gt;Pré-condições estão relacionadas aos parâmetros de entrada (ou inputs).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Regra:&lt;/strong&gt; O subtipo &lt;strong&gt;não pode&lt;/strong&gt; ser mais restritivo que o tipo base.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemplos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Se a abstração aceita um container iterável com &lt;strong&gt;qualquer tipo de elemento&lt;/strong&gt;,
o subtipo não pode exigir uma &lt;code&gt;list[str]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Se a abstração espera uma &lt;strong&gt;URL&lt;/strong&gt; (HTTP ou HTTPS), o subtipo não pode aceitar
apenas &lt;strong&gt;caminhos de arquivo local&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Se a abstração aceita &lt;strong&gt;qualquer &lt;code&gt;int&lt;/code&gt;&lt;/strong&gt;, o subtipo não pode restringir para
&lt;strong&gt;apenas inteiros positivos&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Se a abstração aceita &lt;code&gt;str&lt;/code&gt; &lt;strong&gt;vazia ou não vazia&lt;/strong&gt;, o subtipo não pode proibir
string vazia.&lt;/li&gt;
&lt;li&gt;Se a abstração aceita &lt;code&gt;None&lt;/code&gt; como valor opcional, o subtipo não pode rejeitar
&lt;code&gt;None&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Se a abstração aceita &lt;strong&gt;qualquer objeto&lt;/strong&gt; no &lt;code&gt;in&lt;/code&gt; (&lt;code&gt;__contains__&lt;/code&gt;), o subtipo
não pode lançar &lt;code&gt;TypeError&lt;/code&gt; para tipos diferentes.&lt;/li&gt;
&lt;li&gt;Se a abstração aceita &lt;code&gt;float&lt;/code&gt; &lt;strong&gt;com ou sem casas decimais&lt;/strong&gt;, o subtipo não
pode aceitar apenas números inteiros representados como float (ex.: &lt;code&gt;1.0&lt;/code&gt;,
&lt;code&gt;2.0&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Se a abstração aceita &lt;strong&gt;qualquer formato de data válido&lt;/strong&gt;, o subtipo não pode
aceitar apenas &lt;code&gt;datetime.date&lt;/code&gt; e rejeitar &lt;code&gt;datetime.datetime&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Se a abstração aceita parâmetros &lt;strong&gt;por posição ou por nome&lt;/strong&gt;, o subtipo não
pode exigir que todos sejam nomeados (ou todos posicionais).&lt;/li&gt;
&lt;li&gt;Se a abstração aceita qualquer arquivo aberto (modo leitura ou escrita), o
subtipo não pode exigir exclusivamente arquivos abertos em modo leitura.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Exemplo concreto para pré-condições (inputs / parâmetros)&lt;/h3&gt;
&lt;p&gt;Vejamos um exemplo onde o subtipo viola uma pré-condição do LSP:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Tags:
    def __init__(self, tags: set[str]) -&amp;gt; None:
        self._tags = tags

    # Contrato amplo: aceita qualquer objeto
    def __contains__(self, item: object) -&amp;gt; bool:
        return item in self._tags  # Para tipo &amp;quot;errado&amp;quot;, retorna False


class StrictTags(Tags):
    # 🚫 Pré-condição mais restritiva: agora só aceita str
    def __contains__(self, item: object) -&amp;gt; bool:
        if not isinstance(item, str):
            raise TypeError(&amp;quot;item must be str&amp;quot;)
        return item in self._tags


# Cliente escrito para o tipo base:
def has_tag(t: Tags, q: object) -&amp;gt; bool:
    return q in t


t1 = Tags({&amp;quot;python&amp;quot;, &amp;quot;types&amp;quot;})
t2 = StrictTags({&amp;quot;python&amp;quot;, &amp;quot;types&amp;quot;})

print(has_tag(t1, 123))  # False (ok no contrato do base)
print(has_tag(t2, 123))  # 💥 TypeError, subtipo ficou mais restritivo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No código acima, &lt;code&gt;Tags&lt;/code&gt; aceita que qualquer &lt;code&gt;object&lt;/code&gt; seja utilizado com
&lt;code&gt;__contains__&lt;/code&gt; (&lt;code&gt;in&lt;/code&gt; e &lt;code&gt;not in&lt;/code&gt;). Mas &lt;code&gt;StrictTags&lt;/code&gt; impõe que apenas &lt;code&gt;str&lt;/code&gt; pode
ser utilizado. O type checker não reclama, mas o comportamento mudou, quebrando
a pré-condição da classe base.&lt;/p&gt;
&lt;p&gt;Pode parecer sutil, pode funcionar agora, mas em algum momento isso vai quebrar.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Pós-condições (retorno / output)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Regra:&lt;/strong&gt; O subtipo &lt;strong&gt;não pode&lt;/strong&gt; entregar menos do que o tipo base, ou seja,
não pode enfraquecer as condições do tipo base.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemplos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Se o pai promete &lt;strong&gt;retornar sempre um número positivo&lt;/strong&gt;, o filho não pode
retornar negativos.&lt;/li&gt;
&lt;li&gt;Se o pai promete retornar um &lt;strong&gt;objeto não nulo&lt;/strong&gt; (&lt;code&gt;None&lt;/code&gt; nunca é retorno
válido), o filho não pode retornar &lt;code&gt;None&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Se o pai promete retornar uma &lt;strong&gt;coleção ordenada&lt;/strong&gt;, o filho não pode retornar
elementos fora de ordem.&lt;/li&gt;
&lt;li&gt;Se o pai promete retornar &lt;strong&gt;todos os registros&lt;/strong&gt; encontrados, o filho não pode
retornar apenas parte deles sem avisar.&lt;/li&gt;
&lt;li&gt;Se o pai promete retornar um &lt;strong&gt;tipo específico&lt;/strong&gt; (&lt;code&gt;list&lt;/code&gt;), o filho não pode
retornar outro tipo compatível mas diferente (&lt;code&gt;tuple&lt;/code&gt;, &lt;code&gt;set&lt;/code&gt;), mesmo que tenha
os mesmos dados.&lt;/li&gt;
&lt;li&gt;Se o pai promete retornar &lt;strong&gt;um valor convertido para minúsculas&lt;/strong&gt;, o filho não
pode retornar o valor com caixa mista.&lt;/li&gt;
&lt;li&gt;Se o pai promete retornar um &lt;strong&gt;JSON válido&lt;/strong&gt;, o filho não pode retornar uma
string que não seja JSON parseável.&lt;/li&gt;
&lt;li&gt;Se o pai promete retornar um &lt;strong&gt;arquivo legível até o fim&lt;/strong&gt;, o filho não pode
retornar um arquivo truncado.&lt;/li&gt;
&lt;li&gt;Se o pai promete retornar um &lt;strong&gt;caminho absoluto&lt;/strong&gt;, o filho não pode retornar
um caminho relativo.&lt;/li&gt;
&lt;li&gt;Se o pai promete retornar um &lt;strong&gt;hash único&lt;/strong&gt;, o filho não pode retornar um
valor fixo ou repetido.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Observação:&lt;/strong&gt; Você pode &lt;strong&gt;prometer mais&lt;/strong&gt; que o pai, mas nunca menos.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3&gt;Exemplo concreto para pós-condições (outputs / retornos)&lt;/h3&gt;
&lt;p&gt;Um exemplo de algo que só retorna positivos no Python é o &lt;code&gt;__len__&lt;/code&gt;. Este método
é chamado ao usar &lt;code&gt;len()&lt;/code&gt; para saber quantos itens existem no container. Ou o
container está vazio (0 itens) ou tem elementos. Ele nunca terá uma quantidade
negativa de itens.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Tipagem perfeita, mas pós-condição violada
class SizedProtocol:
    def __init__(self, data: list[int]) -&amp;gt; None:
        self._data = data

    def __len__(self) -&amp;gt; int:
        return len(self._data)  # Sempre &amp;gt;= 0 (contrato implícito do Python)

class BadSized(SizedProtocol):
    def __len__(self) -&amp;gt; int:
        return len(self._data) - 1  # 🚫 Pode gerar valor negativo (sutil)


bad_sized = BadSized([])  # Vazio, então seria zero, mas será -1
size = len(bad_sized)     # ValueError: __len__() should return &amp;gt;= 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse exemplo já quebrou antes de nascer, bastou passar um objeto vazio e pedir
quantos itens ele tem.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Invariantes (verdades que sempre se mantêm)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Regra:&lt;/strong&gt; O subtipo deve manter todos os invariantes do tipo base.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemplos:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Se a classe base garante que um &lt;strong&gt;ID é imutável após criação&lt;/strong&gt;, o subtipo não
pode permitir alterar o ID.&lt;/li&gt;
&lt;li&gt;Se a classe base garante que o &lt;strong&gt;saldo inicial de conta bancária ≥ 0&lt;/strong&gt;, o
subtipo não pode criar contas com saldo negativo.&lt;/li&gt;
&lt;li&gt;Se a classe base garante que uma &lt;strong&gt;lista está sempre ordenada&lt;/strong&gt;, o subtipo não
pode inserir elementos fora de ordem.&lt;/li&gt;
&lt;li&gt;Se a classe base garante que a &lt;strong&gt;largura e altura são independentes&lt;/strong&gt;, o
subtipo não pode forçar que sejam sempre iguais (clássico &lt;code&gt;Rectangle&lt;/code&gt; vs
&lt;code&gt;Square&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Se a classe base garante que um &lt;strong&gt;arquivo temporário é apagado após uso&lt;/strong&gt;, o
subtipo não pode manter o arquivo no disco.&lt;/li&gt;
&lt;li&gt;Se a classe base garante que a &lt;strong&gt;conexão com banco de dados está aberta&lt;/strong&gt;
enquanto o objeto existir, o subtipo não pode fechar a conexão no meio da
execução.&lt;/li&gt;
&lt;li&gt;Se a classe base garante que &lt;strong&gt;valores duplicados não existem&lt;/strong&gt; numa coleção,
o subtipo não pode permitir duplicatas.&lt;/li&gt;
&lt;li&gt;Se a classe base garante que &lt;strong&gt;datas estão sempre no futuro&lt;/strong&gt; (ex.:
agendamento), o subtipo não pode permitir datas no passado.&lt;/li&gt;
&lt;li&gt;Se a classe base garante que um &lt;strong&gt;token expira em até 1 hora&lt;/strong&gt;, o subtipo não
pode emitir tokens com expiração indefinida.&lt;/li&gt;
&lt;li&gt;Se a classe base garante que a &lt;strong&gt;moeda de uma transação é fixa&lt;/strong&gt; após criação,
o subtipo não pode permitir mudar a moeda depois.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Exemplo concreto para invariantes (verdades que devem se manter)&lt;/h3&gt;
&lt;p&gt;Um dos exemplos mais conhecidos de quebra de invariante é quando se tenta usar o
&lt;code&gt;Quadrado&lt;/code&gt; como subtipo de &lt;code&gt;Retângulo&lt;/code&gt;. A ideia parece natural: &lt;em&gt;um quadrado é
um retângulo com a mesma altura e largura&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;O problema é que, nessa frase, já quebramos a invariante dos dois:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Retângulo:&lt;/strong&gt; largura e altura são &lt;strong&gt;independentes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quadrado:&lt;/strong&gt; largura e altura são &lt;strong&gt;sempre iguais&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ou seja, &lt;code&gt;Quadrado&lt;/code&gt; e &lt;code&gt;Retângulo&lt;/code&gt; são objetos distintos que, por acaso,
compartilham uma propriedade em comum: a &lt;strong&gt;área&lt;/strong&gt;. Mas, se for por isso,
&lt;code&gt;Círculo&lt;/code&gt; também tem área e nem por isso é um retângulo.&lt;/p&gt;
&lt;p&gt;Para ilustrar um caso diferente do clássico retângulo/quadrado, veja o código
abaixo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class DatabaseConfig:
    def __init__(self, dsn: str) -&amp;gt; None:
        self._dsn = dsn

    @property
    def dsn(self) -&amp;gt; str: # uma invariância
        return self._dsn


class MySqlDatabaseConfig(DatabaseConfig):
    def __init__(self, host: str, user: str, password: str, db: str) -&amp;gt; None:
        # Não tem DSN, mas força herança para &amp;quot;reaproveitar&amp;quot; métodos
        self.host = host
        self.user = user
        self.password = password
        self.db = db

        # Apenas inicializa a base com valor vazio (quebrando a invariante)
        super().__init__(&amp;quot;&amp;quot;)


def connect(cfg: DatabaseConfig) -&amp;gt; None:
    # Cliente depende da invariante DatabaseConfig.dsn
    print(&amp;quot;Connecting to:&amp;quot;, cfg.dsn)


cfg_bad = MySqlDatabaseConfig(host=&amp;quot;&amp;quot;, user=&amp;quot;root&amp;quot;, password=&amp;quot;&amp;quot;, db=&amp;quot;app&amp;quot;)
connect(cfg_bad)  # ❌ imprime DSN vazio; quebra a invariante
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No exemplo acima, &lt;code&gt;DatabaseConfig&lt;/code&gt; foi projetada para trabalhar com &lt;code&gt;dsn&lt;/code&gt; como
parte obrigatória. O subtipo herda a classe mas ignora essa regra, inicializando
com valor vazio. Isso quebra o contrato implícito de que &lt;code&gt;dsn&lt;/code&gt; sempre está
configurado.&lt;/p&gt;
&lt;p&gt;Uma forma de resolver seria:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Extrair métodos compartilhados para outra classe/módulo, evitando herança
forçada.&lt;/li&gt;
&lt;li&gt;Padronizar todas as subclasses para realmente usarem &lt;code&gt;dsn&lt;/code&gt; no mesmo formato.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Por que isso importa?&lt;/h2&gt;
&lt;p&gt;O LSP é fácil de quebrar sem perceber. Não é preciso &amp;quot;herança do mal&amp;quot; nem mudar
tipagem, um simples detalhe de lógica já quebra o contrato. Muitos desses bugs
são sutis e podem viver despercebidos por anos... até que um dia 💥💥💥.&lt;/p&gt;
&lt;p&gt;No fim das contas, respeitar o LSP é menos sobre decorar uma definição formal e
mais sobre entender o &lt;strong&gt;contrato invisível&lt;/strong&gt; que existe entre a classe base e
seus subtipos. Muitas violações não aparecem nos testes de tipagem, nem nos seus
testes automatizados, mas cobram seu preço em produção, quando o sistema começa
a se comportar de forma inesperada. Então, da próxima vez que criar uma
hierarquia de classes, lembre-se: herdar é fácil, mas herdar corretamente é
outra história completamente diferente.&lt;/p&gt;
&lt;hr&gt;
</content:encoded></item><item><title>logging no Python: Pare de usar print no lugar errado</title><link>https://otaviomiranda.com.br/2025/logging-no-python-pare-de-usar-print-no-lugar-errado/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2025/logging-no-python-pare-de-usar-print-no-lugar-errado/</guid><description>Nesse artigo falamos do módulo logging no Python do básico ao avançado. Pare de usar o print no local errado e comece a ter logs na sua aplicação Python.</description><pubDate>Tue, 05 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;O &lt;code&gt;logging&lt;/code&gt; é o módulo oficial do Python para lidar com logs de forma
estruturada, profissional e extensível. Além disso, ele serve como uma
alternativa muito mais poderosa ao velho e conhecido &lt;code&gt;print()&lt;/code&gt; para debug rápido
durante o desenvolvimento.&lt;/p&gt;
&lt;p&gt;Enquanto o &lt;code&gt;print()&lt;/code&gt; é ótimo pra exibir algo na tela, o &lt;code&gt;logging&lt;/code&gt; te dá controle
total sobre o evento que vai ser registrado, &lt;strong&gt;como&lt;/strong&gt; vai ser formatado e &lt;strong&gt;pra
onde&lt;/strong&gt; essa informação vai (terminal, arquivo, socket, banco, etc). São funções
diferentes entre &lt;code&gt;print&lt;/code&gt; e &lt;code&gt;logging&lt;/code&gt;, mas esse comparativo sempre aparece quando
falamos de &lt;code&gt;logging&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Ele também trabalha com níveis de severidade (&lt;code&gt;DEBUG&lt;/code&gt;, &lt;code&gt;INFO&lt;/code&gt;, &lt;code&gt;WARNING&lt;/code&gt;,
&lt;code&gt;ERROR&lt;/code&gt;, &lt;code&gt;CRITICAL&lt;/code&gt;), suporta múltiplos formatos, múltiplos destinos (handlers),
hierarquia de loggers e configuração via código ou arquivos externos.&lt;/p&gt;
&lt;p&gt;Em outras palavras: ele foi feito pra &lt;code&gt;log&lt;/code&gt; em aplicações reais. São registros
de eventos categorizados para aplicações de gente grande, que sabe o que está
fazendo e sabe que vai precisar debugar e refatorar o código em algum momento.&lt;/p&gt;
&lt;p&gt;O melhor é que já vem com o Python. Sem dependência. Sem gambiarra.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Links úteis para este artigo&lt;/h2&gt;
&lt;p&gt;Se você caiu nesse artigo de paraquedas, ele é apenas parte de um conteúdo BEM
MAIOR. Temos todo esse texto em vídeo no YouTube e também um repositório com
todo o código, segue:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PLbIBj8vQhvm28qR-yvWP3JELGelWxsxaI&quot;&gt;Logging no Python - Curso completo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/luizomf/logging_course_yt&quot;&gt;Repositório do curso&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Então vamos continuar...&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Como o &lt;code&gt;logging&lt;/code&gt; funciona por dentro&lt;/h2&gt;
&lt;p&gt;O sistema de logging do Python é organizado como uma estrutura hierárquica (em
forma de árvore), onde cada &lt;em&gt;logger&lt;/em&gt; é um objeto único identificado por um nome
textual (por exemplo: &lt;code&gt;meuapp&lt;/code&gt;, &lt;code&gt;meuapp.api&lt;/code&gt;, etc). Essa hierarquia é definida
simplesmente pelo nome, usando pontos (&lt;code&gt;.&lt;/code&gt;) para indicar níveis diferentes.&lt;/p&gt;
&lt;p&gt;Exemplo de loggers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;app&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app.database&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app.http&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app.http.requests&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Visualmente, a estrutura ficaria assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;root
└── app
    ├── database
    └── http
        └── requests
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Essa hierarquia permite algo muito poderoso: a &lt;strong&gt;propagação&lt;/strong&gt; dos logs. Por
padrão, quando um logger emite um log, essa mensagem sobe na hierarquia até
chegar ao logger pai e, eventualmente, ao logger raiz (&lt;code&gt;root&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Na prática, isso significa que você pode configurar todos os handlers (por
exemplo: imprimir no terminal, gravar em arquivos, enviar logs para serviços
externos) diretamente no logger raiz. Assim, todos os loggers filhos
automaticamente reutilizam esses handlers, sem precisar configurá-los
individualmente toda vez.&lt;/p&gt;
&lt;p&gt;Só tem um detalhe importante aqui: algumas bibliotecas também usam loggers
internamente, seguindo essa mesma hierarquia de nomes. Por isso, os logs
emitidos por essas libs podem acabar sendo capturados pelos seus handlers
globais também—desde que o nível configurado permita isso, é claro.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Se seu app não for grande e cheio de partes independentes, não
precisa complicar criando múltiplos loggers e uma hierarquia extensa. Na
maioria dos casos, um único logger já resolve tudo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nos exemplos a seguir, vamos ver tudo isso funcionando claramente na prática.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Conceitos principais: Loggers, Handlers, Formatters e Filters&lt;/h2&gt;
&lt;p&gt;O &lt;code&gt;logging&lt;/code&gt; é formado por 4 blocos principais:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Logger:&lt;/strong&gt; é quem dispara a mensagem (ex: &lt;code&gt;logger.info(&amp;quot;oi&amp;quot;)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Handler:&lt;/strong&gt; define &lt;strong&gt;pra onde&lt;/strong&gt; a mensagem vai (terminal, arquivo, e-mail
etc).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formatter:&lt;/strong&gt; define &lt;strong&gt;como&lt;/strong&gt; a mensagem vai aparecer (formato da string).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filter (opcional):&lt;/strong&gt; permite &lt;strong&gt;filtrar&lt;/strong&gt; o que será logado.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Esses blocos funcionam como peças de LEGO: você encaixa e combina como quiser.&lt;/p&gt;
&lt;p&gt;Pode ter um logger que salva tudo em um arquivo &lt;code&gt;.json&lt;/code&gt;, outro que só mostra
&lt;code&gt;ERROR&lt;/code&gt; no terminal com cor, outro que manda &lt;code&gt;INFO&lt;/code&gt; pro Telegram (por que não?).&lt;/p&gt;
&lt;p&gt;Tudo isso é configurável via código ou por arquivos &lt;code&gt;.ini&lt;/code&gt;, &lt;code&gt;.yaml&lt;/code&gt;, &lt;code&gt;.json&lt;/code&gt;,
como preferir.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Níveis de severidade: &lt;code&gt;level&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;O nível de severidade (&lt;code&gt;level&lt;/code&gt;) aparece em dois momentos distintos:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Na configuração do logger e/ou handler.&lt;/li&gt;
&lt;li&gt;Na emissão do log.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A primeira define o que será aceito ou descartado. A segunda define qual a
severidade de um log específico.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 1. Configuração: esse logger aceita logs de WARNING pra cima
logger.setLevel(logging.WARNING)

# ...

# 2. Emissão: esse log será emitido com nível DEBUG
logger.debug(&amp;quot;sou um debug&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Como o &lt;code&gt;level&lt;/code&gt; funciona&lt;/h2&gt;
&lt;p&gt;Tanto loggers quanto handlers possuem um &lt;code&gt;level&lt;/code&gt;. Esse valor determina se uma
mensagem será processada ou descartada, com base em sua severidade.&lt;/p&gt;
&lt;p&gt;Os níveis disponíveis são:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NOTSET&lt;/code&gt; ou &lt;code&gt;0&lt;/code&gt; – sem configuração explícita.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DEBUG&lt;/code&gt; ou &lt;code&gt;10&lt;/code&gt; – detalhes técnicos para depuração (tipo &lt;code&gt;print()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INFO&lt;/code&gt; ou &lt;code&gt;20&lt;/code&gt; – informações gerais da execução.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WARNING&lt;/code&gt; ou &lt;code&gt;30&lt;/code&gt; – algo inesperado aconteceu, mas foi contornado.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ERROR&lt;/code&gt; ou &lt;code&gt;40&lt;/code&gt; – erro durante a execução.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CRITICAL&lt;/code&gt; ou &lt;code&gt;50&lt;/code&gt; – erro grave. A aplicação pode parar ou já parou.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Você pode usar os nomes (&lt;code&gt;DEBUG&lt;/code&gt;, &lt;code&gt;INFO&lt;/code&gt;...) ou os números diretamente.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;O valor numérico importa porque a regra é: &lt;strong&gt;um log só será processado se o
&lt;code&gt;level&lt;/code&gt; desse log for maior ou igual ao &lt;code&gt;level&lt;/code&gt; do logger e do handler.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Exemplo prático usando &lt;code&gt;level&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;logger.setLevel(logging.ERROR)

logger.debug(&amp;quot;DEBUG&amp;quot;)     # ignorado
logger.warning(&amp;quot;WARNING&amp;quot;) # ignorado
logger.error(&amp;quot;ERROR&amp;quot;)     # processado
logger.critical(&amp;quot;CRITICAL&amp;quot;) # processado
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Emissão de um log em um &lt;code&gt;level&lt;/code&gt; específico&lt;/h2&gt;
&lt;p&gt;Para emitir um log, usamos os métodos do logger que correspondem ao nível de
severidade desejado:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;logger.debug(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logger.info(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logger.warning(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logger.error(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logger.critical(...)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Isso emite um log com nível WARNING (30)
logger.warning(&amp;quot;mensagem do meu log&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Neste caso, estamos emitindo uma mensagem com nível 30 (&lt;code&gt;WARNING&lt;/code&gt;). Ela será
avaliada pelo logger e por cada handler configurado, e só será processada se
passar pelos filtros de &lt;code&gt;level&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;basicConfig: iniciando com &lt;code&gt;logging&lt;/code&gt; no código&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;basicConfig&lt;/code&gt; é uma função do módulo &lt;code&gt;logging&lt;/code&gt;, feita para configurar o &lt;code&gt;root&lt;/code&gt;
logger de forma simples e rápida. Geralmente, ela será usada em scripts menores
para facilitar a configuração rápida do &lt;code&gt;logging&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Dependendo de quais argumentos forem usados, ela pode configurar handlers
diferentes no &lt;code&gt;root logger&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code&gt;StreamHandler&lt;/code&gt;: saída para &lt;code&gt;stderr&lt;/code&gt; ou &lt;code&gt;stdout&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Se você não enviar o argumento &lt;code&gt;filename&lt;/code&gt; para &lt;code&gt;basicConfig&lt;/code&gt;, o handler padrão
usado será um &lt;code&gt;StreamHandler&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;O termo &lt;em&gt;stream&lt;/em&gt; se refere a objetos semelhantes a arquivos, ou seja, objetos
que possuem métodos como &lt;code&gt;.write()&lt;/code&gt; e &lt;code&gt;.flush()&lt;/code&gt;. Tanto &lt;code&gt;sys.stdout&lt;/code&gt; quanto
&lt;code&gt;sys.stderr&lt;/code&gt; são exemplos desses objetos.&lt;/p&gt;
&lt;p&gt;Por padrão, o &lt;code&gt;StreamHandler&lt;/code&gt; escreve no &lt;code&gt;stderr&lt;/code&gt;. Já o &lt;code&gt;print()&lt;/code&gt; padrão do
Python escreve no &lt;code&gt;stdout&lt;/code&gt;. Ou seja: ambos fazem &lt;code&gt;print&lt;/code&gt;, mas vão pra fluxos
diferentes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; É possível trocar o stream da classe &lt;code&gt;StreamHandler&lt;/code&gt;, passando um
argumento como &lt;code&gt;sys.stdout&lt;/code&gt;, mas isso foge do nosso foco aqui.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Código: &lt;code&gt;basicConfig&lt;/code&gt; + &lt;code&gt;StreamHandler&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Primeiro, importamos o módulo &lt;code&gt;logging&lt;/code&gt; e definimos o formato de cada linha de
log:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging

# Formato do log - veja todos os atributos disponíveis em:
# https://docs.python.org/3/library/logging.html#logrecord-attributes
simple_format = &amp;quot;%(levelname)s|%(name)s|%(asctime)s|%(message)s|%(filename)s|%(lineno)d&amp;quot;

# Isso configura o root logger conforme explico a seguir.
logging.basicConfig(format=simple_format)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O trecho acima faz o seguinte:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cria um &lt;code&gt;StreamHandler&lt;/code&gt; que envia os logs para o &lt;code&gt;stderr&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Aplica um &lt;code&gt;Formatter&lt;/code&gt; com o formato definido;&lt;/li&gt;
&lt;li&gt;Adiciona esse handler ao &lt;em&gt;root logger&lt;/em&gt;;&lt;/li&gt;
&lt;li&gt;Define o nível do &lt;em&gt;root logger&lt;/em&gt; como &lt;code&gt;WARNING&lt;/code&gt; (padrão do Python, caso não
seja especificado).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Com isso, o &lt;em&gt;root logger&lt;/em&gt; já está pronto pra uso. Basta fazer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Logando com o root (não vamos usar isso, só exemplo)
logging.warning(&amp;quot;Oi!&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; O &lt;code&gt;basicConfig&lt;/code&gt; aceita vários outros argumentos, como &lt;code&gt;level&lt;/code&gt;,
&lt;code&gt;filename&lt;/code&gt;, &lt;code&gt;filemode&lt;/code&gt;, &lt;code&gt;handlers&lt;/code&gt;, &lt;code&gt;stream&lt;/code&gt; e mais. Se você quiser, pode
definir o nível diretamente, por exemplo: &lt;code&gt;level=logging.DEBUG&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;getLogger()&lt;/code&gt; cria ou acessa nosso próprio logger&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Com o &lt;em&gt;root logger&lt;/em&gt; configurado, a gente pode usar a função &lt;code&gt;getLogger()&lt;/code&gt; para
criar (ou acessar) nossos próprios loggers. Essa função tem três comportamentos
distintos, dependendo do argumento:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;logging.getLogger()&lt;/code&gt;: sem argumento, retorna o &lt;em&gt;root logger&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logging.getLogger(&amp;quot;meuapp&amp;quot;)&lt;/code&gt;: com um nome, cria um novo logger se ainda não
existir.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logging.getLogger(&amp;quot;meuapp&amp;quot;)&lt;/code&gt;: nas próximas chamadas com o mesmo nome, retorna
o mesmo logger criado anteriormente (singleton por nome).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depois de criado, você pode definir o &lt;strong&gt;nível de severidade&lt;/strong&gt; que esse logger
vai aceitar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;logger = logging.getLogger(&amp;quot;meuapp&amp;quot;)
logger.setLevel(logging.DEBUG)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Feito isso, já pode começar a mandar logs e ver tudo aparecendo no terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Exibe logs com StreamHandler
# debug info warning error critical
logger.debug(&amp;quot;mensagem de log&amp;quot;)
logger.info(&amp;quot;mensagem de log&amp;quot;)
logger.warning(&amp;quot;mensagem de log&amp;quot;)
logger.error(&amp;quot;mensagem de log&amp;quot;)
logger.critical(&amp;quot;mensagem de log&amp;quot;)

# Exception
try:
    print(1 / 0)
except ZeroDivisionError:
    logger.exception(&amp;quot;dividiu por zero aí&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Saída esperada:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DEBUG|meuapp|2025-06-29 17:36:09,226|mensagem de log|main.py|24
INFO|meuapp|2025-06-29 17:36:09,226|mensagem de log|main.py|25
WARNING|meuapp|2025-06-29 17:36:09,226|mensagem de log|main.py|26
ERROR|meuapp|2025-06-29 17:36:09,226|mensagem de log|main.py|27
CRITICAL|meuapp|2025-06-29 17:36:09,226|mensagem de log|main.py|28
ERROR|meuapp|2025-06-29 17:36:09,226|dividiu por zero aí|main.py|34
Traceback (most recent call last):
  File &amp;quot;main.py&amp;quot;, line 32, in &amp;lt;module&amp;gt;
    print(1 / 0)
          ~~^~~
ZeroDivisionError: division by zero
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Código completo para &lt;code&gt;basicConfig&lt;/code&gt; e &lt;code&gt;StreamHandler&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;O código completo ficou assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging

# Formato para o Formatter
# Veja os atributos disponíveis em:
# https://docs.python.org/3/library/logging.html#logrecord-attributes
simple_format = &amp;quot;%(levelname)s|%(name)s|%(asctime)s|%(message)s|%(filename)s|%(lineno)d&amp;quot;
logging.basicConfig(format=simple_format)

# cria o meu logger &amp;quot;meuapp&amp;quot;
logger = logging.getLogger(&amp;quot;meuapp&amp;quot;)
logger.setLevel(logging.DEBUG)

logger.debug(&amp;quot;mensagem de log&amp;quot;)
logger.info(&amp;quot;mensagem de log&amp;quot;)
logger.warning(&amp;quot;mensagem de log&amp;quot;)
logger.error(&amp;quot;mensagem de log&amp;quot;)
logger.critical(&amp;quot;mensagem de log&amp;quot;)

try:
    print(1 / 0)
except ZeroDivisionError:
    logger.exception(&amp;quot;Alguém tentou dividir por zero aí.&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code&gt;FileHandler&lt;/code&gt;: saída para um arquivo&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;basicConfig&lt;/code&gt; também é capaz de escrever logs em arquivos mudando levemente os
argumentos. Precisamos passar o caminho do arquivo de log e o modo de abertura
desejado para esse arquivo.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Se você mudar a pasta onde o arquivo deverá ser salvo, essa pasta
precisa existir, do contrário terá um erro.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Peguei só o trecho que precisa de modificação no código anterior:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging

format1 = &amp;quot;%(levelname)s|%(name)s|%(asctime)s|%(message)s|%(filename)s|%(lineno)d&amp;quot;

# Migrar para arquivo: basta informar o caminho, modo de abertura e encoding.
logging.basicConfig(
    level=logging.DEBUG,
    format=format1,
    filename=&amp;quot;log.log&amp;quot;,
    filemode=&amp;quot;a&amp;quot;,
    encoding=&amp;quot;utf-8&amp;quot;,
)

# Meu logger -&amp;gt; meuapp (root -&amp;gt; meuapp)
logger = logging.getLogger(&amp;quot;meuapp&amp;quot;)

# Agora você não verá mais saída no console
logger.debug(&amp;quot;Isso deve aparecer no arquivo log.log&amp;quot;)

# Arquivo log.log
# DEBUG|meuapp|2025-07-05 10:12:30,311|Isso deve aparecer no arquivo log.log|lesson01.py|20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ao fazer essa configuração, em vez de usar o &lt;code&gt;StreamHandler&lt;/code&gt; que vimos
anteriormente, agora ele usa o &lt;code&gt;FileHandler&lt;/code&gt;. Para esse handler, precisamos do
caminho do arquivo (&lt;code&gt;filename&lt;/code&gt;), do modo de abertura (&lt;code&gt;filemode&lt;/code&gt;) e do
&lt;code&gt;encoding&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Um ponto importante aqui é que você &lt;strong&gt;não verá mais nada no terminal&lt;/strong&gt;, porque o
nosso handler agora envia apenas para o arquivo, e não para &lt;code&gt;stderr&lt;/code&gt; ou
&lt;code&gt;stdout&lt;/code&gt;. Para ver ambos, precisamos de mais de um handler.&lt;/p&gt;
&lt;p&gt;O &lt;code&gt;filemode&lt;/code&gt; pode ser qualquer um dos modos de escrita, por exemplo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;a&lt;/code&gt; - escreve no final do arquivo (não apaga logs antigos, apenas adiciona)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;w&lt;/code&gt; - escreve no começo do arquivo (apaga tudo que estava lá)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;code&gt;basicConfig&lt;/code&gt;, &lt;code&gt;FileHandler&lt;/code&gt; e &lt;code&gt;StreamHandler&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Que tal a gente combinar tudo isso e adicionar dois handlers de uma vez? Bora
ver como usar &lt;code&gt;FileHandler&lt;/code&gt; e &lt;code&gt;StreamHandler&lt;/code&gt; junto com a função &lt;code&gt;basicConfig&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Vou mandar o código completo pra ficar mais fácil de entender.&lt;/p&gt;
&lt;p&gt;No exemplo abaixo, a gente cria os dois handlers manualmente e adiciona eles no
logger &lt;code&gt;root&lt;/code&gt; via &lt;code&gt;basicConfig&lt;/code&gt;. O resto continua o padrão de sempre.&lt;/p&gt;
&lt;p&gt;A propagação (&lt;code&gt;propagate = True&lt;/code&gt;) já fica ativa por padrão. Isso quer dizer que,
quando chamamos algo no nosso logger (que não tem handler nenhum), o log é
&lt;strong&gt;propagado&lt;/strong&gt; para o logger acima dele, no caso, o &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Como o &lt;code&gt;root&lt;/code&gt; agora tem dois handlers, &lt;strong&gt;os dois são executados&lt;/strong&gt; quando o nível
de severidade bate. O resultado? Log no terminal &lt;strong&gt;e&lt;/strong&gt; no arquivo ao mesmo
tempo.&lt;/p&gt;
&lt;p&gt;Você poderia ter quantos handlers e formatters quisesse, todos pendurados no
&lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging
import sys

format1 = &amp;quot;%(levelname)s|%(name)s|%(asctime)s|%(message)s|%(filename)s|%(lineno)d&amp;quot;

# Podemos criar nossos próprios handlers usando as classes que mencionei antes
file_handler = logging.FileHandler(
    filename=&amp;quot;log.log&amp;quot;,
    mode=&amp;quot;a&amp;quot;,
    encoding=&amp;quot;utf-8&amp;quot;,
)
stream_handler = logging.StreamHandler(sys.stdout)

# Nossos handlers precisam de um formatter
main_formatter = logging.Formatter(fmt=format1)

# A configuração do formatter pode ser reutilizada.
file_handler.setFormatter(main_formatter)
stream_handler.setFormatter(main_formatter)

# configura o root logger
logging.basicConfig(handlers=[file_handler, stream_handler])

# cria o meu logger
logger = logging.getLogger(&amp;quot;meuapp&amp;quot;)
# define o nível do meu log
logger.setLevel(logging.DEBUG)

# Saída nos dois handlers
logger.debug(&amp;quot;mensagem de log&amp;quot;)
logger.info(&amp;quot;mensagem de log&amp;quot;)
logger.warning(&amp;quot;mensagem de log&amp;quot;)
logger.error(&amp;quot;mensagem de log&amp;quot;)
logger.critical(&amp;quot;mensagem de log&amp;quot;)

# Exception
try:
    print(1 / 0)
except ZeroDivisionError:
    logger.exception(&amp;quot;Alguém tentou dividir por zero aí.&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A saída do código acima fica como mostro abaixo tanto no console quando no
terminal.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;DEBUG|meuapp|2025-07-05 10:59:37,842|mensagem de log|lesson05_01.py|41
INFO|meuapp|2025-07-05 10:59:37,842|mensagem de log|lesson05_01.py|42
WARNING|meuapp|2025-07-05 10:59:37,842|mensagem de log|lesson05_01.py|43
ERROR|meuapp|2025-07-05 10:59:37,842|mensagem de log|lesson05_01.py|44
CRITICAL|meuapp|2025-07-05 10:59:37,842|mensagem de log|lesson05_01.py|45
ERROR|meuapp|2025-07-05 10:59:37,843|Alguém tentou dividir por zero aí.|lesson05_01.py|51
Traceback (most recent call last):
  File &amp;quot;/Users/luizotavio/Desktop/tutoriais_e_cursos/logging_course_yt/src/logging_course/lesson05_01.py&amp;quot;, line 49, in &amp;lt;module&amp;gt;
    print(1 / 0)
          ~~^~~
ZeroDivisionError: division by zero
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Hierarquia de Loggers e Handlers&lt;/h2&gt;
&lt;p&gt;Para unir a teoria à prática que exploramos, e para evitar as armadilhas mais
comuns, vamos entender com precisão o que acontece no sistema de &lt;code&gt;logging&lt;/code&gt; do
Python:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pontos Fundamentais para Lembrar:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;basicConfig()&lt;/code&gt;&lt;/strong&gt;: É a função de conveniência que configura o &lt;code&gt;root&lt;/code&gt; logger
(o &amp;quot;pai&amp;quot; de todos). Por padrão, ele adiciona um &lt;code&gt;StreamHandler&lt;/code&gt; (para o
console), um &lt;code&gt;Formatter&lt;/code&gt; básico e define o &lt;code&gt;level&lt;/code&gt; do &lt;code&gt;root&lt;/code&gt; para &lt;code&gt;WARNING&lt;/code&gt;.
Em sistemas menores, é tranquilo usar, mas em sistemas maiores usaremos
&lt;code&gt;dictConfig&lt;/code&gt; que falaremos adiante.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;getLogger(&amp;#39;nome&amp;#39;)&lt;/code&gt;&lt;/strong&gt;: Cria (ou obtém) um &lt;code&gt;Logger&lt;/code&gt; nomeado, que é filho do
&lt;code&gt;root&lt;/code&gt; logger (ou de outro logger se o nome tiver pontos, como &lt;code&gt;app.modulo&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;setLevel()&lt;/code&gt;&lt;/strong&gt;: Usado para definir o nível mínimo de mensagens que um
&lt;code&gt;Logger&lt;/code&gt; ou um &lt;code&gt;Handler&lt;/code&gt; irá processar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Métodos de Log&lt;/strong&gt;: &lt;code&gt;debug()&lt;/code&gt;, &lt;code&gt;info()&lt;/code&gt;, &lt;code&gt;warning()&lt;/code&gt;, &lt;code&gt;error()&lt;/code&gt; e
&lt;code&gt;critical()&lt;/code&gt; são os métodos que você usa em um &lt;code&gt;Logger&lt;/code&gt; para emitir mensagens
com um nível de gravidade específico.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;A Grande Sacada: A Propagação e a Filtragem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Esta é a parte crucial e onde a maioria dos materiais simplifica demais. Preste
muita atenção aqui!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Múltiplos Handlers:&lt;/strong&gt; Um &lt;code&gt;Logger&lt;/code&gt; pode ter um ou vários &lt;code&gt;Handlers&lt;/code&gt; anexados
a ele.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handlers e Formatters:&lt;/strong&gt; Cada &lt;code&gt;Handler&lt;/code&gt; é responsável por direcionar o log
para um destino (console, arquivo, etc.) e sempre usará um único &lt;code&gt;Formatter&lt;/code&gt;
para definir como a mensagem será exibida. Ele também pode ter &lt;code&gt;Filters&lt;/code&gt; (que
veremos depois) para um controle ainda mais fino.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Níveis Duplos:&lt;/strong&gt; Tanto o &lt;code&gt;Logger&lt;/code&gt; (o coletor de logs) quanto o &lt;code&gt;Handler&lt;/code&gt; (o
publicador de logs) possuem seu próprio &lt;code&gt;level&lt;/code&gt;. Esta é a fonte de possíveis
problemas e pode te deixar debugando o próprio &lt;code&gt;logging&lt;/code&gt; por horas.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;A Propagação: O Caminho Real do Log (e o Papel dos Handlers)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Quando um log é emitido por um &lt;code&gt;Logger&lt;/code&gt; nomeado, ele percorre uma jornada pela
hierarquia. Esse &amp;quot;caminho&amp;quot; pode ser confuso, porque não é apenas o &lt;code&gt;Logger&lt;/code&gt; que
está filtrando, os &lt;code&gt;Handlers&lt;/code&gt; também entram no jogo.&lt;/p&gt;
&lt;p&gt;Imagine a hierarquia de loggers assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;root           [root_handler_1, root_handler_2]
└── A          [A_handler_1]
    └── A.B    [B_handler_1, B_handler_2, B_handler_3]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vamos supor que o &lt;code&gt;level&lt;/code&gt; de &lt;code&gt;A.B&lt;/code&gt; foi configurado como &lt;code&gt;INFO&lt;/code&gt; (20) e que
emitimos:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;A.B.warning(&amp;quot;Mensagem de Exemplo&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;No &lt;code&gt;Logger&lt;/code&gt; &lt;code&gt;A.B&lt;/code&gt; (o emissor do log)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A primeira filtragem acontece aqui. O log tem &lt;code&gt;level&lt;/code&gt; &lt;code&gt;WARNING&lt;/code&gt; (30), e o logger
&lt;code&gt;A.B&lt;/code&gt; aceita logs com &lt;code&gt;INFO&lt;/code&gt; (20) ou superior. Então a verificação é simples:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;level do log &amp;gt;= level do logger → ok
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se passar aqui, segue adiante. Se &lt;strong&gt;não&lt;/strong&gt; passar, o log morre aqui, &lt;strong&gt;não chega
em nenhum handler&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nos Handlers de &lt;code&gt;A.B&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Agora o log chega nos &lt;code&gt;Handlers&lt;/code&gt; do logger &lt;code&gt;A.B&lt;/code&gt;, no exemplo: &lt;code&gt;B_handler_1&lt;/code&gt;,
&lt;code&gt;B_handler_2&lt;/code&gt; e &lt;code&gt;B_handler_3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Cada handler faz &lt;strong&gt;sua própria checagem de level&lt;/strong&gt;. Se o log for aceito, é
publicado por aquele handler. Se não, é ignorado por ele (mas ainda pode ser
aceito pelos outros handlers ou loggers superiores).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No Logger &lt;code&gt;A&lt;/code&gt; (pai de &lt;code&gt;A.B&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Se a propriedade &lt;code&gt;propagate&lt;/code&gt; estiver &lt;code&gt;True&lt;/code&gt; (valor padrão), o log &lt;strong&gt;sobe na
hierarquia&lt;/strong&gt; e chega ao logger &lt;code&gt;A&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Mas aqui vem a &lt;strong&gt;pegadinha&lt;/strong&gt;: o &lt;code&gt;level&lt;/code&gt; do logger &lt;code&gt;A&lt;/code&gt; &lt;strong&gt;não importa mais&lt;/strong&gt;. Ele
&lt;strong&gt;não é checado&lt;/strong&gt;. A partir daqui, só os &lt;code&gt;handlers&lt;/code&gt; dos loggers pais é que
decidem o que fazer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nos Handlers de &lt;code&gt;A&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cada handler de &lt;code&gt;A&lt;/code&gt; (ex: &lt;code&gt;A_handler_1&lt;/code&gt;) faz a mesma checagem de sempre:
&lt;code&gt;level do log &amp;gt;= level do handler&lt;/code&gt;. Se passar, o log é publicado.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No Logger &lt;code&gt;root&lt;/code&gt; (e seus handlers)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Mais do mesmo: o log chega no logger &lt;code&gt;root&lt;/code&gt;, ignora o &lt;code&gt;level&lt;/code&gt; dele, e é
analisado apenas pelos handlers &lt;code&gt;root_handler_1&lt;/code&gt; e &lt;code&gt;root_handler_2&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Essa explicação é importante porque muita gente pensa que os loggers pais têm o
poder de &lt;strong&gt;interromper&lt;/strong&gt; ou &lt;strong&gt;descartar&lt;/strong&gt; um log que subiu pela propagação. Mas
não têm. O único logger com poder de barrar a mensagem é &lt;strong&gt;o que emitiu o log&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Se fosse pra resumir num post-it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;O log é verificado &lt;strong&gt;no logger que o emitiu&lt;/strong&gt;. Se passar, é entregue aos seus
&lt;code&gt;handlers&lt;/code&gt; e, com &lt;code&gt;propagate = True&lt;/code&gt;, sobe para os loggers pais. Mas a partir
daí, &lt;strong&gt;apenas os &lt;code&gt;handlers&lt;/code&gt; desses loggers superiores&lt;/strong&gt; fazem checagem. Os
loggers pais &lt;strong&gt;não barram&lt;/strong&gt; mais nada.&lt;/p&gt;
&lt;p&gt;Se o logger emissor não aceitar o log, &lt;strong&gt;acabou o jogo ali&lt;/strong&gt;. Nada é
publicado.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;Loggers de terceiros podem entrar no seu log&lt;/h2&gt;
&lt;p&gt;Se você usar o &lt;code&gt;logging&lt;/code&gt; de forma incorreta: tipo deixando o &lt;code&gt;level&lt;/code&gt; do &lt;code&gt;root&lt;/code&gt;
em &lt;code&gt;DEBUG&lt;/code&gt; ou usando o &lt;code&gt;root&lt;/code&gt; como logger principal por algum motivo. Uma coisa
muito comum que costuma ocorrer é você começar a ver logs de &lt;code&gt;DEBUG&lt;/code&gt; de
aplicações de terceiros no seu log.&lt;/p&gt;
&lt;p&gt;Geralmente queremos logs de libs de terceiros no nosso log, mas não um milhão de
&lt;code&gt;DEBUGs&lt;/code&gt;. Talvez apenas de &lt;code&gt;WARNING&lt;/code&gt; para cima, ou até de &lt;code&gt;ERROR&lt;/code&gt; para cima.&lt;/p&gt;
&lt;p&gt;Vamos simular e corrigir isso a seguir.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code&gt;rich&lt;/code&gt;: cobaia e nossos logs mais bonitos no console&lt;/h2&gt;
&lt;p&gt;Vamos usar a biblioteca &lt;code&gt;rich&lt;/code&gt; pra deixar os logs do console mais agradáveis de
ler, com cor, syntax highlight e outros agrados visuais. Só que vamos usar essa
lib bem mais adiante no texto.&lt;/p&gt;
&lt;p&gt;Mesmo assim, instale-a com o gerenciador que preferir e vamos usar ela mesmo
como exemplo de log de terceiros:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Usando uv
uv add rich

# Ou com pip
pip install rich
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Exemplo prático: logs de terceiros no seu log com rich&lt;/h2&gt;
&lt;p&gt;Vamos configurar o &lt;code&gt;logging&lt;/code&gt; novamente, do zero, só que com &lt;code&gt;root&lt;/code&gt; configurado
em &lt;code&gt;level&lt;/code&gt; &lt;code&gt;DEBUG&lt;/code&gt; (Não recomendado):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging

simple_format = &amp;quot;%(levelname)s|%(name)s|%(asctime)s|%(message)s|%(filename)s|%(lineno)d&amp;quot;

# Configura o root logger com:
# - StreamHandler (stderr)
# - Formatter customizado
# - Level DEBUG
logging.basicConfig(format=simple_format, level=logging.DEBUG)

# Cria o nosso logger, mas sem handlers próprios
logger = logging.getLogger(&amp;quot;meuapp&amp;quot;)
logger.setLevel(logging.DEBUG)

# Apenas um teste
logger.warning(&amp;quot;Isso é um teste&amp;quot;)

# Saída esperada:
# WARNING|meuapp|2025-07-01 19:44:54,566|Isso é um teste|main.py|14
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora, vamos simular o uso do &lt;code&gt;rich&lt;/code&gt;, que é uma lib que &lt;strong&gt;também usa o módulo
&lt;code&gt;logging&lt;/code&gt; internamente&lt;/strong&gt;. Suponha que você queira imprimir um markdown no
terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from rich import print as rprint
from rich.markdown import Markdown

md = Markdown(&amp;quot;# Nos Handlers de `A`&amp;quot;)
rprint(md)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se algum código interno do &lt;code&gt;rich&lt;/code&gt; emitir logs, mesmo que você não peça, esses
logs vão aparecer, porque o &lt;code&gt;root&lt;/code&gt; logger está &lt;strong&gt;aberto no nível &lt;code&gt;DEBUG&lt;/code&gt;&lt;/strong&gt; e
&lt;strong&gt;todos os handlers estão lá&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Esse é o tipo de poluição que você pode querer evitar.&lt;/p&gt;
&lt;p&gt;Isso era só pra ser um código normal, simulando algo dentro da minha aplicação.
Mas olha só o que apareceu no log:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; eu cortei bastante texto do log abaixo, mas dependendo do que você
estiver fazendo, pode ficar &lt;strong&gt;absurdamente longo&lt;/strong&gt;. Em alguns casos, aparecem
&lt;strong&gt;tantos logs&lt;/strong&gt; que sua aplicação (ou até seu computador) pode travar.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;WARNING|meuapp|2025-07-01 20:02:21,221|Isso é um teste|main.py|14
DEBUG|markdown_it.rules_block.code|2025-07-01 20:02:21,271...
DEBUG|markdown_it.rules_block.fence|2025-07-01 20:02:21,271...
DEBUG|markdown_it.rules_block.blockquote|2025-07-01 20:02:21,271...
DEBUG|markdown_it.rules_block.hr|2025-07-01 20:02:21,271...
DEBUG|markdown_it.rules_block.list|2025-07-01 20:02:21,271...
DEBUG|markdown_it.rules_block.reference|2025-07-01 20:02:21,271...
DEBUG|markdown_it.rules_block.html_block|2025-07-01 20:02:21,271...
DEBUG|markdown_it.rules_block.heading|2025-07-01 20:02:21,271...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Qual o problema aqui? Nenhum, &lt;strong&gt;se for intencional&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Só aconteceu que a sua aplicação e o &lt;code&gt;rich&lt;/code&gt; decidiram seguir a mesma estratégia:
&lt;strong&gt;usar o &lt;code&gt;root&lt;/code&gt; logger pra tudo&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Quando você configura o &lt;code&gt;root&lt;/code&gt; com um nível permissivo (tipo &lt;code&gt;DEBUG&lt;/code&gt;), qualquer
lib que use o sistema de logging do Python pode ter suas mensagens &lt;strong&gt;publicadas
nos seus handlers&lt;/strong&gt;, inclusive aquelas que você nunca pediu pra logar.&lt;/p&gt;
&lt;p&gt;Como mencionei antes, é normal querermos logs de libs de terceiros no nosso log.
Elas fazem parte da nossa aplicação, porém, em um nível interessante, como
&lt;code&gt;WARNING&lt;/code&gt; ou &lt;code&gt;ERROR&lt;/code&gt;, e acima.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Soluções possíveis para log de libs de terceiros&lt;/h2&gt;
&lt;p&gt;Tem várias coisas que a gente pode fazer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Subir o &lt;code&gt;level&lt;/code&gt; do logger que está causando problema&lt;/li&gt;
&lt;li&gt;Configurar o &lt;code&gt;root&lt;/code&gt; adequadamente (de &lt;code&gt;WARNING&lt;/code&gt; para cima)&lt;/li&gt;
&lt;li&gt;Nunca usar o &lt;code&gt;root&lt;/code&gt; logger diretamente como logger da sua aplicação&lt;/li&gt;
&lt;li&gt;Isolar o seu logger do &lt;code&gt;root&lt;/code&gt; (não recomendado, mas pode ser útil se está
criando uma lib para outras pessoas)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Como subir o &lt;code&gt;level&lt;/code&gt; de logs de terceiros com &lt;code&gt;getLogger&lt;/code&gt;?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No próprio log que poluiu meu console e arquivo, a &lt;strong&gt;segunda coluna&lt;/strong&gt; mostra o
nome do logger: &lt;code&gt;markdown_it&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;Muita gente configura seus loggers como mostro a seguir. Isso é até uma
recomendação da documentação se eu não estiver enganado:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;logger = logging.getLogger(__name__)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isso cria (ou acessa) um logger com o nome do módulo onde está sendo executado.
Exemplo: se você está em um pacote chamado &lt;code&gt;learn_logging&lt;/code&gt;, e o módulo se chama
&lt;code&gt;config_logger.py&lt;/code&gt;, o nome do logger será:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__main__&lt;/code&gt; se executar direto&lt;/li&gt;
&lt;li&gt;&lt;code&gt;learn_logging.config_logger&lt;/code&gt; se importar de outro lugar&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mas voltando ao problema: se o log vem de &lt;code&gt;markdown_it&lt;/code&gt;, basta fazer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;logging.getLogger(&amp;quot;markdown_it&amp;quot;).setLevel(logging.INFO)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Geralmente isso silencia o &lt;code&gt;logger&lt;/code&gt; que está jogando um monte de mensagens no
seu logger. Mas e se forem muitos? E se forem todas as 10 libs externas que você
está usando num código só? E se as 10 libs que você instalou também usam outras
100 libs? Fica impraticável.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Meus handlers nos meus loggers&lt;/h2&gt;
&lt;p&gt;Você pode configurar os &lt;strong&gt;handlers no seu próprio logger&lt;/strong&gt;. Como estamos usando
&lt;code&gt;basicConfig&lt;/code&gt;, os handlers estavam indo pro &lt;code&gt;root&lt;/code&gt;. Agora vamos fazer de outro
modo. Só um exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging

# Cria um handler só nosso
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)

# Cria um formatter
simple_format = &amp;quot;%(levelname)s|%(name)s|%(asctime)s|%(message)s|%(filename)s|%(lineno)d&amp;quot;
main_formatter = logging.Formatter(fmt=simple_format)
stream_handler.setFormatter(main_formatter)

# CADÊ O ROOT??? sumiu 😎

logger = logging.getLogger(&amp;quot;meuapp&amp;quot;)
logger.setLevel(logging.DEBUG)
logger.addHandler(stream_handler)

logger.info(&amp;quot;mensagem de log&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pronto. Agora você não verá mais logs do &lt;code&gt;rich&lt;/code&gt; ou de qualquer lib de terceiro.
&lt;strong&gt;Não porque bloqueamos eles&lt;/strong&gt;, mas porque &lt;strong&gt;não estamos mais ouvindo o
&lt;code&gt;root&lt;/code&gt;&lt;/strong&gt;. E como essas libs usam o &lt;code&gt;root&lt;/code&gt;, seus logs vão pra lugar nenhum.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; isso pode ser um problema, visto que libs de terceiros fazem parte
da nossa aplicação. &lt;br&gt;&lt;br&gt;Omitir logs de libs de terceiros é como omitir logs de uma parte do código que
realmente vai para produção. &lt;br&gt;&lt;br&gt;O que eu faria nesse caso, seria manter um handler menos importante no meu
próprio logger com level &lt;code&gt;DEBUG&lt;/code&gt; (vejo tudo no meu handler), mas colocaria os
handlers importantes, com níveis de &lt;code&gt;WARNING&lt;/code&gt; para cima, no &lt;code&gt;root&lt;/code&gt; para
continuar tendo acesso aos logs de libs de terceiros. &lt;br&gt;&lt;br&gt;Cada caso é um caso a ser estudado.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code&gt;dictConfig&lt;/code&gt; - agora vamos aos negócios&lt;/h2&gt;
&lt;p&gt;Como mencionei quando comecei o trecho sobre &lt;code&gt;basicConfig&lt;/code&gt;, ela é realmente
básica. E o que é mais contraditório nisso tudo, é que se a sua configuração for
levemente mais complexa, ela se torna extremamente complexa também.&lt;/p&gt;
&lt;p&gt;A forma mais completa (e moderna) de configurar o &lt;code&gt;logging&lt;/code&gt; no Python é usando
&lt;code&gt;dictConfig&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Como o próprio nome indica, ao invés de ficar configurando tudo solto como
quando usamos &lt;code&gt;basicConfig&lt;/code&gt;, com &lt;code&gt;dictConfig&lt;/code&gt; podemos montar toda a configuração
usando um único dicionário. Isso te permite, inclusive, criar arquivos de
configuração em outros formatos como JSON ou YAML, e carregar esse arquivo ao
subir a aplicação.&lt;/p&gt;
&lt;p&gt;Vamos migrar essa configuração para &lt;code&gt;dictConfig&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging

file_handler = logging.FileHandler(filename=&amp;quot;log.log&amp;quot;, mode=&amp;quot;a&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;)
stream_handler = logging.StreamHandler()

simple_format = &amp;quot;%(levelname)s|%(name)s|%(asctime)s|%(message)s|%(filename)s|%(lineno)d&amp;quot;

main_formatter = logging.Formatter(fmt=simple_format)
file_handler.setFormatter(main_formatter)
stream_handler.setFormatter(main_formatter)

logging.basicConfig(handlers=[file_handler, stream_handler])

logger = logging.getLogger(&amp;quot;meuapp&amp;quot;)
logger.setLevel(logging.DEBUG)

logger.debug(&amp;quot;mensagem de log&amp;quot;)
logger.warning(&amp;quot;mensagem de log&amp;quot;)
logger.error(&amp;quot;mensagem de log&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aqui temos o seguinte:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2 handlers: &lt;code&gt;FileHandler&lt;/code&gt; e &lt;code&gt;StreamHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;1 formatter: &lt;code&gt;Formatter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A configuração do &lt;code&gt;root&lt;/code&gt; com os 2 handlers&lt;/li&gt;
&lt;li&gt;A configuração no nosso logger &lt;code&gt;meuapp&lt;/code&gt; com &lt;code&gt;level&lt;/code&gt; &lt;code&gt;DEBUG&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Com &lt;code&gt;dictConfig&lt;/code&gt; isso ficaria assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging
from logging.config import dictConfig
from typing import Any

# Dicionário de Configuração de Logging
# A configuração centralizada para o `dictConfig`.
LOGGING_CONFIG: dict[str, Any] = {
    # Versão da configuração. Sempre 1 por enquanto.
    &amp;quot;version&amp;quot;: 1,
    # &amp;#39;False&amp;#39; para não desativar loggers de bibliotecas de terceiros.
    &amp;quot;disable_existing_loggers&amp;quot;: False,
    # Aqui vamos configurar o(s) formatter(s)
    &amp;quot;formatters&amp;quot;: {
        # A chave é o nome que você usa para se referir a esse formatter
        &amp;quot;main_formatter&amp;quot;: {
            # As configs são `chave`: `valor` no dicionário
            # Aqui está nosso formato anterior
            &amp;quot;format&amp;quot;: &amp;quot;%(levelname)s|%(name)s|%(asctime)s|%(message)s|%(filename)s|%(lineno)d&amp;quot;,
        },
        # Se tiver mais formatters, basta adicionar mais chaves com nomes desejados
    },
    # Aqui vem os handlers
    &amp;quot;handlers&amp;quot;: {
        # É sempre a mesma ideia, a chave é o nome do handler
        &amp;quot;file_handler&amp;quot;: {
            &amp;quot;class&amp;quot;: &amp;quot;logging.FileHandler&amp;quot;,
            &amp;quot;filename&amp;quot;: &amp;quot;log.log&amp;quot;,
            &amp;quot;mode&amp;quot;: &amp;quot;a&amp;quot;,
            &amp;quot;encoding&amp;quot;: &amp;quot;utf-8&amp;quot;,
            # Perceba o nome do formatter aqui (é a chave que criei lá em formatters)
            &amp;quot;formatter&amp;quot;: &amp;quot;main_formatter&amp;quot;,
            &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
        },
        &amp;quot;stream_handler&amp;quot;: {
            &amp;quot;class&amp;quot;: &amp;quot;logging.StreamHandler&amp;quot;,
            &amp;quot;formatter&amp;quot;: &amp;quot;main_formatter&amp;quot;,
            &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
        },
    },
    # Root
    # Como o `root` é especial, tem uma chave só pra ele
    # Configuração do logger raiz, que captura todos os logs.
    &amp;quot;root&amp;quot;: {
        # Novamente, estou usando as chaves criadas em handlers
        &amp;quot;handlers&amp;quot;: [&amp;quot;file_handler&amp;quot;, &amp;quot;stream_handler&amp;quot;],
    },
    # Aqui vem nossos loggers, apesar de que não precisamos disso
    # vale te mostrar como funciona
    &amp;quot;loggers&amp;quot;: {
        &amp;quot;meuapp&amp;quot;: {
            &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
        },
    },
}
dictConfig(LOGGING_CONFIG)

logger = logging.getLogger(&amp;quot;meuapp&amp;quot;)

logger.debug(&amp;quot;mensagem de log&amp;quot;)
logger.warning(&amp;quot;mensagem de log&amp;quot;)
logger.error(&amp;quot;mensagem de log&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Não assusta não, se você olhar direitinho só temos isso:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;LOGGING_CONFIG: dict[str, Any] = {
    &amp;quot;version&amp;quot;: 1, # sempre o mesmo valor
    &amp;quot;disable_existing_loggers&amp;quot;: False, # para mim, sempre o mesmo valor
    &amp;quot;formatters&amp;quot;: {}, # aqui vem seus formatters
    &amp;quot;handlers&amp;quot;: {}, # aqui os handlers
    &amp;quot;filters&amp;quot;: {}, # aqui viriam os filters
    &amp;quot;root&amp;quot;: {}, # aqui as configurações do `root`
    &amp;quot;loggers&amp;quot;: {}, # aqui cada chave é um logger específico
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As duas primeiras chaves você nem precisa mexer, é sempre o mesmo valor. O
restante são palavras que falamos algumas centenas de vezes ao longo desse
artigo.&lt;/p&gt;
&lt;p&gt;A única coisa que pode soar estranho aqui é a estrutura, porque aqui você não
está criando variáveis, nem está passando atributos, muito menos instanciando
nada. Você só está informando o que você quer e o &lt;code&gt;logging&lt;/code&gt; com a &lt;code&gt;dictConfig&lt;/code&gt;
que se virem. A nata da programação declarativa.&lt;/p&gt;
&lt;p&gt;Eu vou te passar muitas configurações diferentes ao longo do texto, mas se
estiver com pressa, vai lá na
&lt;a href=&quot;https://docs.python.org/3/library/logging.config.html#dictionary-schema-details&quot;&gt;documentação do Python&lt;/a&gt;
para mais detalhes sobre o &lt;code&gt;Dictionary Schema Details&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Handlers padrão em &lt;code&gt;logging.handlers&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Caso precise de outros tipos de handlers para SMTP, SOCKET, HTTP e demais, antes
de sair escrevendo suas classes aí, saiba todos handlers que temos disponíveis
em &lt;code&gt;logging.handlers&lt;/code&gt; (também alguns outros espalhados fora desse módulo).&lt;/p&gt;
&lt;p&gt;É bem provável que você não precise escrever seu handler do zero. A documentação
desses handlers está em
&lt;a href=&quot;https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers&quot;&gt;Python - logging.handlers - Logging handlers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Breve resumo de cada handler:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BaseRotatingHandler&lt;/strong&gt; É a classe base para handlers que fazem rotação
automática de arquivos de log. Você geralmente não usa diretamente, só herda
dela pra criar novos handlers que rotacionam logs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RotatingFileHandler&lt;/strong&gt; Grava logs num arquivo e automaticamente cria arquivos
novos quando atingem um tamanho definido (por exemplo: 5 MB por arquivo).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TimedRotatingFileHandler&lt;/strong&gt; Similar ao anterior, mas aqui a rotação ocorre
baseada em intervalos de tempo (ex: diário, semanal, mensal).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WatchedFileHandler&lt;/strong&gt; &lt;em&gt;(Unix/Linux)&lt;/em&gt; Observa o arquivo de log e, se o arquivo
for movido, deletado ou recriado por um processo externo (ex: logrotate), o
handler percebe e reabre automaticamente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SocketHandler&lt;/strong&gt; Envia logs via rede usando sockets TCP para um servidor
remoto de logs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DatagramHandler&lt;/strong&gt; Semelhante ao SocketHandler, mas usa UDP em vez de TCP.
Isso significa que o envio dos logs é mais rápido, porém não confiável.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SysLogHandler&lt;/strong&gt; &lt;em&gt;(Linux/macOS)&lt;/em&gt; Envia logs diretamente para o syslog do
sistema operacional, integrando facilmente com o sistema operacional ou outros
serviços que escutam o syslog.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SMTPHandler&lt;/strong&gt; Envia logs por e-mail usando SMTP. Comumente usado pra logs
críticos (ex: erros inesperados em produção).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NTEventLogHandler&lt;/strong&gt; &lt;em&gt;(Windows)&lt;/em&gt; Registra logs no Event Viewer (Visualizador
de Eventos) do Windows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTPHandler&lt;/strong&gt; Envia logs via requisições HTTP para um servidor web externo,
útil em integração com APIs ou serviços de log em nuvem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BufferingHandler&lt;/strong&gt; Acumula logs na memória, e só os libera quando o buffer
atinge um limite definido. Normalmente não usado diretamente, serve como base
para handlers que implementam buffering.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MemoryHandler&lt;/strong&gt; Derivado do BufferingHandler, guarda logs na memória até que
uma condição (nível crítico ou quantidade máxima) seja atingida. Nesse
momento, os logs são repassados para outro handler configurado.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QueueHandler&lt;/strong&gt; Envia logs para uma fila (&lt;code&gt;queue.Queue&lt;/code&gt;). Ideal para
processamento assíncrono de logs, melhorando o desempenho do app principal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QueueListener&lt;/strong&gt; Complemento do QueueHandler, escuta logs em uma fila e os
despacha para outros handlers configurados. Facilita a implementação de
logging assíncrono em aplicações concorrentes.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Handlers diretamente em &lt;code&gt;logging&lt;/code&gt; (fora de &lt;code&gt;logging.handlers&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Além desses do módulo &lt;code&gt;logging.handlers&lt;/code&gt;, tem alguns handlers no módulo
principal (&lt;code&gt;logging&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;StreamHandler&lt;/strong&gt;: Escreve no terminal (&lt;code&gt;stderr&lt;/code&gt; ou &lt;code&gt;stdout&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FileHandler&lt;/strong&gt;: Escreve logs diretamente num arquivo sem rotação automática.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NullHandler&lt;/strong&gt;: Ignora completamente os logs recebidos, útil pra evitar
avisos se nenhum handler tiver configurado.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;Não tenho nem como falar sobre todos esses handlers nesse artigo, mas creio que
com o que você está aprendendo aqui, não terá dificuldade em implementar nenhum
deles para seu caso de uso específico.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code&gt;RotatingFileHandler&lt;/code&gt; e múltiplos &lt;code&gt;StreamHandler&lt;/code&gt; via &lt;code&gt;stdout&lt;/code&gt; e &lt;code&gt;stderr&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Para adicionar um pouquinho mais de complexidade (e utilidade prática!) na nossa
configuração, vamos fazer o seguinte:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Substituir o &lt;code&gt;FileHandler&lt;/code&gt; pelo &lt;code&gt;RotatingFileHandler&lt;/code&gt; (que rotaciona arquivos
automaticamente);&lt;/li&gt;
&lt;li&gt;Criar um &lt;code&gt;StreamHandler&lt;/code&gt; que envia logs de níveis &lt;code&gt;DEBUG&lt;/code&gt; e &lt;code&gt;INFO&lt;/code&gt; para o
&lt;code&gt;stdout&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Criar outro &lt;code&gt;StreamHandler&lt;/code&gt; que envia logs mais críticos (&lt;code&gt;WARNING&lt;/code&gt;, &lt;code&gt;ERROR&lt;/code&gt; e
&lt;code&gt;CRITICAL&lt;/code&gt;) para o &lt;code&gt;stderr&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Também vou criar um formatter para cada handlers. Não há necessidade real para
isso, mas quero distinção nos três logs, então preciso de três formatters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vou quebrar o dicionário em partes porque está ficando extremamente grande.
Vamos trabalhar com as chaves de primeiro nível do &lt;code&gt;dict&lt;/code&gt;: &lt;code&gt;formatters&lt;/code&gt;,
&lt;code&gt;handlers&lt;/code&gt;, &lt;code&gt;loggers&lt;/code&gt;, &lt;code&gt;filters&lt;/code&gt;, &lt;code&gt;root&lt;/code&gt;, etc. Só o que mudar.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code&gt;dictConfig&lt;/code&gt;, chave &lt;code&gt;formatters&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;quot;formatters&amp;quot;: {
    &amp;quot;file&amp;quot;: {
        &amp;quot;format&amp;quot;: (
            # Os parenteses em strings geralmente é porque é muito longa
            # Vamos usar `file` para os arquivos de log
            &amp;quot;%(levelname)s|%(name)s|%(asctime)s|%(message)s|%(filename)s|%(lineno)d&amp;quot;
        ),
        &amp;quot;datefmt&amp;quot;: &amp;quot;%Y-%m-%dT%H:%M:%S%z&amp;quot;,  # Muda o formato da data
    },
    &amp;quot;stdout&amp;quot;: {
        # Coloquei um `OUT: ` aqui pra gente conseguir distinguir no log
        &amp;quot;format&amp;quot;: &amp;quot;OUT: [%(levelname)s] %(message)s&amp;quot;,
    },
    &amp;quot;stderr&amp;quot;: {
        &amp;quot;format&amp;quot;: (
            # Coloquei um `ERR: ` aqui pra gente conseguir distinguir no log
            # Também coloquei uma cor vermelha aqui.
            # Isso não tem a ver com o dict, dictConfig ou o logging
            &amp;quot;\033[31mERR: [%(levelname)s] %(message)s - &amp;quot;
            &amp;quot;%(filename)s|%(lineno)d\033[0m&amp;quot;
        ),
    },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Certo, é bem verboso, mas isso são 3 formatters, do mesmo modo que fizemos
antes, porém em &lt;code&gt;dict&lt;/code&gt;. Os nomes desses formatters são as chaves do dicionário:
&lt;code&gt;file&lt;/code&gt;, &lt;code&gt;stdout&lt;/code&gt; e &lt;code&gt;stderr&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code&gt;dictConfig&lt;/code&gt;, chave &lt;code&gt;handlers&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;quot;handlers&amp;quot;: {
    &amp;quot;file&amp;quot;: {
        &amp;quot;class&amp;quot;: &amp;quot;logging.handlers.RotatingFileHandler&amp;quot;,
        &amp;quot;formatter&amp;quot;: &amp;quot;file&amp;quot;,  # LOGGING_CONFIG[&amp;quot;formatters&amp;quot;][&amp;quot;file&amp;quot;]
        &amp;quot;filename&amp;quot;: &amp;quot;log.log&amp;quot;,  # Arquivo principal do log
        &amp;quot;maxBytes&amp;quot;: 1024 * 1024 * 5,  # Tamanho máximo do log. 5MiB
        &amp;quot;backupCount&amp;quot;: 5,  # Máximo de backups do arquivo de log
        &amp;quot;encoding&amp;quot;: &amp;quot;utf-8&amp;quot;,  # Codificação de caracteres do arquivo
    },
    &amp;quot;stdout&amp;quot;: {
        &amp;quot;class&amp;quot;: &amp;quot;logging.StreamHandler&amp;quot;,
        &amp;quot;formatter&amp;quot;: &amp;quot;stdout&amp;quot;,  # LOGGING_CONFIG[&amp;quot;formatters&amp;quot;][&amp;quot;stream&amp;quot;]
        &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,  # 🚨 Isso vai gerar problema (tente entender)
        &amp;quot;stream&amp;quot;: &amp;quot;ext://sys.stdout&amp;quot;,  # ext é algo externo ao dict: sys.stdout
    },
    &amp;quot;stderr&amp;quot;: {
        &amp;quot;class&amp;quot;: &amp;quot;logging.StreamHandler&amp;quot;,
        &amp;quot;formatter&amp;quot;: &amp;quot;stderr&amp;quot;,  # LOGGING_CONFIG[&amp;quot;formatters&amp;quot;][&amp;quot;stream&amp;quot;]
        &amp;quot;level&amp;quot;: &amp;quot;WARNING&amp;quot;,
        &amp;quot;stream&amp;quot;: &amp;quot;ext://sys.stderr&amp;quot;,  # ext é algo externo ao dict: sys.stderr
    },
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Os nomes dos handlers são as chaves do dicionário: &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;stdout&lt;/code&gt; e &lt;code&gt;stderr&lt;/code&gt;.
Estou usando o mesmo nome para &lt;code&gt;handlers&lt;/code&gt; e &lt;code&gt;formatters&lt;/code&gt;, o que facilita a
organização.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code&gt;dictConfig&lt;/code&gt;, chaves &lt;code&gt;loggers&lt;/code&gt; e &lt;code&gt;root&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;quot;root&amp;quot;: {
    &amp;quot;handlers&amp;quot;: [&amp;quot;file&amp;quot;, &amp;quot;stdout&amp;quot;, &amp;quot;stderr&amp;quot;],
},
&amp;quot;loggers&amp;quot;: {
    &amp;quot;meuapp&amp;quot;: {
        &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
    },
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pronto, agora foi só adicionar os handlers no logger e finalizamos.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Código completo com &lt;code&gt;dictConfig&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Isso fica extremamente verboso, mas depois vamos mover esse dicionário para um
arquivo JSON.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging
from logging.config import dictConfig
from typing import Any

LOGGING_CONFIG: dict[str, Any] = {
    &amp;quot;version&amp;quot;: 1,
    &amp;quot;disable_existing_loggers&amp;quot;: False,
    &amp;quot;formatters&amp;quot;: {
        &amp;quot;file&amp;quot;: {
            &amp;quot;format&amp;quot;: (
                &amp;quot;%(levelname)s|%(name)s|%(asctime)s|%(message)s|%(filename)s|%(lineno)d&amp;quot;
            ),
            &amp;quot;datefmt&amp;quot;: &amp;quot;%Y-%m-%dT%H:%M:%S%z&amp;quot;,  # Muda o formato da data
        },
        &amp;quot;stdout&amp;quot;: {
            &amp;quot;format&amp;quot;: &amp;quot;OUT: [%(levelname)s] %(message)s&amp;quot;,
        },
        &amp;quot;stderr&amp;quot;: {
            &amp;quot;format&amp;quot;: (
                &amp;quot;\033[31mERR: [%(levelname)s] %(message)s - &amp;quot;
                &amp;quot;%(filename)s|%(lineno)d\033[0m&amp;quot;
            ),
        },
    },
    &amp;quot;handlers&amp;quot;: {
        &amp;quot;file&amp;quot;: {
            &amp;quot;class&amp;quot;: &amp;quot;logging.handlers.RotatingFileHandler&amp;quot;,
            &amp;quot;formatter&amp;quot;: &amp;quot;file&amp;quot;,  # LOGGING_CONFIG[&amp;quot;formatters&amp;quot;][&amp;quot;file&amp;quot;]
            &amp;quot;filename&amp;quot;: &amp;quot;log.log&amp;quot;,  # Arquivo principal do log
            &amp;quot;maxBytes&amp;quot;: 1024 * 1024 * 5,  # Tamanho máximo do log. 5MiB
            &amp;quot;backupCount&amp;quot;: 5,  # Máximo de backups do arquivo de log
            &amp;quot;encoding&amp;quot;: &amp;quot;utf-8&amp;quot;,  # Codificação de caracteres do arquivo
        },
        &amp;quot;stdout&amp;quot;: {
            &amp;quot;class&amp;quot;: &amp;quot;logging.StreamHandler&amp;quot;,
            &amp;quot;formatter&amp;quot;: &amp;quot;stdout&amp;quot;,  # LOGGING_CONFIG[&amp;quot;formatters&amp;quot;][&amp;quot;stream&amp;quot;]
            &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,  # 🚨 Isso vai gerar problema (tente entender)
            &amp;quot;stream&amp;quot;: &amp;quot;ext://sys.stdout&amp;quot;,  # ext é algo externo ao dict: sys.stdout
        },
        &amp;quot;stderr&amp;quot;: {
            &amp;quot;class&amp;quot;: &amp;quot;logging.StreamHandler&amp;quot;,
            &amp;quot;formatter&amp;quot;: &amp;quot;stderr&amp;quot;,  # LOGGING_CONFIG[&amp;quot;formatters&amp;quot;][&amp;quot;stream&amp;quot;]
            &amp;quot;level&amp;quot;: &amp;quot;WARNING&amp;quot;,
            &amp;quot;stream&amp;quot;: &amp;quot;ext://sys.stderr&amp;quot;,  # ext é algo externo ao dict: sys.stderr
        },
    },
    &amp;quot;root&amp;quot;: {
        &amp;quot;handlers&amp;quot;: [&amp;quot;file&amp;quot;, &amp;quot;stdout&amp;quot;, &amp;quot;stderr&amp;quot;],
    },
    &amp;quot;loggers&amp;quot;: {
        &amp;quot;meuapp&amp;quot;: {
            &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
        },
    },
}
dictConfig(LOGGING_CONFIG)

logger = logging.getLogger(&amp;quot;meuapp&amp;quot;)

logger.debug(&amp;quot;mensagem de log&amp;quot;)
logger.info(&amp;quot;mensagem de log&amp;quot;)
logger.warning(&amp;quot;mensagem de log&amp;quot;)
logger.error(&amp;quot;mensagem de log&amp;quot;)
logger.critical(&amp;quot;mensagem de log&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eu estava esperando uma saída diferente, mas perceba que os logs estão
&lt;strong&gt;duplicados&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;OUT: [DEBUG] mensagem de log
OUT: [INFO] mensagem de log
OUT: [WARNING] mensagem de log                  ### &amp;lt;- `warning` está no stdout
ERR: [WARNING] mensagem de log - main.py|60
OUT: [ERROR] mensagem de log                    ### &amp;lt;- `error` está no stdout
ERR: [ERROR] mensagem de log - main.py|61
OUT: [CRITICAL] mensagem de log                 ### &amp;lt;- `critical` está no stdout
ERR: [CRITICAL] mensagem de log - main.py|62
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lembra que a ideia era: &lt;code&gt;DEBUG&lt;/code&gt; e &lt;code&gt;INFO&lt;/code&gt; vão para o &lt;code&gt;stdout&lt;/code&gt;, e &lt;code&gt;WARNING&lt;/code&gt;,
&lt;code&gt;ERROR&lt;/code&gt;, &lt;code&gt;CRITICAL&lt;/code&gt; vão para o &lt;code&gt;stderr&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;O problema está nos níveis configurados. Quando colocamos o handler &lt;code&gt;stdout&lt;/code&gt; com
nível &lt;code&gt;DEBUG&lt;/code&gt;, isso significa:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;Aceite logs de &lt;code&gt;DEBUG&lt;/code&gt; para cima&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ou seja, ele aceita &lt;strong&gt;todos os níveis&lt;/strong&gt; (&lt;code&gt;DEBUG&lt;/code&gt;, &lt;code&gt;INFO&lt;/code&gt;, &lt;code&gt;WARNING&lt;/code&gt;, &lt;code&gt;ERROR&lt;/code&gt;,
&lt;code&gt;CRITICAL&lt;/code&gt;). Por isso estamos vendo os logs mais graves saindo tanto no &lt;code&gt;stdout&lt;/code&gt;
quanto no &lt;code&gt;stderr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pra resolver isso, vamos precisar de &lt;strong&gt;filters&lt;/strong&gt;, assim conseguimos controlar
&lt;strong&gt;com mais precisão&lt;/strong&gt; quais mensagens vão pra cada handler.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Claro, bora terminar essa parte com estilo e clareza. Aqui vai a continuação
ideal pra essa seção:&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;code&gt;filters&lt;/code&gt; - filtrando logs&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;filters&lt;/code&gt; são adicionados nos handlers ou loggers para controlar melhor quais
mensagens de log devem ser processadas por cada um. Eles funcionam como um
&lt;strong&gt;filtro extra&lt;/strong&gt;, além do nível mínimo (&lt;code&gt;level&lt;/code&gt;). De forma simplista, é só você
imaginar que o &lt;code&gt;level&lt;/code&gt; é um filter que veio pronto, porém é limitado.&lt;/p&gt;
&lt;p&gt;O nosso caso anterior é um exemplo clássico: Queríamos que o handler do &lt;code&gt;stdout&lt;/code&gt;
exibisse apenas &lt;code&gt;DEBUG&lt;/code&gt; e &lt;code&gt;INFO&lt;/code&gt;. Mas como ele estava com nível &lt;code&gt;DEBUG&lt;/code&gt;, ele
acabou aceitando &lt;strong&gt;todos os níveis acima também&lt;/strong&gt;, e por isso vimos &lt;code&gt;WARNING&lt;/code&gt;,
&lt;code&gt;ERROR&lt;/code&gt; e &lt;code&gt;CRITICAL&lt;/code&gt; aparecendo duas vezes (uma vez no &lt;code&gt;stdout&lt;/code&gt; e outra no
&lt;code&gt;stderr&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;A solução é aplicar um &lt;code&gt;filter&lt;/code&gt; que &lt;strong&gt;bloqueie tudo acima de &lt;code&gt;INFO&lt;/code&gt;&lt;/strong&gt; no handler
do &lt;code&gt;stdout&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Criando um &lt;code&gt;filter&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Vamos criar um filtro customizado simples. Estou criando outro módulo chamado
&lt;code&gt;filters.py&lt;/code&gt; no pacote em que estou (&lt;code&gt;logging_course&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Para criar o filter, vou criar uma classe que herda de &lt;code&gt;logging.Filter&lt;/code&gt;. O
método &lt;code&gt;filter&lt;/code&gt; retorna &lt;code&gt;True&lt;/code&gt; se aquele log é permitido ou &lt;code&gt;False&lt;/code&gt; se é para
descartar. Estranhamente, não é necessário herdar de &lt;code&gt;logging.Filter&lt;/code&gt;, mas é
mais semântico. Qualquer classe que tenha o método &lt;code&gt;filter&lt;/code&gt; vai funcionar. Além
disso, o filter também pode alterar o log e retornar um &lt;code&gt;LogRecord&lt;/code&gt;
completamente diferente.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; &lt;code&gt;LogRecord&lt;/code&gt; é o objeto de log com todas as informações daquele log
em específico. Toda vez que você emite um log, sua mensagem gera um novo
&lt;code&gt;LogRecord&lt;/code&gt; que vai ter a data, o módulo, o número da linha, o nível do log,
etc.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging

class MaxLevelFilter(logging.Filter):
    def __init__(self, max_level: str) -&amp;gt; None:
        # max_level é um argumento adicional que coloquei
        # max_level deve receber o nome do level, mas quero o número
        super().__init__()

        # self.max_level terá o número do level
        self.max_level = logging.getLevelNamesMapping().get(max_level.upper(), 50)

    def filter(self, record: logging.LogRecord) -&amp;gt; bool:
        # record é o LogRecord que eu disse antes
        # record.levelno é o número do level do log
        # se o número do level do log for menor ou igual ao max_level que
        # definimos no filter o log passa.
        # INFO 20 só aceitará logs INFO e DEBUG.
        return record.levelno &amp;lt;= self.max_level
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse filtro aceita apenas mensagens &lt;strong&gt;até o nível informado&lt;/strong&gt;. Se passarmos
&lt;code&gt;logging.INFO&lt;/code&gt;, ele permite &lt;code&gt;DEBUG&lt;/code&gt; e &lt;code&gt;INFO&lt;/code&gt;, e bloqueia o resto.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Atualizando o &lt;code&gt;dictConfig&lt;/code&gt; com &lt;code&gt;filters&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Agora vamos incluir o filtro na nossa configuração:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;LOGGING_CONFIG: dict[str, Any] = {
    &amp;quot;version&amp;quot;: 1, # mesma coisa
    &amp;quot;disable_existing_loggers&amp;quot;: False, # mesma coisa
    # `filters` é uma chave de primeiro nível,
    # assim como `formatters`
    &amp;quot;filters&amp;quot;: {
        # O nome do meu filtro dentro da dictConfig é a chave
        &amp;quot;only_debug_info&amp;quot;: {
            # Como é uma classe customizada, com argumento personalizado,
            # temos que colocar essa chave estranha com parênteses
            # O caminho completo do meu filter: logging_course.filters.MaxLevelFilter
            &amp;quot;()&amp;quot;: &amp;quot;logging_course.filters.MaxLevelFilter&amp;quot;,
            # Meu argumento adicional com valor INFO
            &amp;quot;max_level&amp;quot;: &amp;quot;INFO&amp;quot;
        }
    },
    &amp;quot;formatters&amp;quot;: {...}, # mesma coisa
    # resto tudo a mesma coisa
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E aplicar esse filtro no handler do &lt;code&gt;stdout&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;LOGGING_CONFIG: dict[str, Any] = {
    &amp;quot;version&amp;quot;: 1, # mesma coisa
    &amp;quot;disable_existing_loggers&amp;quot;: False, # mesma coisa
    &amp;quot;filters&amp;quot;: {...}, # vimos acima
    &amp;quot;formatters&amp;quot;: {...}, # mesma coisa
    &amp;quot;handlers&amp;quot;: {
        &amp;quot;stdout&amp;quot;: {
            &amp;quot;class&amp;quot;: &amp;quot;logging.StreamHandler&amp;quot;,
            &amp;quot;formatter&amp;quot;: &amp;quot;stdout&amp;quot;,
            &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
            &amp;quot;filters&amp;quot;: [&amp;quot;only_debug_info&amp;quot;], # Poderíamos ter vários filters aqui
            &amp;quot;stream&amp;quot;: &amp;quot;ext://sys.stdout&amp;quot;
        },
        # ... os outros handlers são a mesma coisa
    },
    # ...mesma coisa
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Resultado final com &lt;code&gt;filter&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Com isso, o comportamento será exatamente o que você queria:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;OUT: [DEBUG] mensagem de log
OUT: [INFO] mensagem de log
ERR: [WARNING] mensagem de log - main.py|67
ERR: [ERROR] mensagem de log - main.py|68
ERR: [CRITICAL] mensagem de log - main.py|69
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora sim, &lt;code&gt;stdout&lt;/code&gt; correto, &lt;code&gt;stderr&lt;/code&gt; correto.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Configuração via JSON (mas aqui a gente vai manter tudo em Python)&lt;/h2&gt;
&lt;p&gt;Apesar de ser comum externalizar a configuração do &lt;code&gt;logging&lt;/code&gt; usando arquivos
&lt;code&gt;.json&lt;/code&gt;, nesse artigo vou manter tudo em Python para facilitar a vida, mas em um
cenário real, é uma maravilha poder configurar tudo via &amp;quot;JSON&amp;quot;.&lt;/p&gt;
&lt;p&gt;Eu estou usando JSON como exemplo, mas você pode usar qualquer formato que
quiser. JSON, YAML, TOML são bem populares. Outra coisa importante é que o
próprio &lt;code&gt;logging&lt;/code&gt; também tem uma configuração chamada
&lt;code&gt;logging.config.fileConfig&lt;/code&gt; caso prefira, está tudo na
&lt;a href=&quot;https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig&quot;&gt;documentação oficial do Python&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Exemplo de configuração JSON com dictConfig&lt;/h2&gt;
&lt;p&gt;Vamos criar um arquivo chamado &lt;code&gt;logging.json&lt;/code&gt; na raiz do projeto. Estou criando
outra configuração nesse exemplo para encurtar um pouco o texto (essa não é a
melhor configuração, como vimos antes, mas vai servir). Também vou jogar o único
handler que criei dentro do &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;version&amp;quot;: 1,
  &amp;quot;disable_existing_loggers&amp;quot;: false,
  &amp;quot;formatters&amp;quot;: {
    &amp;quot;default&amp;quot;: {
      &amp;quot;format&amp;quot;: &amp;quot;[%(levelname)s] %(message)s&amp;quot;
    }
  },
  &amp;quot;handlers&amp;quot;: {
    &amp;quot;console&amp;quot;: {
      &amp;quot;class&amp;quot;: &amp;quot;logging.StreamHandler&amp;quot;,
      &amp;quot;formatter&amp;quot;: &amp;quot;default&amp;quot;,
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
      &amp;quot;stream&amp;quot;: &amp;quot;ext://sys.stdout&amp;quot;
    }
  },
  &amp;quot;root&amp;quot;: {
    &amp;quot;handlers&amp;quot;: [&amp;quot;console&amp;quot;],
    &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse &lt;code&gt;DEBUG&lt;/code&gt; aí no root chega a dar arrepios.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Carregando a configuração JSON com Python:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import json
import logging.config

with open(&amp;quot;logging.json&amp;quot;, &amp;quot;r&amp;quot;) as f:
    config = json.load(f)

logging.config.dictConfig(config)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Observações:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;É meio óbvio, mas JSON não é Python (wow, descobriu agora?). Mas sério, algumas
coisas não funcionam exatamente como no Python. Um exemplo: eu estava usando uma
cor com &lt;code&gt;\033[31m&lt;/code&gt; e &lt;code&gt;\033[0m&lt;/code&gt; (vermelho e reset). Na hora que o JSON é
carregado, isso tudo é string normal, ou seja, vai aparecer como texto no seu
&lt;code&gt;stdout&lt;/code&gt; ou &lt;code&gt;stderr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Tem como corrigir? Sim, já que você tocou no assunto, vamos criar nosso próprio
formatter.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Formatter: que tal criar seu próprio formatter?&lt;/h2&gt;
&lt;p&gt;Vamos lá, vou criar um formatter só como exemplo para você entender como
funciona. Mas para deixar o nosso &lt;code&gt;console&lt;/code&gt; mais bonito, vou usar o &lt;code&gt;rich&lt;/code&gt; no
final das contas.&lt;/p&gt;
&lt;p&gt;Assim como fiz com &lt;code&gt;filters&lt;/code&gt;, vou criar outro arquivo chamado &lt;code&gt;formatters.py&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging
from typing import ClassVar


class ColorFormatter(logging.Formatter):
    COLORS: ClassVar[dict[str, str]] = {
        &amp;quot;DEBUG&amp;quot;: &amp;quot;\033[36m&amp;quot;,  # Ciano
        &amp;quot;INFO&amp;quot;: &amp;quot;\033[32m&amp;quot;,  # Verde
        &amp;quot;WARNING&amp;quot;: &amp;quot;\033[33m&amp;quot;,  # Amarelo
        &amp;quot;ERROR&amp;quot;: &amp;quot;\033[31m&amp;quot;,  # Vermelho
        &amp;quot;CRITICAL&amp;quot;: &amp;quot;\033[41m&amp;quot;,  # Fundo vermelho
    }

    RESET = &amp;quot;\033[0m&amp;quot;

    def format(self, record: logging.LogRecord) -&amp;gt; str:
        color = self.COLORS.get(record.levelname, &amp;quot;&amp;quot;)
        message = super().format(record)
        return f&amp;quot;{color}{message}{self.RESET}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;Como usar no JSON com &lt;code&gt;dictConfig&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Agora vou voltar a usar a configuração que estou usando enquanto escrevo o
artigo só pra te mostrar o tamanho que está ficando meu JSON e também para você
ver como ficaria num cenário real.&lt;/p&gt;
&lt;p&gt;Esse é o meu JSON nesse momento:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;version&amp;quot;: 1,
  &amp;quot;disable_existing_loggers&amp;quot;: false,
  &amp;quot;formatters&amp;quot;: {
    &amp;quot;file&amp;quot;: {
      &amp;quot;format&amp;quot;: &amp;quot;%(levelname)s|%(name)s|%(asctime)s|%(message)s|%(filename)s|%(lineno)d&amp;quot;,
      &amp;quot;datefmt&amp;quot;: &amp;quot;%Y-%m-%dT%H:%M:%S%z&amp;quot;
    },
    &amp;quot;stdout&amp;quot;: {
      &amp;quot;format&amp;quot;: &amp;quot;OUT: [%(levelname)s] %(message)s&amp;quot;
    },
    &amp;quot;stderr&amp;quot;: {
      &amp;quot;format&amp;quot;: &amp;quot;ERR: [%(levelname)s] %(message)s - %(filename)s|%(lineno)d&amp;quot;
    },
    &amp;quot;color&amp;quot;: {
      &amp;quot;()&amp;quot;: &amp;quot;logging_course.formatters.ColorFormatter&amp;quot;,
      &amp;quot;format&amp;quot;: &amp;quot;[%(levelname)s] %(message)s&amp;quot;
    }
  },
  &amp;quot;filters&amp;quot;: {
    &amp;quot;only_debug_info&amp;quot;: {
      &amp;quot;()&amp;quot;: &amp;quot;logging_course.filters.MaxLevelFilter&amp;quot;,
      &amp;quot;max_level&amp;quot;: &amp;quot;INFO&amp;quot;
    }
  },
  &amp;quot;handlers&amp;quot;: {
    &amp;quot;file&amp;quot;: {
      &amp;quot;class&amp;quot;: &amp;quot;logging.handlers.RotatingFileHandler&amp;quot;,
      &amp;quot;formatter&amp;quot;: &amp;quot;file&amp;quot;,
      &amp;quot;filename&amp;quot;: &amp;quot;log.log&amp;quot;,
      &amp;quot;maxBytes&amp;quot;: 5242880,
      &amp;quot;backupCount&amp;quot;: 5,
      &amp;quot;encoding&amp;quot;: &amp;quot;utf-8&amp;quot;
    },
    &amp;quot;stdout&amp;quot;: {
      &amp;quot;class&amp;quot;: &amp;quot;logging.StreamHandler&amp;quot;,
      &amp;quot;formatter&amp;quot;: &amp;quot;color&amp;quot;,
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
      &amp;quot;filters&amp;quot;: [&amp;quot;only_debug_info&amp;quot;],
      &amp;quot;stream&amp;quot;: &amp;quot;ext://sys.stdout&amp;quot;
    },
    &amp;quot;stderr&amp;quot;: {
      &amp;quot;class&amp;quot;: &amp;quot;logging.StreamHandler&amp;quot;,
      &amp;quot;formatter&amp;quot;: &amp;quot;color&amp;quot;,
      &amp;quot;level&amp;quot;: &amp;quot;WARNING&amp;quot;,
      &amp;quot;stream&amp;quot;: &amp;quot;ext://sys.stderr&amp;quot;
    }
  },
  &amp;quot;root&amp;quot;: {
    &amp;quot;handlers&amp;quot;: [&amp;quot;file&amp;quot;, &amp;quot;stdout&amp;quot;, &amp;quot;stderr&amp;quot;]
  },
  &amp;quot;loggers&amp;quot;: {
    &amp;quot;meuapp&amp;quot;: {
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;Se você pegou o jeito da coisa, é a mesma ideia que vimos em &lt;code&gt;filters&lt;/code&gt; (na
verdade é a mesma ideia para quase tudo). Sempre que eu tenho uma classe com
interface diferente das classes padrão do &lt;code&gt;dictConfig&lt;/code&gt;, preciso informar o
caminho completo dessa classe no dicionário (ou onde quer quer estiver sua
configuração, JSON no nosso caso). Além disso, tenho que usar &lt;code&gt;()&lt;/code&gt; na chave,
assim as chaves que vierem a seguir no JSON serão passadas para sua classe para
configuração.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resultado:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;[DEBUG] mensagem de log       &amp;lt;- em ciano
[INFO] mensagem de log        &amp;lt;- em verde
[WARNING] mensagem de log     &amp;lt;- em amarelo
[ERROR] mensagem de log       &amp;lt;- em vermelho
[CRITICAL] mensagem de log    &amp;lt;- fundo vermelho
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se quiser deixar isso ainda mais modular depois, pode mover os códigos ANSI pra
uma constante global ou até gerar dinamicamente por tema. Mas isso é suficiente
por agora, porque nem vamos usar isso, usaremos o &lt;code&gt;rich&lt;/code&gt;. Como falei isso umas
30 vezes no texto, vamos ver como fazer isso.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;RichHandler - o Logging Handler do rich&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;rich&lt;/code&gt; tem sido extensivamente usado com o Python para aplicações de terminal
mais elegantes, coloridas e bonitas. E não é por menos: tem praticamente tudo o
que você precisaria em termos de componentes visuais para terminal,
&lt;a href=&quot;https://rich.readthedocs.io/en/stable/logging.html&quot;&gt;incluindo um handler para logging&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Para usar, basta fazer o seguinte na no seu arquivo de configuração JSON ou no
dicionário mesmo.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;version&amp;quot;: 1,
  &amp;quot;disable_existing_loggers&amp;quot;: false,
  &amp;quot;formatters&amp;quot;: {
    &amp;quot;stdout&amp;quot;: {
      &amp;quot;datefmt&amp;quot;: &amp;quot;[%X]&amp;quot;
    }
  },
  &amp;quot;handlers&amp;quot;: {
    &amp;quot;stdout&amp;quot;: {
      &amp;quot;()&amp;quot;: &amp;quot;rich.logging.RichHandler&amp;quot;,
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
      &amp;quot;omit_repeated_times&amp;quot;: false,
      &amp;quot;formatter&amp;quot;: &amp;quot;stdout&amp;quot;
    }
  },
  &amp;quot;root&amp;quot;: {
    &amp;quot;handlers&amp;quot;: [&amp;quot;stdout&amp;quot;]
  },
  &amp;quot;loggers&amp;quot;: {
    &amp;quot;meuapp&amp;quot;: {
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Só de fazer isso, meu log ficou como mostro na imagem a seguir. Por padrão o
&lt;code&gt;RichHandler&lt;/code&gt; emit os logs para &lt;code&gt;stdout&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/rich_log_exemple.webp&quot; alt=&quot;Exemplo de log do rich, uma tabela com os logs coloridos por nível de severidade.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Se quiser, pode manter o &lt;code&gt;rich&lt;/code&gt; para o seu &lt;code&gt;debug&lt;/code&gt;. E daqui em diante, toda vez
que pensar em ver o valor de uma variável ou algo semelhante, use o log. Deixa o
&lt;code&gt;print&lt;/code&gt; para coisas que realmente precisam de print, como exibir algo par ao
usuário mesmo.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;JSON log formatter: vamos salvar logs em JSON Lines?&lt;/h2&gt;
&lt;p&gt;Só pra constar: você também pode criar seu próprio formatter e mudar o formato
de saída para o que achar melhor. Vamos fazer isso em JSON porque é um dos
formatos mais usados, mas você decide qual usar. A ideia é sempre a mesma.&lt;/p&gt;
&lt;p&gt;Na real, em vez do JSON tradicional, vamos usar uma variação chamada
&lt;a href=&quot;https://jsonlines.org/&quot;&gt;JSON Lines&lt;/a&gt; (ou NDJSON, Newline Delimited JSON). Nesse
formato, cada linha é um objeto JSON independente.&lt;/p&gt;
&lt;p&gt;O motivo é simples: eficiência. Quando a gente cria um JSON comum, você precisa
guardar tudo num único objeto (ou array). A cada novo log, teria que carregar
esse JSON, adicionar o novo item, e depois salvar tudo de novo.&lt;/p&gt;
&lt;p&gt;Com JSONL, não. Podemos simplesmente dar um &lt;code&gt;append&lt;/code&gt; (&lt;code&gt;a&lt;/code&gt;) no final do arquivo,
sem precisar mexer em nada que já estava lá.&lt;/p&gt;
&lt;p&gt;E o melhor: fazer &lt;code&gt;parse&lt;/code&gt; disso é fácil. Basta ler o arquivo linha por linha e
interpretar cada uma como um JSON separado.&lt;/p&gt;
&lt;p&gt;Até porque, convenhamos: não é todo dia que você vai fazer parse de logs. Mas
gravar logs... isso sim, é todo dia.&lt;/p&gt;
&lt;p&gt;Também vamos melhorar o nosso &lt;code&gt;setup&lt;/code&gt; de leve até chegarmos a uma configuração
final pronta.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Melhorando nosso setup geral do logging&lt;/h2&gt;
&lt;p&gt;Até o momento, temos trabalhado com uma configuração toda solta. O ideal é
movermos as partes da nossa configuração para uma função. Então vamos fazer o
seguinte (veja nos comentários de código):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Criei uma arquivo separado apenas para configurações
# src/logging_course/config_logger.py
import json
import logging
from logging.config import dictConfig
from pathlib import Path
from typing import Any

# Caminhos em geral
ROOT_DIR = Path(&amp;quot;.&amp;quot;)  # Raiz do projeto
LOGS_DIR = ROOT_DIR / &amp;quot;logs&amp;quot;  # Pasta para logs (./logs)
LOG_CONFIG_PATH = ROOT_DIR / &amp;quot;logging.json&amp;quot;  # Arquivo de configuração


def setup(logger_name: str) -&amp;gt; logging.Logger:
    &amp;quot;&amp;quot;&amp;quot;Configura o logger principal da aplicação.&amp;quot;&amp;quot;&amp;quot;

    # Se a pasta LOGS_DIR não existir, teremos um erro na aplicação,
    # então verificamos e, se não existir, criamos.
    if not LOGS_DIR.is_dir():
        LOGS_DIR.mkdir(parents=True, exist_ok=True)

    with LOG_CONFIG_PATH.open(encoding=&amp;quot;utf8&amp;quot;) as file:
        logging_config = json.load(file)

    dictConfig(logging_config)

    # Já estou retornando um logger, mas nem precisaria disso
    return logging.getLogger(logger_name)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A única diferença do que fizemos anteriormente é que agora você tem uma função
encapsulando tudo. Além disso, quero jogar os arquivos de log na pasta &lt;code&gt;logs&lt;/code&gt;
para evitar ficar poluindo a raiz do meu projeto. Se essa pasta não existir,
isso vai parar a aplicação logo quando &lt;code&gt;logging&lt;/code&gt; for tentar salvar o arquivo de
log. Se quiser tirar esse trecho de código da nossa função, não tem problema,
basta criar a pasta &lt;code&gt;logs&lt;/code&gt; manualmente.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Formatter personalizado: JSONLoggerFormatter&lt;/h2&gt;
&lt;p&gt;Existem libs prontas para isso, mas como estamos aqui para aprender, vamos criar
nosso próprio logger.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# src/logging_course/formatters.py
import json
import logging
from datetime import UTC, datetime
from typing import Any, ClassVar, override


# Essas são as chaves disponíveis no LogRecord
# Podemos incluir qualquer uma dessas chaves no nosso log.
LOG_RECORD_KEYS = [
    &amp;quot;name&amp;quot;,
    &amp;quot;msg&amp;quot;,
    &amp;quot;args&amp;quot;,
    &amp;quot;levelname&amp;quot;,
    &amp;quot;levelno&amp;quot;,
    &amp;quot;pathname&amp;quot;,
    &amp;quot;filename&amp;quot;,
    &amp;quot;module&amp;quot;,
    &amp;quot;exc_info&amp;quot;,
    &amp;quot;exc_text&amp;quot;,
    &amp;quot;stack_info&amp;quot;,
    &amp;quot;lineno&amp;quot;,
    &amp;quot;funcName&amp;quot;,
    &amp;quot;created&amp;quot;,
    &amp;quot;msecs&amp;quot;,
    &amp;quot;relativeCreated&amp;quot;,
    &amp;quot;thread&amp;quot;,
    &amp;quot;threadName&amp;quot;,
    &amp;quot;processName&amp;quot;,
    &amp;quot;process&amp;quot;,
    &amp;quot;taskName&amp;quot;,
]

# Só fiz isso para separar o que é padrão do que é meu mesmo
# message não existe em no LogRecord
LOG_RECORDS_EXTENDED_KEYS = [*LOG_RECORD_KEYS, &amp;quot;message&amp;quot;]


# Nosso logger herda de `logging.Formatter`. Vou configurar tudo dessa super
# classe por dentro do meu __init__, ou seja, não vou receber configurações
# externas.
# A única coisa que adicionei aqui foi `include_keys`, que vamos poder usar para
# ativar ou desativar chaves do nosso log.
class JSONLogFormatter(logging.Formatter):
    def __init__(self, include_keys: list[str] | None = None) -&amp;gt; None:
        super().__init__(
            fmt=None,
            datefmt=&amp;quot;%Y-%m-%dT%H:%M:%S%z&amp;quot;,
            style=&amp;quot;%&amp;quot;,
            validate=False,
        )
        # Se você não enviar `include_keys`, todas as chaves serão adicionadas.
        self.include_keys = (
            include_keys if include_keys is not None else LOG_RECORDS_EXTENDED_KEYS
        )

    @override
    def format(self, record: logging.LogRecord) -&amp;gt; str:
        # Crio um dicionário onde obtenho todas as chaves de &amp;quot;include_keys&amp;quot;
        # de dentro do LogRecord (`record`). Inicialmente, os valores são
        # iguais, mas alguns desses valores não podem ser serializados para JSON.
        dict_record: dict[str, Any] = {
            key: getattr(record, key)
            for key in self.include_keys
            if getattr(record, key, None) is not None
            and key in LOG_RECORDS_EXTENDED_KEYS
        }

        if &amp;quot;created&amp;quot; in dict_record:
            # Sobrescrevi o método `formatTime` para retornar um datetime
            # ao invés de `struct_time` que é o padrão. Assim consigo trabalhar
            # com timezone.
            dict_record[&amp;quot;created&amp;quot;] = self.formatTime(record)

        if &amp;quot;message&amp;quot; in self.include_keys:
            dict_record[&amp;quot;message&amp;quot;] = record.getMessage()

        if &amp;quot;exc_info&amp;quot; in dict_record and record.exc_info:
            # `exc_info` traz informações sobre exceções. Precisamos formatar
            # esse valor para uma string. Por sorte isso existe em `Formatter`.
            dict_record[&amp;quot;exc_info&amp;quot;] = self.formatException(record.exc_info)

        if &amp;quot;stack_info&amp;quot; in dict_record and record.stack_info:
            # Aqui também precisamos formatar o valor do stack da exceção para str.
            dict_record[&amp;quot;stack_info&amp;quot;] = self.formatStack(record.stack_info)

        # Caso utilize extras ao emitir o log
        # Ex.: logger.warning(&amp;quot;Mensagem&amp;quot;, extra={&amp;quot;contexto&amp;quot;: &amp;quot;qualquer coisa&amp;quot;})
        # A chave &amp;quot;contexto&amp;quot; será adicionada ao log também
        for key, val in vars(record).items():
            if key in LOG_RECORDS_EXTENDED_KEYS:
                # Essas chaves nós tratamos antes
                continue

            # Adicionamos a chave extra ao log
            dict_record[key] = val

        # E pronto, salvamos o log como JSON.
        # Aqui só estamos retornando, quem salva mesmo é RotatingFileHandler.
        return json.dumps(dict_record, default=str)

    @override
    def formatTime(self, record: logging.LogRecord, datefmt: str | None = None) -&amp;gt; str:
        # Como disse nos comentários acima, só estou trocando o timestamp
        # para datetime para ter timezone. Configure como preferir.
        date = datetime.fromtimestamp(record.created, tz=UTC)

        if datefmt:
            return date.strftime(datefmt)

        return date.isoformat()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Não deixe de ler os comentários que deixei nos códigos. Eu estou deixando de
escrever texto puro aqui para comentar nas linhas onde as coisas realmente
acontece para você entender melhor.&lt;/p&gt;
&lt;p&gt;Agora só falta configurarmos o nosso &lt;code&gt;logging.json&lt;/code&gt; ou o seu dicionário do
&lt;code&gt;dictConfig&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Configuração atual do nosso &lt;code&gt;logging.json&lt;/code&gt; do &lt;code&gt;dictConfig&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Agora vamos usar dois handlers que aprendemos antes, o &lt;code&gt;RichHandler&lt;/code&gt; para o
nosso &lt;code&gt;console&lt;/code&gt; e o &lt;code&gt;RotatingFileHandler&lt;/code&gt; para o &lt;code&gt;JSONLogFormatter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Você sabe fazer isso, vamos lá:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;version&amp;quot;: 1,
  &amp;quot;disable_existing_loggers&amp;quot;: false,
  &amp;quot;formatters&amp;quot;: {
    &amp;quot;console&amp;quot;: {
      &amp;quot;format&amp;quot;: &amp;quot;%(message)s&amp;quot;,
      &amp;quot;datefmt&amp;quot;: &amp;quot;%H:%M:%S&amp;quot;
    },
    &amp;quot;json&amp;quot;: {
      &amp;quot;()&amp;quot;: &amp;quot;logging_course.formatters.JSONLogFormatter&amp;quot;,
      &amp;quot;include_keys&amp;quot;: [
        &amp;quot;created&amp;quot;,
        &amp;quot;message&amp;quot;,
        &amp;quot;levelname&amp;quot;,
        &amp;quot;name&amp;quot;,
        &amp;quot;filename&amp;quot;,
        &amp;quot;module&amp;quot;,
        &amp;quot;exc_info&amp;quot;,
        &amp;quot;lineno&amp;quot;,
        &amp;quot;threadName&amp;quot;,
        &amp;quot;processName&amp;quot;,
        &amp;quot;taskName&amp;quot;,
        &amp;quot;args&amp;quot;
      ]
    }
  },
  &amp;quot;handlers&amp;quot;: {
    &amp;quot;console&amp;quot;: {
      &amp;quot;class&amp;quot;: &amp;quot;rich.logging.RichHandler&amp;quot;,
      &amp;quot;formatter&amp;quot;: &amp;quot;console&amp;quot;,
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
      &amp;quot;rich_tracebacks&amp;quot;: false,
      &amp;quot;tracebacks_show_locals&amp;quot;: false,
      &amp;quot;show_time&amp;quot;: true,
      &amp;quot;show_level&amp;quot;: true,
      &amp;quot;omit_repeated_times&amp;quot;: false,
      &amp;quot;markup&amp;quot;: true,
      &amp;quot;enable_link_path&amp;quot;: true,
      &amp;quot;show_path&amp;quot;: true
    },
    &amp;quot;file&amp;quot;: {
      &amp;quot;class&amp;quot;: &amp;quot;logging.handlers.RotatingFileHandler&amp;quot;,
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
      &amp;quot;formatter&amp;quot;: &amp;quot;json&amp;quot;,
      &amp;quot;filename&amp;quot;: &amp;quot;logs/log.jsonl&amp;quot;,
      &amp;quot;maxBytes&amp;quot;: 5242880,
      &amp;quot;backupCount&amp;quot;: 5,
      &amp;quot;encoding&amp;quot;: &amp;quot;utf-8&amp;quot;
    }
  },
  &amp;quot;root&amp;quot;: {
    &amp;quot;handlers&amp;quot;: [&amp;quot;file&amp;quot;, &amp;quot;console&amp;quot;]
  },
  &amp;quot;loggers&amp;quot;: {
    &amp;quot;meuapp&amp;quot;: {
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pronto, agora temos dois logs prontos para uso em uma tacada só. Sai no seu
terminal e vai para o arquivo &lt;code&gt;.jsonl&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Bônus: um parser simples para JSONL&lt;/h3&gt;
&lt;p&gt;Aqui está como fazer o parse do arquivo &lt;code&gt;.jsonl&lt;/code&gt; como exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def parse_jsonl(path: Path) -&amp;gt; list[dict[str, Any]]:
    with path.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf8&amp;quot;) as file:
        lines = file.readlines()

    logs: list[dict[str, Any]] = []

    if not lines:
        return logs

    for line in lines:
        logs.append(json.loads(line))

    return logs


if __name__ == &amp;quot;__main__&amp;quot;:
    from rich import print as p

    log_file = LOGS_DIR / &amp;quot;log.jsonl&amp;quot;

    for log in parse_jsonl(log_file):
        # This is just an example
        p(log)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Estamos acabando. Vamos ver só mais uma coisa que eu acho que você vai achar
interessante.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;QueueHandler e QueueListener: log sem travar sua aplicação&lt;/h2&gt;
&lt;p&gt;Às vezes, o seu logger pode virar um gargalo na aplicação. Isso mesmo. A função
que deveria te ajudar a debugar começa a &lt;strong&gt;travar tudo&lt;/strong&gt;. Por quê?&lt;/p&gt;
&lt;p&gt;Imagine isso:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;logger.info(&amp;quot;Processando pedido: %s&amp;quot;, pedido)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Parece inocente, mas se esse log for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Formatado com um &lt;code&gt;formatter&lt;/code&gt; mais pesado (ex: JSON complexo)&lt;/li&gt;
&lt;li&gt;Escrito em disco (arquivo grande, I/O lento)&lt;/li&gt;
&lt;li&gt;Impresso no terminal com renderização colorida (&lt;code&gt;RichHandler&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Enviado para múltiplos &lt;code&gt;handlers&lt;/code&gt; ao mesmo tempo&lt;/li&gt;
&lt;li&gt;Enviando por e-mail, HTTP, Socket, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;... então essa linha pode virar &lt;strong&gt;um mini-freio&lt;/strong&gt; toda vez que roda. E se sua
app roda centenas ou milhares de logs por segundo (ex: scraping, filas, web,
workers), esse tempo se acumula.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;A solução: enfileirar o log e deixar outra thread cuidar deles&lt;/h3&gt;
&lt;p&gt;É exatamente isso que &lt;code&gt;QueueHandler&lt;/code&gt; e &lt;code&gt;QueueListener&lt;/code&gt; fazem.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Você passa a &lt;strong&gt;responsabilidade de processar e gravar o log para outra
thread&lt;/strong&gt;, usando uma &lt;code&gt;Queue&lt;/code&gt; (fila).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Assim, sua aplicação:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Só coloca o log na fila (&lt;code&gt;QueueHandler&lt;/code&gt; faz isso)&lt;/li&gt;
&lt;li&gt;E continua rodando normal, sem esperar&lt;/li&gt;
&lt;li&gt;Enquanto isso, outra thread (&lt;code&gt;QueueListener&lt;/code&gt;) fica escutando essa fila e
processa os logs em segundo plano.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Por que isso funciona tão bem?&lt;/h3&gt;
&lt;p&gt;Porque &lt;code&gt;queue.Queue()&lt;/code&gt; em Python é &lt;strong&gt;thread-safe e super leve&lt;/strong&gt;. Você consegue
colocar o log na fila em microssegundos. A thread que escuta a fila
(&lt;code&gt;QueueListener&lt;/code&gt;) processa os logs no tempo que precisar, totalmente separada da
thread principal da sua aplicação.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Exemplo prático com nosso JSON de &lt;code&gt;dictConfig&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Para implementar isso, vamos adicionar uma nova seção ou entrada no seu
&lt;code&gt;dictConfig&lt;/code&gt;. Ao configurar um &lt;code&gt;QueueHandler&lt;/code&gt;, você também informará quais são
os &amp;quot;handlers reais&amp;quot; que o &lt;code&gt;QueueListener&lt;/code&gt; (que funcionará em uma thread
separada) deve usar para processar as mensagens de log retiradas da fila.&lt;/p&gt;
&lt;p&gt;Basicamente:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Definimos um &lt;code&gt;QueueHandler&lt;/code&gt; que será o ponto de entrada dos logs na fila.&lt;/li&gt;
&lt;li&gt;Dentro da configuração desse &lt;code&gt;QueueHandler&lt;/code&gt; no &lt;code&gt;dictConfig&lt;/code&gt; (ou em uma seção
&lt;code&gt;queue_listeners&lt;/code&gt;), você indica os &lt;code&gt;handlers&lt;/code&gt; para onde o &lt;code&gt;QueueListener&lt;/code&gt;
deve enviar os logs.&lt;/li&gt;
&lt;li&gt;Então, qualquer &lt;code&gt;logger&lt;/code&gt; que você queira que se beneficie do processamento
assíncrono, você o associa a este &lt;code&gt;QueueHandler&lt;/code&gt;. Por exemplo, você pode
mover os handlers que estavam no &lt;code&gt;root&lt;/code&gt; logger (ou em qualquer outro logger
específico) para serem manipulados pelo seu &lt;code&gt;QueueHandler&lt;/code&gt;. No meu caso, vou
usar no &lt;code&gt;root&lt;/code&gt; logger.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;version&amp;quot;: 1,
  &amp;quot;disable_existing_loggers&amp;quot;: false,
  &amp;quot;formatters&amp;quot;: {
    &amp;quot;console&amp;quot;: {
      &amp;quot;format&amp;quot;: &amp;quot;%(message)s&amp;quot;,
      &amp;quot;datefmt&amp;quot;: &amp;quot;%H:%M:%S&amp;quot;
    },
    &amp;quot;json&amp;quot;: {
      &amp;quot;()&amp;quot;: &amp;quot;logging_course.formatters.JSONLogFormatter&amp;quot;,
      &amp;quot;include_keys&amp;quot;: [
        &amp;quot;created&amp;quot;,
        &amp;quot;message&amp;quot;,
        &amp;quot;levelname&amp;quot;,
        &amp;quot;name&amp;quot;,
        &amp;quot;filename&amp;quot;,
        &amp;quot;module&amp;quot;,
        &amp;quot;exc_info&amp;quot;,
        &amp;quot;lineno&amp;quot;,
        &amp;quot;threadName&amp;quot;,
        &amp;quot;processName&amp;quot;,
        &amp;quot;taskName&amp;quot;,
        &amp;quot;args&amp;quot;
      ]
    }
  },
  &amp;quot;handlers&amp;quot;: {
    &amp;quot;console&amp;quot;: {
      &amp;quot;class&amp;quot;: &amp;quot;rich.logging.RichHandler&amp;quot;,
      &amp;quot;formatter&amp;quot;: &amp;quot;console&amp;quot;,
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
      &amp;quot;rich_tracebacks&amp;quot;: false,
      &amp;quot;tracebacks_show_locals&amp;quot;: false,
      &amp;quot;show_time&amp;quot;: true,
      &amp;quot;show_level&amp;quot;: true,
      &amp;quot;omit_repeated_times&amp;quot;: false,
      &amp;quot;markup&amp;quot;: true,
      &amp;quot;enable_link_path&amp;quot;: true,
      &amp;quot;show_path&amp;quot;: true
    },
    &amp;quot;file&amp;quot;: {
      &amp;quot;class&amp;quot;: &amp;quot;logging.handlers.RotatingFileHandler&amp;quot;,
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
      &amp;quot;formatter&amp;quot;: &amp;quot;json&amp;quot;,
      &amp;quot;filename&amp;quot;: &amp;quot;logs/log.jsonl&amp;quot;,
      &amp;quot;maxBytes&amp;quot;: 5242880,
      &amp;quot;backupCount&amp;quot;: 5,
      &amp;quot;encoding&amp;quot;: &amp;quot;utf-8&amp;quot;
    },
    &amp;quot;queue&amp;quot;: {
      &amp;quot;class&amp;quot;: &amp;quot;logging.handlers.QueueHandler&amp;quot;,
      &amp;quot;handlers&amp;quot;: [&amp;quot;file&amp;quot;, &amp;quot;console&amp;quot;],
      &amp;quot;respect_handler_level&amp;quot;: true
    }
  },
  &amp;quot;root&amp;quot;: {
    &amp;quot;handlers&amp;quot;: [&amp;quot;queue&amp;quot;]
  },
  &amp;quot;loggers&amp;quot;: {
    &amp;quot;meuapp&amp;quot;: {
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Certo, a configuração está pronta, mas se você executou o código, provavelmente
percebeu que os logs pararam de funcionar. Isso acontece porque, embora tenhamos
configurado o &lt;code&gt;QueueHandler&lt;/code&gt; para &lt;em&gt;colocar&lt;/em&gt; os logs na fila, &lt;strong&gt;ninguém está
lendo essa fila ainda!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Lembra que o nosso objetivo é ter o &lt;code&gt;QueueHandler&lt;/code&gt; para &lt;strong&gt;enfileirar&lt;/strong&gt; os logs
(tirando o peso da sua aplicação) e, em paralelo, o &lt;code&gt;QueueListener&lt;/code&gt; para
&lt;strong&gt;processar&lt;/strong&gt; esses logs em outra thread. A configuração do &lt;code&gt;dictConfig&lt;/code&gt; que
acabamos de ver define o &lt;code&gt;QueueHandler&lt;/code&gt; e informa para onde o &lt;code&gt;QueueListener&lt;/code&gt;
deve enviar os logs.&lt;/p&gt;
&lt;p&gt;Agora, precisamos explicitamente iniciar o &lt;code&gt;QueueListener&lt;/code&gt; para que ele comece a
consumir as mensagens da fila e as encaminhe para os handlers &lt;code&gt;&amp;quot;file&amp;quot;&lt;/code&gt; e
&lt;code&gt;&amp;quot;console&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Para que o sistema de log baseado em filas realmente funcione, precisamos dar a
partida no &lt;code&gt;QueueListener&lt;/code&gt;. Lembre-se, o &lt;code&gt;QueueHandler&lt;/code&gt; apenas coloca os logs na
fila; o &lt;code&gt;QueueListener&lt;/code&gt; é quem lê e processa.&lt;/p&gt;
&lt;p&gt;Precisamos fazer isso programaticamente, após aplicar a configuração do
&lt;code&gt;dictConfig&lt;/code&gt;. Geralmente, este código é adicionado logo após a chamada para
&lt;code&gt;logging.config.dictConfig()&lt;/code&gt; (ou &lt;code&gt;fileConfig()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemplo para iniciar o &lt;code&gt;QueueListener&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging
import logging.config
import atexit

# A função getHandlerByName() está disponível a partir do Python 3.12.
queue_handler = logging.getHandlerByName(&amp;quot;queue&amp;quot;)

if queue_handler is not None:
    # O QueueHandler, quando configurado via dictConfig com &amp;quot;handlers&amp;quot;,
    # automaticamente cria e gerencia uma instância de QueueListener.
    # Nós simplesmente precisamos iniciar essa instância.
    queue_handler.listener.start()

    # É crucial registrar uma função para parar o listener quando o programa
    # for encerrado. Isso garante um desligamento limpo da thread do listener.
    atexit.register(queue_handler.listener.stop)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Meu código final e completo&lt;/h2&gt;
&lt;p&gt;Fizemos muitas coisas ao longo desse artigo, mas temos que finalizar em algum
lugar, concorda? Então vou te passar como ficou a minha configuração final com
alguns ajustes que fiz. Sinta-se à vontade para usar e modificar como quiser.&lt;/p&gt;
&lt;p&gt;Para amarrar tudo, aqui está a versão final da sua função &lt;code&gt;setup&lt;/code&gt; no módulo
&lt;code&gt;config_logger&lt;/code&gt; (na verdade, estou mandando o módulo inteiro, então tem mais do
que só &lt;code&gt;setup&lt;/code&gt; aí). Mudei ela levemente para incluir a lógica de inicialização e
desligamento do &lt;code&gt;QueueListener&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Note que esta função &lt;code&gt;setup&lt;/code&gt; agora é apenas para &lt;strong&gt;configurar&lt;/strong&gt; o logger, sem
criar ou retornar instâncias de logger. Você precisará usar
&lt;code&gt;logging.getLogger(&amp;#39;nome&amp;#39;)&lt;/code&gt; em outras partes da sua aplicação para obter e usar
os loggers configurados.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import atexit
import json
import logging
from logging.config import dictConfig
from pathlib import Path
from typing import Any

# Caminhos em geral
ROOT_DIR = Path(&amp;quot;.&amp;quot;)  # Raiz do projeto
LOGS_DIR = ROOT_DIR / &amp;quot;logs&amp;quot;  # Pasta para logs (./logs)
LOG_CONFIG_PATH = ROOT_DIR / &amp;quot;logging.json&amp;quot;  # Arquivo de configuração


# não preciso mais de `name` como argumento aqui
def setup() -&amp;gt; None:
    &amp;quot;&amp;quot;&amp;quot;Configura o logger principal da aplicação.&amp;quot;&amp;quot;&amp;quot;

    # Se a pasta LOGS_DIR não existir, teremos um erro na aplicação,
    # então verificamos e, se não existir, criamos.
    if not LOGS_DIR.is_dir():
        LOGS_DIR.mkdir(parents=True, exist_ok=True)

    with LOG_CONFIG_PATH.open(encoding=&amp;quot;utf8&amp;quot;) as file:
        logging_config = json.load(file)

    dictConfig(logging_config)

    # Aqui está o que adicionamos para iniciar e finalizar `QueueListener`
    queue_handler = logging.getHandlerByName(&amp;quot;queue&amp;quot;)
    if queue_handler is not None:
        # pyright não reconheceu os tipos
        queue_handler.listener.start()  # pyright: ignore
        atexit.register(queue_handler.listener.stop)  # pyright: ignore

    # sem retorno


def parse_jsonl(path: Path) -&amp;gt; list[dict[str, Any]]:
    with path.open(&amp;quot;r&amp;quot;, encoding=&amp;quot;utf8&amp;quot;) as file:
        lines = file.readlines()

    logs: list[dict[str, Any]] = []

    if not lines:
        return logs

    for line in lines:
        logs.append(json.loads(line))

    return logs


if __name__ == &amp;quot;__main__&amp;quot;:
    from rich import print as p

    log_file = LOGS_DIR / &amp;quot;log.jsonl&amp;quot;

    for log in parse_jsonl(log_file):
        # This is just an example
        p(log)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O nosso JSON que funciona como configuração principal da &lt;code&gt;dictConfig&lt;/code&gt; ficou
dessa forma (não devo ter mudado nada nele):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;version&amp;quot;: 1,
  &amp;quot;disable_existing_loggers&amp;quot;: false,
  &amp;quot;formatters&amp;quot;: {
    &amp;quot;console&amp;quot;: {
      &amp;quot;format&amp;quot;: &amp;quot;%(message)s&amp;quot;,
      &amp;quot;datefmt&amp;quot;: &amp;quot;%H:%M:%S&amp;quot;
    },
    &amp;quot;json&amp;quot;: {
      &amp;quot;()&amp;quot;: &amp;quot;logging_course.formatters.JSONLogFormatter&amp;quot;,
      &amp;quot;include_keys&amp;quot;: [
        &amp;quot;created&amp;quot;,
        &amp;quot;message&amp;quot;,
        &amp;quot;levelname&amp;quot;,
        &amp;quot;name&amp;quot;,
        &amp;quot;filename&amp;quot;,
        &amp;quot;module&amp;quot;,
        &amp;quot;exc_info&amp;quot;,
        &amp;quot;lineno&amp;quot;,
        &amp;quot;threadName&amp;quot;,
        &amp;quot;processName&amp;quot;,
        &amp;quot;taskName&amp;quot;,
        &amp;quot;args&amp;quot;
      ]
    }
  },
  &amp;quot;handlers&amp;quot;: {
    &amp;quot;console&amp;quot;: {
      &amp;quot;class&amp;quot;: &amp;quot;rich.logging.RichHandler&amp;quot;,
      &amp;quot;formatter&amp;quot;: &amp;quot;console&amp;quot;,
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
      &amp;quot;rich_tracebacks&amp;quot;: false,
      &amp;quot;tracebacks_show_locals&amp;quot;: false,
      &amp;quot;show_time&amp;quot;: true,
      &amp;quot;show_level&amp;quot;: true,
      &amp;quot;omit_repeated_times&amp;quot;: false,
      &amp;quot;markup&amp;quot;: true,
      &amp;quot;enable_link_path&amp;quot;: true,
      &amp;quot;show_path&amp;quot;: true
    },
    &amp;quot;file&amp;quot;: {
      &amp;quot;class&amp;quot;: &amp;quot;logging.handlers.RotatingFileHandler&amp;quot;,
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;,
      &amp;quot;formatter&amp;quot;: &amp;quot;json&amp;quot;,
      &amp;quot;filename&amp;quot;: &amp;quot;logs/log.jsonl&amp;quot;,
      &amp;quot;maxBytes&amp;quot;: 5242880,
      &amp;quot;backupCount&amp;quot;: 5,
      &amp;quot;encoding&amp;quot;: &amp;quot;utf-8&amp;quot;
    },
    &amp;quot;queue&amp;quot;: {
      &amp;quot;class&amp;quot;: &amp;quot;logging.handlers.QueueHandler&amp;quot;,
      &amp;quot;handlers&amp;quot;: [&amp;quot;file&amp;quot;, &amp;quot;console&amp;quot;],
      &amp;quot;respect_handler_level&amp;quot;: true
    }
  },
  &amp;quot;root&amp;quot;: {
    &amp;quot;handlers&amp;quot;: [&amp;quot;queue&amp;quot;]
  },
  &amp;quot;loggers&amp;quot;: {
    &amp;quot;meuapp&amp;quot;: {
      &amp;quot;level&amp;quot;: &amp;quot;DEBUG&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Em qualquer parte da aplicação, eu estava testando as nossas configurações com o
seguinte código:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging

from logging_course import config_logger

if __name__ == &amp;quot;__main__&amp;quot;:
    config_logger.setup()

    logger = logging.getLogger(&amp;quot;meuapp&amp;quot;)

    logger.debug(&amp;quot;mensagem&amp;quot;)
    logger.debug(&amp;quot;Olá %s %s&amp;quot;, &amp;quot;Luiz&amp;quot;, &amp;quot;Otávio&amp;quot;, extra={&amp;quot;contexto&amp;quot;: &amp;quot;qualquer coisa&amp;quot;})
    logger.info(&amp;quot;mensagem&amp;quot;)
    logger.info(&amp;quot;Olá %s %s&amp;quot;, &amp;quot;Luiz&amp;quot;, &amp;quot;Otávio&amp;quot;, extra={&amp;quot;contexto&amp;quot;: &amp;quot;qualquer coisa&amp;quot;})
    logger.warning(&amp;quot;mensagem&amp;quot;)
    logger.warning(&amp;quot;Olá %s %s&amp;quot;, &amp;quot;Luiz&amp;quot;, &amp;quot;Otávio&amp;quot;, extra={&amp;quot;contexto&amp;quot;: &amp;quot;qualquer coisa&amp;quot;})
    logger.error(&amp;quot;mensagem&amp;quot;)
    logger.error(&amp;quot;Olá %s %s&amp;quot;, &amp;quot;Luiz&amp;quot;, &amp;quot;Otávio&amp;quot;, extra={&amp;quot;contexto&amp;quot;: &amp;quot;qualquer coisa&amp;quot;})
    logger.critical(&amp;quot;mensagem&amp;quot;)
    logger.critical(&amp;quot;Olá %s %s&amp;quot;, &amp;quot;Luiz&amp;quot;, &amp;quot;Otávio&amp;quot;, extra={&amp;quot;contexto&amp;quot;: &amp;quot;qualquer coisa&amp;quot;})

    try:
        print(1 / 0)
    except ZeroDivisionError:
        logger.exception(&amp;quot;Deu ruim&amp;quot;)
        logger.exception(
            &amp;quot;Olá %s %s&amp;quot;, &amp;quot;Luiz&amp;quot;, &amp;quot;Otávio&amp;quot;, extra={&amp;quot;contexto&amp;quot;: &amp;quot;qualquer coisa&amp;quot;}
        )
        logger.exception(&amp;quot;Deu ruim&amp;quot;, stack_info=True)
        logger.exception(
            &amp;quot;Olá %s %s&amp;quot;,
            &amp;quot;Luiz&amp;quot;,
            &amp;quot;Otávio&amp;quot;,
            extra={&amp;quot;contexto&amp;quot;: &amp;quot;qualquer coisa&amp;quot;},
            stack_info=True,
        )

    print(&amp;quot;Testando blocking&amp;quot;)
    print(&amp;quot;Esses prints são independentes do log.&amp;quot;)
    print(&amp;quot;Podem aparecer aleatóriamente no topo, meio ou final dos logs.&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;Boas Práticas no Logging do Python: e agora? - Aula 13&lt;/h2&gt;
&lt;p&gt;Parabéns!
&lt;a href=&quot;https://www.youtube.com/playlist?list=PLbIBj8vQhvm28qR-yvWP3JELGelWxsxaI&quot;&gt;Você chegou ao final do nosso curso&lt;/a&gt;
(e do texto, se estiver apenas lendo). A gente poderia continuar falando sobre
logging por horas, mas agora você já deixou o &lt;code&gt;print()&lt;/code&gt; pra trás, entendeu
Handlers, Formatters, Filtros, &lt;code&gt;dictConfig&lt;/code&gt;, JSON e até logging assíncrono com
&lt;code&gt;QueueHandler&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Você tem um poder enorme nas mãos. Mas agora vem as perguntas mais importantes:
&lt;strong&gt;Onde e como usar esse poder?&lt;/strong&gt; &lt;strong&gt;O que vale a pena registrar?&lt;/strong&gt; &lt;strong&gt;Como
escrever uma mensagem de log que ainda faça sentido daqui a 6 meses, às 3 da
manhã, quando um bug crítico estourar em produção?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Nesta última aula, o foco muda um pouco. Vamos falar menos sobre &lt;em&gt;como
configurar&lt;/em&gt; e mais sobre &lt;em&gt;por que logar, o que logar e quando usar cada nível&lt;/em&gt;.
Vamos explorar as boas práticas que separam um log amador de um log
profissional, e que podem realmente salvar sua pele quando tudo estiver pegando
fogo.&lt;/p&gt;
&lt;p&gt;Ah, e vale lembrar: isso aqui não tem fim. Sempre tem mais pra aprender.
&lt;a href=&quot;https://medium.com/@joecrobak/seven-best-practices-for-keeping-sensitive-data-out-of-logs-3d7bbd12904&quot;&gt;Erros em logs acontecem até nas maiores empresas do mundo&lt;/a&gt;,
então continue estudando, melhorando, revisando seus logs... e evitando que eles
virem uma armadilha.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Para Quem Você Está Escrevendo o Log?&lt;/h3&gt;
&lt;p&gt;Antes de mais nada: &lt;strong&gt;log não é só pra você, desenvolvedor.&lt;/strong&gt; Na verdade, o que
deve ser registrado em log geralmente &lt;strong&gt;não é decidido apenas pelos devs&lt;/strong&gt;. Em
ambientes profissionais, outros setores também dependem desses dados.&lt;/p&gt;
&lt;p&gt;Claro, como dev, você provavelmente vai usar &lt;code&gt;debug&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;warning&lt;/code&gt;,&lt;code&gt;error&lt;/code&gt;,
&lt;code&gt;critical&lt;/code&gt; e &lt;code&gt;exception&lt;/code&gt; para entender o comportamento do seu código. Mas a
empresa como um todo precisa de muito mais:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;O usuário criou uma conta&lt;/li&gt;
&lt;li&gt;O usuário fez login&lt;/li&gt;
&lt;li&gt;Visualizou o produto X&lt;/li&gt;
&lt;li&gt;Comprou o produto Y&lt;/li&gt;
&lt;li&gt;Pesquisou por &amp;quot;banana orgânica&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tudo isso pode parecer irrelevante para debugging técnico, mas para outras
áreas, esses logs são &lt;strong&gt;ouro em forma de texto&lt;/strong&gt;. Gente de marketing, produto,
segurança, auditoria... todos querem (e precisam) ver esses eventos.&lt;/p&gt;
&lt;p&gt;Então, &lt;strong&gt;antes de sair logando tudo no &lt;code&gt;debug&lt;/code&gt;&lt;/strong&gt;, entenda o &lt;strong&gt;propósito&lt;/strong&gt; do
log. Você está logando para debug ou para outro setor? Se é para outro setor,
quais dados eles precisam? Esses dados podem ser salvos? São sensíveis?&lt;/p&gt;
&lt;p&gt;Veja alguns exemplos genéricos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Logs para Desenvolvedores (Você do Futuro):&lt;/strong&gt; Foco em debugging. Informações
que ajudam a entender o fluxo da aplicação, o estado de variáveis e a causa de
um bug (não é só debug, todos os níveis de log podem ser relevantes aqui).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Logs para SysAdmins / SRE / DevOps (Monitoramento):&lt;/strong&gt; Foco em saúde do
sistema. Logs sobre falhas, lentidão, uso de recursos, quedas de serviço, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Logs para o Negócio / Auditoria (Análise):&lt;/strong&gt; Foco em rastrear ações
importantes do ponto de vista do negócio. Exemplo: &amp;quot;Usuário X comprou produto
Y&amp;quot;, &amp;quot;Relatório Z foi gerado&amp;quot; (claro, você precisa se comunicar com outros
setores para saber o que logar).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Logs para fins legais?&lt;/strong&gt; Se um usuário da sua aplicação fizer algo ilegal,
talvez a justiça queira saber o que aconteceu. Aqui entra outro tipo de
responsabilidade: retenção de logs, dados sensíveis, LGPD, etc. &lt;strong&gt;Consulte um
advogado&lt;/strong&gt; (ou o jurídico da empresa) para saber o que pode ou não ser
armazenado.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Decifrando os Níveis de Severidade na Prática&lt;/h3&gt;
&lt;p&gt;Agora sim: &lt;strong&gt;vamos entender quando usar cada nível de log na prática.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Abaixo você encontra a descrição de cada nível com exemplos concretos, tanto de
situações quanto de código.&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;&lt;code&gt;DEBUG&lt;/code&gt;: &amp;quot;Esse é nosso (dos devs)&amp;quot;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Quando usar:&lt;/strong&gt; Para diagnóstico detalhado durante o desenvolvimento ou
debugging intenso.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;O que logar:&lt;/strong&gt; Variáveis internas, payloads de API, queries SQL, entrada e
saída de funções complexas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regra:&lt;/strong&gt; Geralmente desativado em produção para evitar excesso de ruído (e
vazamento de dados sensíveis da aplicação).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Cuidado com isso, payload pode conter dados sensíveis da aplicação
# e do usuário
logger.debug(&amp;quot;Iniciando função de login com payload: %s&amp;quot;, payload)
# Cuidado novamente (nem preciso falar de novo né?)
logger.debug(&amp;quot;Consulta SQL gerada: %s&amp;quot;, query)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;&lt;code&gt;INFO&lt;/code&gt;: Aconteceu algo certo&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Quando usar:&lt;/strong&gt; Para registrar eventos normais e esperados no fluxo da
aplicação.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;O que logar:&lt;/strong&gt; Login de usuário, criação de conta, execução bem-sucedida de
uma tarefa.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Sem senhas, pelo amor de Deus
logger.info(&amp;quot;Usuário %s logou com sucesso&amp;quot;, username)
logger.info(&amp;quot;Relatório gerado e enviado por email&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;&lt;code&gt;WARNING&lt;/code&gt;: Atenção, algo inesperado aconteceu&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Quando usar:&lt;/strong&gt; Quando algo não saiu como o esperado, mas a aplicação
conseguiu se recuperar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;O que logar:&lt;/strong&gt; Funcionalidades obsoletas, API lenta, falha com fallback,
tentativa de reconexão, muitas tentativas de login, etc...&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;logger.warning(&amp;quot;API externa demorou %d segundos para responder&amp;quot;, elapsed)
logger.warning(&amp;quot;Configuração &amp;#39;X&amp;#39; está obsoleta. Use &amp;#39;Y&amp;#39; no lugar.&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;&lt;code&gt;ERROR&lt;/code&gt;: Uma operação falhou&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Quando usar:&lt;/strong&gt; Quando uma tarefa específica não pôde ser concluída, mas a
aplicação continua rodando.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;O que logar:&lt;/strong&gt; Falha de validação, erro ao salvar no banco, exceção tratada
que impediu o sucesso da operação, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;logger.error(&amp;quot;Erro ao salvar usuário no banco: %s&amp;quot;, user_id)
logger.error(&amp;quot;Falha ao processar pagamento: %s&amp;quot;, str(error))
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;&lt;code&gt;CRITICAL&lt;/code&gt;: O navio está afundando!&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Quando usar:&lt;/strong&gt; Para falhas graves que impedem o funcionamento da aplicação.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;O que logar:&lt;/strong&gt; Perda de conexão com o banco após várias tentativas, falha na
inicialização de componente essencial, falha na rede interna, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;logger.critical(&amp;quot;Banco de dados inacessível após 5 tentativas. Encerrando aplicação.&amp;quot;)
logger.critical(&amp;quot;Arquivo de configuração principal está corrompido.&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;Menção honrosa: &lt;code&gt;logger.exception()&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Quando usar:&lt;/strong&gt; Sempre que você estiver dentro de um bloco &lt;code&gt;try...except&lt;/code&gt; é
interessante logar a exceção na maioria dos casos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vantagem:&lt;/strong&gt; Além de logar como &lt;code&gt;error&lt;/code&gt;, ele inclui automaticamente o
&lt;strong&gt;traceback&lt;/strong&gt;. Isso é ouro puro para debugging.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;try:
    resultado = processar_pagamento()
except Exception as e:
    logger.exception(&amp;quot;Erro inesperado ao processar pagamento&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;Com esses exemplos em mente, você já consegue diferenciar claramente o papel de
cada nível de severidade e aplicar isso com consciência. O importante é manter
consistência, clareza e foco em quem vai ler o log depois, muitas vezes, esse
alguém vai ser você mesmo (só que com sono e pressa).&lt;/p&gt;
&lt;p&gt;Além disso, como o que aprendemos sobre &lt;code&gt;loggers&lt;/code&gt;, &lt;code&gt;handlers&lt;/code&gt; e &lt;code&gt;filters&lt;/code&gt;, você
conseguirá separar bem os logs por pacote, módulo, por setor da empresa, ou como
preferir. Também conseguirá filtrar dados desnecessários em logs específicos.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;As Regras de Ouro de uma Boa Mensagem de Log&lt;/h3&gt;
&lt;p&gt;Depois de tudo que aprendemos sobre configuração e níveis de log, chegou a hora
da pergunta mais importante:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Como escrever uma mensagem de log que realmente ajuda?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Aqui vão 3 regras de ouro, simples, mas poderosas, que separam logs descartáveis
de logs profissionais e confiáveis.&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;&lt;strong&gt;Regra 1: Escreva logs para máquinas também, não só para humanos&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Sim, logs precisam ser legíveis. Mas eles também devem ser &lt;strong&gt;estruturados e
processáveis&lt;/strong&gt; por ferramentas de observabilidade, buscas e alertas.&lt;/p&gt;
&lt;p&gt;Evite logs &amp;quot;misteriosos&amp;quot; e difíceis de analisar com código.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;#🚫 Ruim:
logger.error(&amp;quot;Erro no usuário 123&amp;quot;)

# ✅ Bom:
logger.error(&amp;quot;Falha na atualização do perfil&amp;quot;, extra={
    &amp;quot;user_id&amp;quot;: 123,
    &amp;quot;reason&amp;quot;: &amp;quot;email_invalido&amp;quot;
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Com logs estruturados (em JSON, como fizemos), você pode &lt;strong&gt;filtrar todos os
erros por &lt;code&gt;reason=email_invalido&lt;/code&gt;&lt;/strong&gt;, ou agrupar por &lt;code&gt;user_id&lt;/code&gt;. Isso é impossível
com strings soltas e mal formatadas.&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;&lt;strong&gt;Regra 2: Contexto é rei&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;A mensagem &amp;quot;&lt;code&gt;Falhou&lt;/code&gt;&amp;quot; pode ser verdade, mas não ajuda em nada.&lt;/p&gt;
&lt;p&gt;Um bom log responde: &lt;strong&gt;Quem? O quê? Onde? Quando? Por quê?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Inclua IDs relevantes (de usuário, pedido, transação, etc). Quanto mais contexto
você der, mais rápido será o diagnóstico, inclusive por outras pessoas do time
(ou você no futuro, com pressa e sem paciência). Evite também colocar coisas
desnecessárias no log (falo disso adiante).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 🚫 Ruim:
logger.error(&amp;quot;Pagamento não foi processado&amp;quot;)

# ✅ Bom:
logger.error(&amp;quot;Erro ao processar pagamento&amp;quot;, extra={
    &amp;quot;user_id&amp;quot;: 42,
    &amp;quot;order_id&amp;quot;: 101,
    &amp;quot;gateway&amp;quot;: &amp;quot;Stripe&amp;quot;,
    &amp;quot;error_code&amp;quot;: &amp;quot;card_declined&amp;quot;
})
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;&lt;strong&gt;Regra 3: NUNCA logue informações sensíveis&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Isso é mais do que uma boa prática: é uma questão legal e de segurança.&lt;/p&gt;
&lt;p&gt;O que &lt;strong&gt;nunca&lt;/strong&gt; deve aparecer em logs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Senhas (óbvio, né?)&lt;/li&gt;
&lt;li&gt;Tokens de autenticação ou sessão&lt;/li&gt;
&lt;li&gt;Chaves de API&lt;/li&gt;
&lt;li&gt;Números de cartão de crédito&lt;/li&gt;
&lt;li&gt;Documentos como CPF, RG, etc.&lt;/li&gt;
&lt;li&gt;Endereços físicos de clientes, colaboradores, fornecedores, etc.&lt;/li&gt;
&lt;li&gt;Dados médicos ou sensíveis (LGPD/GDPR)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Dica técnica:&lt;/em&gt; Se você precisa logar objetos que podem conter dados sensíveis
(ex: um &lt;code&gt;User&lt;/code&gt; ou um &lt;code&gt;Request&lt;/code&gt;), crie uma função de serialização segura, ou use
um &lt;strong&gt;Filter&lt;/strong&gt; para limpar ou mascarar os dados antes de enviar ao log.&lt;/p&gt;
&lt;hr&gt;
&lt;h4&gt;&lt;strong&gt;Regra 4: Contexto é bom… até virar poluição&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Sim, na &lt;strong&gt;Regra 2&lt;/strong&gt; eu disse que &lt;em&gt;contexto é rei&lt;/em&gt;. Mas cuidado pra não
transformar o seu log num &lt;strong&gt;romance de 800 páginas com final inconclusivo&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Logar dados demais é tão ruim quanto logar de menos.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vai deixar os arquivos gigantes&lt;/li&gt;
&lt;li&gt;Vai dificultar buscas e análise&lt;/li&gt;
&lt;li&gt;Pode até esconder o que realmente importa&lt;/li&gt;
&lt;li&gt;Vai ficar caro&lt;/li&gt;
&lt;li&gt;Tem mais coisa ruim que não lembrei...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Ruído esconde o sinal.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Veja um exemplo de poluição:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 🚫 Ruim:
logger.debug(&amp;quot;Resposta completa da API: %s&amp;quot;, response.text)  # 1000 linhas de HTML
logger.info(&amp;quot;Usuário fez login&amp;quot;, extra={
    &amp;quot;user_id&amp;quot;: 1,
    &amp;quot;nome&amp;quot;: &amp;quot;Fulano&amp;quot;,
    &amp;quot;email&amp;quot;: &amp;quot;fulano@email.com&amp;quot;, # dado sensível
    &amp;quot;cpf&amp;quot;: &amp;quot;123.456.789-00&amp;quot;, # dado sensível
    &amp;quot;endereco&amp;quot;: &amp;quot;Rua tal, nº tal, bairro tal&amp;quot;, # dado sensível
    &amp;quot;navegador&amp;quot;: &amp;quot;Chrome 126&amp;quot;,
    &amp;quot;sistema&amp;quot;: &amp;quot;macOS&amp;quot;,
    &amp;quot;resolução&amp;quot;: &amp;quot;1920x1080&amp;quot;,
    &amp;quot;timestamp&amp;quot;: &amp;quot;2025-07-10T15:34:21Z&amp;quot;
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Melhor seria algo como:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# ✅ melhor - é o mesmo log, mas eu consigo usar o ID do usuário para fazer
# buscas internas no sistema.
logger.info(&amp;quot;Login realizado com sucesso&amp;quot;, extra={
    &amp;quot;user_id&amp;quot;: 1 # Com o ID do usuário você consegue buscar outros dados
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Regra prática:&lt;/strong&gt; Logue &lt;em&gt;apenas&lt;/em&gt; o que alguém precisa saber para &lt;strong&gt;agir&lt;/strong&gt;. Se o
dado não ajuda a entender, resolver ou monitorar o problema, talvez ele não
precise estar ali. Geralmente, IDs que identificam algo são suficientes para
fazer uma busca interna no sistema sem expor dados sensíveis em logs.&lt;/p&gt;
&lt;p&gt;Lembre-se que quando você não precisa de logs, eles são &amp;quot;lixo&amp;quot; e até
inconvenientes. Em algum momento alguém pode esquecer algo e vazar logs da sua
aplicação. Se isso acontecer, torça muito para não ter nenhum dado sensível no
meio dos dados vazados.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Concluindo&lt;/h3&gt;
&lt;p&gt;Se você chegou até aqui, parabéns. De verdade.&lt;/p&gt;
&lt;p&gt;Você saiu do &lt;code&gt;print()&lt;/code&gt; e agora tem um &lt;strong&gt;arsenal completo de logging
profissional&lt;/strong&gt; nas mãos: Handlers, Formatters, Filters, JSON, &lt;code&gt;dictConfig&lt;/code&gt;, logs
assíncronos com &lt;code&gt;QueueHandler&lt;/code&gt;, e, mais importante, &lt;strong&gt;boas práticas de uso&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Você entendeu que logging não é só sobre escrever mensagens no console. É sobre
&lt;strong&gt;observabilidade&lt;/strong&gt;, &lt;strong&gt;diagnóstico rápido&lt;/strong&gt;, &lt;strong&gt;comunicação entre times&lt;/strong&gt;,
&lt;strong&gt;segurança&lt;/strong&gt; e até &lt;strong&gt;compliance legal&lt;/strong&gt;. Ou seja: logging bem feito salva seu
código, sua sanidade e, às vezes, até o faturamento da empresa.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;E agora, o que fazer com esse conhecimento?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use. Refatore seus projetos. Melhore os logs do seu time. Mostre esse curso pra
alguém que ainda vive no &lt;code&gt;print()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;E quando for criar algo novo… lembra disso: &lt;strong&gt;um bom log não é ruído. É música
pros ouvidos de quem tá tentando resolver um bug às 3 da manhã.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Valeu demais por acompanhar até aqui! Se você curtiu, compartilha, vai lá no
vídeo e comenta, salva, manda pra galera. E se quiser aprender mais Python
moderno, CLI, automações, ou IA aplicada ao mundo real, dá uma olhada nas outras
playlists do canal ou nos cursos completos.&lt;/p&gt;
&lt;p&gt;Nos vemos nos próximos vídeos ou textos.&lt;/p&gt;
&lt;p&gt;Abraço!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Se quiser, esse texto está em markdown e &lt;a href=&quot;text.md&quot;&gt;pode ser baixado aqui&lt;/a&gt;. Você
também rolou bastante essa página né? &lt;a href=&quot;./&quot;&gt;Volte para o topo&lt;/a&gt; ou simplesmente vá
para &lt;a href=&quot;/&quot;&gt;nossa página inicial&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
</content:encoded></item><item><title>Escopo e Namespace em Python</title><link>https://otaviomiranda.com.br/2025/namespace-escopo-python/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2025/namespace-escopo-python/</guid><description>Em Python, o escopo determina a visibilidade e o tempo de vida dos nomes do seu programa, já o namespace é o local onde os nomes estão salvos.</description><pubDate>Sat, 26 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Em Python, &lt;strong&gt;escopo&lt;/strong&gt; é o contexto onde um &lt;strong&gt;nome&lt;/strong&gt; pode ser usado. Ele define a
região do código, o tempo de vida e os limites de acesso desses nomes.&lt;/p&gt;
&lt;p&gt;Durante a execução do seu programa, um ou mais &lt;strong&gt;escopos&lt;/strong&gt; estarão ativos. Isso
determina quais nomes estão visíveis naquele momento e quais resultarão em um
&lt;code&gt;NameError&lt;/code&gt; se você tentar acessá-los.&lt;/p&gt;
&lt;p&gt;Os nomes definidos dentro de um &lt;strong&gt;escopo&lt;/strong&gt; são armazenados em um objeto chamado
&lt;strong&gt;namespace&lt;/strong&gt;. Ele atua como um dicionário, onde a &lt;strong&gt;chave&lt;/strong&gt; é o nome (ou
rótulo) de um objeto e o &lt;strong&gt;valor&lt;/strong&gt; é o objeto referenciado.&lt;/p&gt;
&lt;p&gt;Neste artigo, vamos explorar não apenas o que são &lt;strong&gt;escopo e namespace&lt;/strong&gt;, mas
também vários outros temas relacionados. Ao final, você terá aprendido:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;O que é &lt;strong&gt;escopo&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;O que é &lt;strong&gt;namespace&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Os tipos de escopo: &lt;strong&gt;Local&lt;/strong&gt;, &lt;strong&gt;Enclosing&lt;/strong&gt;, &lt;strong&gt;Global&lt;/strong&gt; e &lt;strong&gt;Built-in&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;A regra LEGB: a ordem de busca por nomes&lt;/li&gt;
&lt;li&gt;Como usar as palavras-chave &lt;code&gt;global&lt;/code&gt; e &lt;code&gt;nonlocal&lt;/code&gt; de forma consciente&lt;/li&gt;
&lt;li&gt;Como usar funções como &lt;code&gt;globals()&lt;/code&gt;, &lt;code&gt;locals()&lt;/code&gt;, &lt;code&gt;vars()&lt;/code&gt; e &lt;code&gt;dir()&lt;/code&gt; para
inspecionar e acessar namespaces&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Em vídeo&lt;/h2&gt;
&lt;p&gt;Eu falo sobre escopo e namespace no Python (tudo o que falo neste artigo) em
dois vídeos no meu canal do Youtube. Caso prefira, assista abaixo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vídeo 1 - &lt;a href=&quot;https://youtu.be/GkgbSIYSHUg&quot;&gt;Escopo e Namespace em Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vídeo 2 -
&lt;a href=&quot;https://youtu.be/U8oF5WWpEGk&quot;&gt;Entenda a regra LEGB e Enclosing no Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;O que é escopo?&lt;/h2&gt;
&lt;p&gt;Quando você cria um nome em seu código, como uma &lt;strong&gt;variável&lt;/strong&gt;, uma &lt;strong&gt;função&lt;/strong&gt; ou
uma &lt;strong&gt;classe&lt;/strong&gt;, ele só pode ser usado &lt;strong&gt;dentro de um certo contexto&lt;/strong&gt;. Esse
contexto é o que chamamos de &lt;strong&gt;escopo&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Em termos práticos, &lt;strong&gt;escopo é a região do seu código onde um nome está
diretamente acessível&lt;/strong&gt;. Ou seja, é onde o Python vai &amp;quot;procurar&amp;quot; por aquele nome
quando você tenta utilizá-lo. Se você conseguir acessar o nome solicitado,
falamos que ele está &lt;strong&gt;no escopo&lt;/strong&gt;, do contrário ele estará &lt;strong&gt;fora do escopo&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt;: sempre que eu usar a palavra &amp;quot;&lt;strong&gt;nome&lt;/strong&gt;&amp;quot;, estou me referindo a
identificadores como variáveis, classes, funções, módulos ou qualquer coisa
que pode receber um &lt;strong&gt;nome&lt;/strong&gt; em Python.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Vamos para um exemplo (sempre leia os comentários de código):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;x = 10  # Variável &amp;#39;x&amp;#39; no escopo global

def mostrar():
    print(x)  # Acessa &amp;#39;x&amp;#39; do escopo global

mostrar()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nesse código, a variável &lt;code&gt;x&lt;/code&gt; foi definida no &lt;strong&gt;escopo global&lt;/strong&gt;, fora de qualquer
função. Quando &lt;code&gt;print(x)&lt;/code&gt; é chamado dentro da função &lt;code&gt;mostrar()&lt;/code&gt;, o Python
primeiramente busca &lt;code&gt;x&lt;/code&gt; no escopo local da função. Como não o encontra lá, ele
&amp;quot;sobe&amp;quot; para o escopo global, onde &lt;code&gt;x&lt;/code&gt; está disponível.&lt;/p&gt;
&lt;p&gt;Mas onde o Python guarda esse nome &lt;code&gt;x&lt;/code&gt; com o valor &lt;code&gt;10&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;É nesse ponto que os &lt;strong&gt;namespaces&lt;/strong&gt; entram em cena: &lt;strong&gt;todo escopo possui seu
próprio &amp;quot;espaço de nomes&amp;quot;&lt;/strong&gt;, um local onde os nomes e seus respectivos objetos
são armazenados. Vamos explorar isso em detalhes a seguir.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;O que é namespace?&lt;/h2&gt;
&lt;p&gt;Um &lt;strong&gt;namespace&lt;/strong&gt; é, essencialmente, uma &lt;strong&gt;estrutura que mapeia nomes a
objetos&lt;/strong&gt;. Você pode pensar nele como um dicionário onde cada &lt;strong&gt;chave&lt;/strong&gt; é o nome
que você define (como o nome de uma variável, função ou classe) e o &lt;strong&gt;valor&lt;/strong&gt; é
o objeto correspondente no seu código.&lt;/p&gt;
&lt;p&gt;Sempre que você cria um nome em Python, essa associação é guardada dentro de um
namespace. Algumas das formas mais comuns de criar nomes incluem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Atribuição:&lt;/strong&gt; &lt;code&gt;variavel = valor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Importação:&lt;/strong&gt; &lt;code&gt;import modulo&lt;/code&gt; ou &lt;code&gt;from modulo import objeto&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Definição de funções:&lt;/strong&gt; &lt;code&gt;def minha_funcao(): ...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Argumentos de funções:&lt;/strong&gt; &lt;code&gt;def outra_funcao(arg1, arg2): ...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Definição de classes:&lt;/strong&gt; &lt;code&gt;class MinhaClasse: ...&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; existem diferenças entre operações de atribuição (criar ou modificar
um valor) e operações de acesso (apenas ler um valor). Vamos falar sobre isso
nesse artigo na seção
&amp;quot;&lt;a href=&quot;#global-nonlocal-324&quot;&gt;global e nonlocal - alterando nomes fora do escopo&lt;/a&gt;&amp;quot;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Todos esses exemplos resultam na criação de nomes dentro do &lt;strong&gt;namespace&lt;/strong&gt; ativo
no momento da sua definição.&lt;/p&gt;
&lt;p&gt;Cada namespace está diretamente ligado a um &lt;strong&gt;escopo&lt;/strong&gt;, que determina onde e
quando aquele nome estará disponível durante a execução do seu código.&lt;/p&gt;
&lt;p&gt;Embora você possa inspecionar namespaces usando funções como &lt;code&gt;globals()&lt;/code&gt; e
&lt;code&gt;locals()&lt;/code&gt; (que retornam representações em dicionário), isso é mais comum para
introspecção de código ou fins didáticos do que para o uso diário.&lt;/p&gt;
&lt;p&gt;Existem diferentes tipos de namespaces em Python, cada um com sua finalidade:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Global:&lt;/strong&gt; Cada módulo Python possui um namespace global, que armazena os
nomes definidos diretamente na sua raiz, fora de funções ou classes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local:&lt;/strong&gt; Cada vez que uma função é chamada, um namespace local é criado para
ela. Ele existe apenas enquanto a função está em execução e contém as
variáveis e argumentos definidos dentro dela.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in:&lt;/strong&gt; Este namespace contém os nomes de todas as funções e exceções
nativas do Python, como &lt;code&gt;print()&lt;/code&gt;, &lt;code&gt;len()&lt;/code&gt;, &lt;code&gt;str()&lt;/code&gt;, etc. Ele é carregado
automaticamente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enclosing (Não-local):&lt;/strong&gt; Quando uma função é definida dentro de outra, a
função interna pode acessar os nomes do namespace da função externa. Este atua
como um escopo &amp;quot;intermediário&amp;quot; entre o local e o global.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Classes:&lt;/strong&gt; O corpo de uma classe também forma seu próprio namespace, onde os
atributos e métodos da classe são definidos. É por isso que você acessa
membros da classe usando a notação de ponto (&lt;code&gt;objeto.atributo&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;É importante notar que os namespaces de funções (Local e Enclosing) são criados
dinamicamente no momento da execução. Quando uma função é chamada, o
interpretador Python gera um &lt;strong&gt;Frame de Execução&lt;/strong&gt; (ou Stack Frame) que
encapsula as variáveis locais, parâmetros e outras informações necessárias para
aquela chamada específica. A função &lt;code&gt;locals()&lt;/code&gt; pode ser usada dentro de uma
função para acessar seu namespace local.&lt;/p&gt;
&lt;p&gt;Uma característica fundamental é que &lt;strong&gt;todos os namespaces são independentes
entre si&lt;/strong&gt;. Por exemplo, você pode ter uma função chamada &lt;code&gt;processar()&lt;/code&gt; em dois
módulos diferentes (&lt;code&gt;modulo1.py&lt;/code&gt; e &lt;code&gt;modulo2.py&lt;/code&gt;). Não haverá conflito, pois cada
uma reside em seu próprio namespace. Para acessá-las, basta prefixar com o nome
do módulo: &lt;code&gt;modulo1.processar()&lt;/code&gt; e &lt;code&gt;modulo2.processar()&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;code&gt;globals()&lt;/code&gt; e &lt;code&gt;locals()&lt;/code&gt; para inspeção de namespaces&lt;/h3&gt;
&lt;p&gt;As funções &lt;code&gt;globals()&lt;/code&gt; e &lt;code&gt;locals()&lt;/code&gt; são ferramentas valiosas para inspecionar
namespaces em tempo de execução:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/library/functions.html#globals&quot;&gt;&lt;code&gt;globals()&lt;/code&gt;&lt;/a&gt;:
Retorna um dicionário que representa o &lt;strong&gt;namespace global&lt;/strong&gt; do módulo atual.
Isso inclui todos os nomes definidos na raiz do arquivo.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/library/functions.html#locals&quot;&gt;&lt;code&gt;locals()&lt;/code&gt;&lt;/a&gt;: Retorna
um dicionário com os nomes definidos no &lt;strong&gt;escopo local&lt;/strong&gt; onde a função está
sendo executada. &lt;strong&gt;Importante&lt;/strong&gt;: ela só inclui nomes que já foram definidos
&lt;em&gt;antes&lt;/em&gt; da sua chamada, então geralmente é melhor chamá-la no final do bloco.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vejamos um exemplo prático:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Escopo global

def show_namespace() -&amp;gt; None:
    # Escopo local
    module_namespace = globals()
    print(&amp;quot;Conteúdo de globals():&amp;quot;, module_namespace)

    # Note que &amp;#39;function_namespace&amp;#39; não aparecerá no locals() se chamado antes dela
    function_namespace = locals()
    print(&amp;quot;Conteúdo de locals():&amp;quot;, function_namespace)


if __name__ == &amp;quot;__main__&amp;quot;:
    show_namespace()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A saída de &lt;code&gt;globals()&lt;/code&gt; será algo parecido com:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;{
    &amp;#39;__name__&amp;#39;: &amp;#39;__main__&amp;#39;,
    &amp;#39;__doc__&amp;#39;: None,
    # ... outras chaves padrão do módulo
    &amp;#39;__builtins__&amp;#39;: &amp;lt;module &amp;#39;builtins&amp;#39; (built-in)&amp;gt;,
    &amp;#39;show_namespace&amp;#39;: &amp;lt;function show_namespace at 0x...&amp;gt; # Nossa função
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Já a saída de &lt;code&gt;locals()&lt;/code&gt; mostrará:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;{
    &amp;#39;module_namespace&amp;#39;: {...} # O dicionário retornado por globals()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alguns pontos a destacar nessa saída:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__name__&lt;/code&gt;: Indica o nome do módulo. Se for o script principal, será
&lt;code&gt;__main__&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;__builtins__&lt;/code&gt;: Referência ao namespace com as funções nativas do Python.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;show_namespace&lt;/code&gt;: O nome da nossa função, definida no escopo global.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;code&gt;vars()&lt;/code&gt; e &lt;code&gt;dir()&lt;/code&gt; - Outras formas de inspecionar&lt;/h3&gt;
&lt;p&gt;Além de &lt;code&gt;globals()&lt;/code&gt; e &lt;code&gt;locals()&lt;/code&gt;, &lt;code&gt;vars()&lt;/code&gt; e &lt;code&gt;dir()&lt;/code&gt; também permitem inspecionar
nomes, mas com algumas diferenças importantes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/library/functions.html#vars&quot;&gt;&lt;code&gt;vars()&lt;/code&gt;&lt;/a&gt;: Retorna o
atributo &lt;code&gt;__dict__&lt;/code&gt; de um objeto, que é onde seus atributos são armazenados.
Se chamada sem argumentos, &lt;code&gt;vars()&lt;/code&gt; se comporta exatamente como &lt;code&gt;locals()&lt;/code&gt;,
retornando o namespace local.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.python.org/3/library/functions.html#dir&quot;&gt;&lt;code&gt;dir()&lt;/code&gt;&lt;/a&gt;: Sem
argumentos, &lt;code&gt;dir()&lt;/code&gt; lista todos os nomes disponíveis no escopo atual. Com um
objeto como argumento, tenta listar todos os nomes acessíveis nele (como
métodos e atributos). Note que &lt;code&gt;dir()&lt;/code&gt; retorna apenas os &lt;em&gt;nomes&lt;/em&gt;, não os
objetos ou seus valores.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vamos ver com um exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def ver_namespace_local():
    var_local = &amp;quot;variável local&amp;quot;

    print(&amp;quot;\nDENTRO DA FUNÇÃO (ESCOPO LOCAL)&amp;quot;)
    print(&amp;quot;Saída de vars():&amp;quot;, vars())
    print(&amp;quot;Saída de dir():&amp;quot;, dir())


print(&amp;quot;FORA DA FUNÇÃO (ESCOPO GLOBAL)&amp;quot;)
print(&amp;quot;Saída de vars():&amp;quot;, vars())
print(&amp;quot;Saída de dir():&amp;quot;, dir())

ver_namespace_local()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Saída de exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# FORA DA FUNÇÃO (ESCOPO GLOBAL)
# Saída de vars():
{
    # ... omitidos outros resultados globais
    &amp;#39;ver_namespace_local&amp;#39;: &amp;lt;function ver_namespace_local at 0x...&amp;gt;
}
# Saída de dir():
[
    &amp;#39;__annotations__&amp;#39;, &amp;#39;__builtins__&amp;#39;, &amp;#39;__cached__&amp;#39;, &amp;#39;__doc__&amp;#39;,
    &amp;#39;__file__&amp;#39;, &amp;#39;__loader__&amp;#39;, &amp;#39;__name__&amp;#39;, &amp;#39;__package__&amp;#39;, &amp;#39;__spec__&amp;#39;,
    &amp;#39;ver_namespace_local&amp;#39; # Nossa função global
]

# DENTRO DA FUNÇÃO (ESCOPO LOCAL)
# Saída de vars():
{
    &amp;#39;var_local&amp;#39;: &amp;#39;variável local&amp;#39;
}
# Saída de dir():
[&amp;#39;var_local&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Como pode ver, quando usadas sem argumentos, &lt;code&gt;vars()&lt;/code&gt; e &lt;code&gt;dir()&lt;/code&gt; se assemelham a
&lt;code&gt;locals()&lt;/code&gt; e &lt;code&gt;globals()&lt;/code&gt; no que diz respeito ao escopo atual. A grande diferença
é que &lt;strong&gt;&lt;code&gt;vars()&lt;/code&gt; e &lt;code&gt;dir()&lt;/code&gt; são mais versáteis&lt;/strong&gt;, pois podem receber um objeto
como argumento, permitindo inspecionar namespaces de módulos importados,
instâncias de classe e outros objetos.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Regra LEGB - Local, Enclosing, Global e Built-In&lt;/h2&gt;
&lt;p&gt;Em Python, é bem comum ter um escopo dentro do outro, pense em funções dentro de
módulos, ou até mesmo funções aninhadas dentro de outras funções.&lt;/p&gt;
&lt;p&gt;Quando isso acontece e você tenta acessar um nome (como uma variável ou função),
o interpretador precisa de uma ordem clara para encontrá-lo, dependendo do ponto
em que você está no código.&lt;/p&gt;
&lt;p&gt;Essa ordem é definida pela regra &lt;strong&gt;LEGB&lt;/strong&gt;. Sempre que você referencia um nome,
seja para leitura, escrita ou execução (tipo &lt;code&gt;x&lt;/code&gt; ou &lt;code&gt;print()&lt;/code&gt;), o Python o busca
sequencialmente, do escopo mais interno para o mais externo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Local (L):&lt;/strong&gt; Primeiro, o Python procura no escopo da &lt;strong&gt;função atual&lt;/strong&gt; onde o
nome está sendo referenciado. Este é o ambiente mais imediato.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enclosing (E) / Não-Local:&lt;/strong&gt; Se o nome não for encontrado localmente, a
busca avança para o escopo das &lt;strong&gt;funções que envolvem a função atual&lt;/strong&gt; (se
houver). Pense nisso como o escopo &amp;quot;da função de fora&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Global (G):&lt;/strong&gt; Caso o nome ainda não seja encontrado, o Python então procura
no &lt;strong&gt;escopo do módulo atual&lt;/strong&gt;. Este é o nível superior do seu arquivo Python.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in (B):&lt;/strong&gt; Por último, a busca chega ao escopo &lt;code&gt;built-in&lt;/code&gt;, que contém
todos os nomes nativos do Python, como funções (&lt;code&gt;len()&lt;/code&gt;, &lt;code&gt;print()&lt;/code&gt;) e exceções
(&lt;code&gt;NameError&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Se o nome não for encontrado em nenhum desses escopos, o Python levanta uma
exceção &lt;code&gt;NameError&lt;/code&gt;, indicando que o nome não foi definido ou não está acessível
no contexto atual.&lt;/p&gt;
&lt;p&gt;Para visualizar como esses escopos se aninham, imagine a seguinte representação:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt; ┌─────────────────────┐
 │ ┌───────────────┐   │
 │ │ ┌─────────┐   │   │
 │ │ │ ┌───┐   │   │   │
 │ │ │ │ L→│ → │ → │ → │ # Local (L): Onde você está agora, o mais interno
 │ │ │ └───┘   │   │   │
 │ │ │       E→│ → │ → │ # Enclosing (E): A função que envolve a sua função
 │ │ └─────────┘   │   │
 │ │             G→│ → │ # Global (G): O módulo inteiro do seu arquivo
 │ └───────────────┘   │
 │                  B→✘│ # Built-in (B): Mais externo, tudo do Python
 └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se preferir, preparei uma imagem também para te ajudar a entender melhor.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/python-legb-rule.webp&quot; alt=&quot;Diagrama mostrando a regra LEGB do Python&quot;&gt;&lt;/p&gt;
&lt;p&gt;A seguir, vamos ver exemplos de código para tornar cada um desses escopos
cristalino.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;A regra LEGB em ação&lt;/h3&gt;
&lt;p&gt;Para entender a &lt;strong&gt;regra LEGB&lt;/strong&gt; na prática, vamos usar um código simples e
observar como o Python busca os nomes em cada escopo. Usei bastante os
comentátios do código para detalhar melhor o que está acontecendo.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Escopo do módulo: Global (G)
var_global = &amp;quot;Estou no escopo global!&amp;quot;

def funcao_externa() -&amp;gt; None:
    # Escopo da função: Local (L) para esta função
    var_local = &amp;quot;Estou no escopo local!&amp;quot;

    # Busca de &amp;#39;var_local&amp;#39;:
    # O Python encontra &amp;#39;var_local&amp;#39; no escopo Local (L).
    print(var_local)

    # Busca de &amp;#39;var_global&amp;#39;:
    # Não encontra &amp;#39;var_global&amp;#39; no Local (L), então busca no Global (G).
    print(var_global)

    # Busca de &amp;#39;print&amp;#39;:
    # Não encontra &amp;#39;print&amp;#39; em L ou G, então busca no Built-in (B).
    print(print) # Imprime a representação da própria função print


if __name__ == &amp;quot;__main__&amp;quot;:
    funcao_externa()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Analisando o fluxo de busca de nomes nesse exemplo, o Python segue a regra LEGB
(sempre do mais interno ao mais externo):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;var_local&lt;/code&gt;&lt;/strong&gt;: Encontrado diretamente no escopo &lt;strong&gt;L&lt;/strong&gt;ocal da
&lt;code&gt;funcao_externa&lt;/code&gt;. Trajeto: &lt;strong&gt;L&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;var_global&lt;/code&gt;&lt;/strong&gt;: Não encontrado em &lt;strong&gt;L&lt;/strong&gt;, a busca sobe para o escopo
&lt;strong&gt;G&lt;/strong&gt;lobal. Trajeto: &lt;strong&gt;L → G&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;print&lt;/code&gt;&lt;/strong&gt;: Não encontrado em &lt;strong&gt;L&lt;/strong&gt; nem em &lt;strong&gt;G&lt;/strong&gt;, a busca atinge o escopo
&lt;strong&gt;B&lt;/strong&gt;uilt-in. Trajeto: &lt;strong&gt;L → G → B&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;É fundamental entender que essa busca é sempre &lt;strong&gt;unidirecional: de dentro para
fora&lt;/strong&gt;. Escopos mais internos conseguem &amp;quot;enxergar&amp;quot; os nomes definidos nos
escopos mais externos (que os contêm), mas o contrário não é verdadeiro.&lt;/p&gt;
&lt;p&gt;Veja um exemplo clássico de &lt;code&gt;NameError&lt;/code&gt; que prova isso:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def funcao_interna():
    mensagem = &amp;quot;Olá do escopo local!&amp;quot;

# Erro! &amp;#39;mensagem&amp;#39; só existe dentro de &amp;#39;funcao_interna&amp;#39;
print(mensagem)
# Saída esperada: NameError: name &amp;#39;mensagem&amp;#39; is not defined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A mensagem de erro &lt;code&gt;NameError: name &amp;#39;mensagem&amp;#39; is not defined&lt;/code&gt; ocorre porque, no
escopo global, o Python não consegue &amp;quot;descer&amp;quot; para o escopo local de
&lt;code&gt;funcao_interna&lt;/code&gt; para encontrar &lt;code&gt;mensagem&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Em resumo: a regra LEGB é uma escada de subida. Você só vê os andares acima
ou o seu próprio. Nunca consegue ver o que está nos andares abaixo do seu.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3&gt;Keywords não seguem a LEGB!&lt;/h3&gt;
&lt;p&gt;É importante diferenciar nomes de &lt;strong&gt;keywords&lt;/strong&gt; (palavras-chave). Termos como
&lt;code&gt;def&lt;/code&gt;, &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;, &lt;code&gt;return&lt;/code&gt;, &lt;code&gt;True&lt;/code&gt;, &lt;code&gt;False&lt;/code&gt;, &lt;code&gt;None&lt;/code&gt; e &lt;code&gt;import&lt;/code&gt; são
partes da gramática do Python, não nomes.&lt;/p&gt;
&lt;p&gt;Por serem palavras reservadas, elas &lt;strong&gt;não pertencem a nenhum namespace&lt;/strong&gt; e,
consequentemente, &lt;strong&gt;não seguem a regra LEGB&lt;/strong&gt;. Tentar usá-las como variáveis
resultará em um erro de sintaxe (&lt;code&gt;SyntaxError&lt;/code&gt;) no momento da análise do código,
antes mesmo da execução:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Isso vai causar um SyntaxError!
# Esse código nem chega a rodar
def = 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se quiser ver a lista completa de palavras-chave reservadas, execute o comando
abaixo no seu terminal Python:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -c &amp;quot;help(&amp;#39;keywords&amp;#39;)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isso mostrará a lista das &lt;code&gt;keywords&lt;/code&gt; do Python, como &lt;code&gt;False&lt;/code&gt;, &lt;code&gt;True&lt;/code&gt;, &lt;code&gt;None&lt;/code&gt;,
&lt;code&gt;and&lt;/code&gt;, &lt;code&gt;as&lt;/code&gt;, &lt;code&gt;assert&lt;/code&gt;, &lt;code&gt;break&lt;/code&gt;, &lt;code&gt;class&lt;/code&gt;, etc. (Existem também as &lt;em&gt;soft
keywords&lt;/em&gt;, mas isso fica para outra conversa).&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Enclosing - entenda o escopo aninhado&lt;/h2&gt;
&lt;p&gt;Enquanto os escopos &lt;strong&gt;Global&lt;/strong&gt;, &lt;strong&gt;Local&lt;/strong&gt; e &lt;strong&gt;Built-in&lt;/strong&gt; costumam ser mais
intuitivos, o escopo &lt;strong&gt;Enclosing&lt;/strong&gt; gera mais dúvidas porque ele só aparece em um
cenário específico: quando temos &lt;strong&gt;funções aninhadas&lt;/strong&gt;, ou seja, uma função
definida dentro de outra. Vamos focar nele agora.&lt;/p&gt;
&lt;p&gt;Considere este código:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def funcao_externa():
    # &amp;#39;var_enclosing&amp;#39; é local para &amp;#39;funcao_externa&amp;#39;
    # e Enclosing para &amp;#39;funcao_interna&amp;#39;
    var_enclosing = &amp;quot;Eu sou do escopo enclosing!&amp;quot;

    def funcao_interna():
        # &amp;#39;var_local&amp;#39; está no escopo Local de &amp;#39;funcao_interna&amp;#39;
        var_local = &amp;quot;Eu sou do escopo local!&amp;quot;

        # Acessando &amp;#39;var_local&amp;#39; (L) e &amp;#39;var_enclosing&amp;#39; (E)
        print(var_local, var_enclosing)

    # Chamamos a função interna para executá-la
    funcao_interna()

funcao_externa()
# Saída esperada: Eu sou do escopo local! Eu sou do escopo enclosing!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Como você pode ver na saída, a &lt;code&gt;funcao_interna()&lt;/code&gt; consegue acessar tanto
&lt;code&gt;var_local&lt;/code&gt; (que está em seu escopo &lt;strong&gt;L&lt;/strong&gt;ocal) quanto &lt;code&gt;var_enclosing&lt;/code&gt; (que está
no escopo &lt;strong&gt;E&lt;/strong&gt;nclosing, da &lt;code&gt;funcao_externa&lt;/code&gt;). Isso acontece exatamente por
causa da regra &lt;strong&gt;LEGB&lt;/strong&gt;: o Python busca os nomes de dentro para fora,
encontrando &lt;code&gt;var_enclosing&lt;/code&gt; no nível acima.&lt;/p&gt;
&lt;p&gt;Por outro lado, a &lt;code&gt;funcao_externa()&lt;/code&gt; &lt;strong&gt;não tem acesso direto&lt;/strong&gt; a &lt;code&gt;var_local&lt;/code&gt;,
pois &lt;code&gt;var_local&lt;/code&gt; reside em um escopo mais interno que o dela. Da mesma forma, o
escopo global não consegue acessar nenhuma das variáveis definidas dentro de
&lt;code&gt;funcao_externa&lt;/code&gt; ou &lt;code&gt;funcao_interna&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Call stack e o tempo de vida dos escopos&lt;/h3&gt;
&lt;p&gt;A razão para esse comportamento reside em como o Python gerencia a execução de
funções utilizando a &lt;strong&gt;call stack&lt;/strong&gt; (pilha de chamadas).&lt;/p&gt;
&lt;p&gt;Sempre que uma função é chamada, o Python cria um &lt;strong&gt;frame de execução&lt;/strong&gt; (ou
&lt;em&gt;stack frame&lt;/em&gt;) para ela. Esse frame é empilhado no topo da call stack, que
funciona no esquema LIFO (Last-In, First-Out: o último que entra é o primeiro
que sai). Apenas o frame no topo da pilha está ativo. Quando a função termina
sua execução, seu frame é &lt;strong&gt;descartado&lt;/strong&gt; da pilha, e o Python retorna o controle
para o frame anterior. É por isso que o escopo local de uma função só &amp;quot;existe&amp;quot;
enquanto ela está em execução.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/call-stack-example.webp&quot; alt=&quot;Exemplo da call stack do Python em execução&quot;&gt;&lt;/p&gt;
&lt;p&gt;A imagem acima ilustra a call stack em ação. O ícone do Python indica a posição
atual do interpretador. As &lt;strong&gt;setas vermelhas&lt;/strong&gt; (&lt;code&gt;---&amp;gt;&lt;/code&gt;) representam o &lt;strong&gt;fluxo de
execução&lt;/strong&gt; e a criação/remoção de frames na pilha. As &lt;strong&gt;setas amarelas&lt;/strong&gt; (&lt;code&gt;↓&lt;/code&gt;)
mostram a &lt;strong&gt;direção de busca de nomes&lt;/strong&gt; de acordo com a regra LEGB (do escopo
mais interno para o mais externo).&lt;/p&gt;
&lt;p&gt;Observe o fluxo na imagem:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A execução inicia no &lt;code&gt;mod.py&lt;/code&gt; (escopo Global).&lt;/li&gt;
&lt;li&gt;Ao chamar &lt;code&gt;funcao_externa()&lt;/code&gt;, um novo frame é empilhado. O interpretador se
move para dentro dela.&lt;/li&gt;
&lt;li&gt;Dentro de &lt;code&gt;funcao_externa()&lt;/code&gt;, ao chamar &lt;code&gt;funcao_interna()&lt;/code&gt;, outro frame é
empilhado, e o interpretador se move para lá.&lt;/li&gt;
&lt;li&gt;Quando &lt;code&gt;funcao_interna()&lt;/code&gt; termina, seu frame é desempilhado, e o controle
retorna para &lt;code&gt;funcao_externa()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finalmente, &lt;code&gt;funcao_externa()&lt;/code&gt; termina, seu frame é desempilhado, e a
execução retorna ao &lt;code&gt;mod.py&lt;/code&gt; (escopo global), concluindo o processo.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Esse mecanismo é fundamental para a organização e o isolamento do código:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Você consegue acessar nomes definidos em escopos mais externos (Globais,
Enclosing) a partir de dentro de uma função.&lt;/li&gt;
&lt;li&gt;Mas &lt;strong&gt;não consegue acessar nomes definidos dentro de funções estando fora
delas&lt;/strong&gt;, porque o escopo local de uma função &lt;strong&gt;deixa de existir&lt;/strong&gt; (é
descartado) assim que ela termina de executar.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para um aprofundamento sobre a call stack e funções, especialmente em cenários
como recursão, recomendo este material:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://otaviomiranda.com.br/2020/funcoes-recursivas-com-python/&quot;&gt;Funções recursivas com Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Agora que entendemos o Enclosing e o que acontece por baixo dos panos, podemos
continuar.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;global e nonlocal - Alterando nomes fora do escopo atual&lt;/h2&gt;
&lt;p&gt;Nas seções anteriores, focamos em como os nomes são &lt;strong&gt;acessados (lidos)&lt;/strong&gt; de um
escopo em outro, um processo que tende a ser mais direto.&lt;/p&gt;
&lt;p&gt;No entanto, a tentativa de &lt;strong&gt;alterar&lt;/strong&gt; um nome que não foi definido no escopo
atual gera uma ambiguidade para o interpretador. Como ele saberia se você quer
criar um &lt;strong&gt;novo nome local&lt;/strong&gt; (com o mesmo rótulo) ou &lt;strong&gt;modificar o nome
existente&lt;/strong&gt; em um escopo externo? Essa é a questão.&lt;/p&gt;
&lt;p&gt;Essa ambiguidade surge justamente pela flexibilidade que o Python oferece: a
capacidade de ter nomes iguais em escopos diferentes sem que um afete o outro, a
menos que você explicitamente peça.&lt;/p&gt;
&lt;p&gt;Mas... antes de mergulharmos nas soluções, uma pausa para as &lt;strong&gt;boas práticas&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Devo alterar nomes fora de seu escopo imediato?&lt;/h3&gt;
&lt;p&gt;A principal função do escopo e do namespace é &lt;strong&gt;proteger nomes de alterações
acidentais&lt;/strong&gt; e garantir o isolamento do código. Fazer alterações intencionais em
nomes que residem fora do escopo imediato vai, em certa medida, contra essa
ideia central.&lt;/p&gt;
&lt;p&gt;Vou listar alguns problemas que essa prática pode gerar (e a lista é bem maior):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dificulta o Debug:&lt;/strong&gt; Rastrear a origem de um bug se torna um pesadelo quando
variáveis são alteradas de forma &amp;quot;mágica&amp;quot; em diferentes partes do código.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dificulta Testes:&lt;/strong&gt; Funções que dependem ou alteram o estado global são mais
complexas de testar isoladamente, exigindo um setup e cleanup maiores.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduz a Legibilidade e Manutenção:&lt;/strong&gt; O código fica menos intuitivo, exigindo
que o leitor &amp;quot;pule&amp;quot; entre escopos para entender o fluxo de dados.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aumenta o Acoplamento:&lt;/strong&gt; Módulos e funções se tornam mais interdependentes,
tornando o sistema mais rígido e difícil de refatorar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Problemas em Concorrência:&lt;/strong&gt; Em ambientes multi-thread ou assíncronos, a
alteração de estado global pode levar a &lt;em&gt;race conditions&lt;/em&gt; e bugs extremamente
difíceis de diagnosticar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contraria Paradigmas Comuns:&lt;/strong&gt; Vai contra princípios de paradigmas como
programação funcional (que preza imutabilidade) e orientação a objetos (que
encapsula estado).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Se você se encontrar na situação de precisar alterar o valor de uma variável
fora do seu escopo imediato, &lt;strong&gt;pare e reavalie a sua solução&lt;/strong&gt;. Quase sempre há
uma forma mais elegante e segura de resolver o problema, como passar os valores
via argumentos ou retornar novos valores.&lt;/p&gt;
&lt;p&gt;Ok, com essa ressalva importante em mente, agora sim vamos entender como
&lt;code&gt;global&lt;/code&gt; e &lt;code&gt;nonlocal&lt;/code&gt; funcionam para alterar variáveis fora do seu escopo local.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;code&gt;global&lt;/code&gt; - alterando nomes no escopo global&lt;/h3&gt;
&lt;p&gt;Vamos analisar o comportamento padrão quando tentamos alterar uma variável
global dentro de uma função. Acompanhe o código e o erro que ele gera:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Escopo global (G)
variavel = &amp;quot;global&amp;quot;

def funcao():
    # Tentativa de usar `variavel` (G)
    print(variavel) # &amp;lt;-- ISSO VAI GERAR O ERRO

    # Criei um novo nome `variavel` no escopo LOCAL (L)
    # Isso fez o nome `variavel` deixar de existir no print acima
    variavel = &amp;quot;local&amp;quot; # &amp;lt;-- ESSE É O MOTIVO DO ERRO

    # Agora, estamos tentando usar a `variavel` LOCAL
    print(variavel)

funcao() # &amp;lt;-- O interpretador não passa daqui
print(variavel)
# A `variavel` global original permanece inalterada
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ao executar &lt;code&gt;funcao()&lt;/code&gt;, você receberá um &lt;code&gt;UnboundLocalError&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UnboundLocalError: cannot access local variable &amp;#39;variavel&amp;#39;
where it is not associated with a value
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O interpretador Python é inteligente: ao ver a linha &lt;code&gt;variavel = &amp;quot;local&amp;quot;&lt;/code&gt; dentro
da função, ele assume que &lt;code&gt;variavel&lt;/code&gt; será uma &lt;strong&gt;variável local&lt;/strong&gt; para
&lt;code&gt;funcao()&lt;/code&gt;. Portanto, quando &lt;code&gt;print(variavel)&lt;/code&gt; é chamado &lt;em&gt;antes&lt;/em&gt; dessa
atribuição local, Python busca por &lt;code&gt;variavel&lt;/code&gt; no escopo local, encontra uma
referência de atribuição futura, mas percebe que ela ainda não tem um valor
associado &lt;strong&gt;dentro daquele escopo local&lt;/strong&gt;. Isso causa o &lt;code&gt;UnboundLocalError&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Para informar ao Python que você deseja modificar a variável &lt;code&gt;variavel&lt;/code&gt; que
reside no escopo &lt;strong&gt;global&lt;/strong&gt;, utilizamos a palavra-chave &lt;code&gt;global&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;variavel = &amp;quot;global&amp;quot;

def funcao():
    # &amp;#39;global variavel&amp;#39; informa ao interpretador
    # que estamos referenciando e modificando a variável global.
    global variavel

    # Agora, podemos usar a variável global ANTES de alterá-la
    print(variavel) # Saída: global

    # Esta atribuição agora modifica a variável global
    variavel = &amp;quot;local&amp;quot;
    print(variavel) # Saída: local

funcao()
print(variavel) # Saída: local (o valor global foi alterado com sucesso)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Com &lt;code&gt;global variavel&lt;/code&gt;, a primeira chamada a &lt;code&gt;print(variavel)&lt;/code&gt; acessa o valor
&amp;quot;global&amp;quot;. Em seguida, a linha &lt;code&gt;variavel = &amp;quot;local&amp;quot;&lt;/code&gt; realmente altera a &lt;code&gt;variavel&lt;/code&gt;
do escopo global.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Importante:&lt;/strong&gt; A declaração &lt;code&gt;global&lt;/code&gt; deve ser feita para &lt;strong&gt;cada variável
global&lt;/strong&gt; que você pretende alterar dentro de &lt;strong&gt;cada função&lt;/strong&gt; onde essa alteração
ocorrerá.&lt;/p&gt;
&lt;p&gt;Finalmente, vale ressaltar que &lt;code&gt;global&lt;/code&gt; &lt;strong&gt;só funciona para nomes no escopo
global&lt;/strong&gt;. Se você precisar alterar um nome em um escopo &lt;code&gt;Enclosing&lt;/code&gt; (não-local),
você precisará usar outra palavra-chave do Python: &lt;code&gt;nonlocal&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;code&gt;nonlocal&lt;/code&gt;: alterando nomes do escopo enclosing&lt;/h3&gt;
&lt;p&gt;A palavra-chave &lt;code&gt;nonlocal&lt;/code&gt; tem uma função similar a &lt;code&gt;global&lt;/code&gt;, mas atua no escopo
&lt;strong&gt;Enclosing&lt;/strong&gt; (o &lt;code&gt;E&lt;/code&gt; da regra LEGB), ou seja, em variáveis de funções externas
que envolvem a função atual, mas que &lt;strong&gt;não são o escopo global&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Isso é particularmente útil em cenários onde uma função interna precisa
modificar uma variável definida na função que a contém.&lt;/p&gt;
&lt;p&gt;Veja o exemplo a seguir com apenas dois níveis de aninhamento:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def soma_mais_um():
    # &amp;#39;numero&amp;#39; está no escopo Enclosing para &amp;#39;incrementa&amp;#39;
    numero = 0

    def incrementa():
        # &amp;#39;nonlocal numero&amp;#39; informa que queremos modificar
        # o &amp;#39;numero&amp;#39; do escopo Enclosing (soma_mais_um)
        nonlocal numero
        numero += 1
        print(f&amp;quot;Número atual (interno): {numero}&amp;quot;)

    # Até aqui, nada alterado
    print(f&amp;quot;Número inicial (externo): {numero}&amp;quot;)

    incrementa() # A alteração ocorre aqui
    print(f&amp;quot;Número final (externo): {numero}&amp;quot;)

soma_mais_um()
# Saída esperada:
# Número inicial (externo): 0
# Número atual (interno): 1
# Número final (externo): 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Neste exemplo, a função &lt;code&gt;incrementa()&lt;/code&gt; consegue modificar a variável &lt;code&gt;numero&lt;/code&gt;
que pertence à &lt;code&gt;soma_mais_um()&lt;/code&gt;, no seu escopo &lt;code&gt;Enclosing&lt;/code&gt;. Se &lt;code&gt;nonlocal numero&lt;/code&gt;
não fosse usado, &lt;code&gt;incrementa()&lt;/code&gt; criaria sua própria variável local &lt;code&gt;numero&lt;/code&gt;, sem
afetar a de fora.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cuidado com a complexidade:&lt;/strong&gt; Embora &lt;code&gt;nonlocal&lt;/code&gt; seja uma ferramenta poderosa e
frequentemente usada em padrões como &lt;em&gt;closures&lt;/em&gt; e &lt;em&gt;decorators&lt;/em&gt;, o aninhamento
excessivo de funções e o uso indiscriminado de &lt;code&gt;nonlocal&lt;/code&gt; podem levar a um
código difícil de ler e depurar. Como já mencionado na seção de boas práticas,
sempre reavalie se há uma forma mais clara de estruturar seu código.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Concluindo - agora você passou de nível 😁&lt;/h3&gt;
&lt;p&gt;E assim chegamos ao fim do nosso texto sobre os conceitos de &lt;strong&gt;escopo&lt;/strong&gt; e
&lt;strong&gt;namespace&lt;/strong&gt; em Python. Embora esse assunto possa parecer muito abstrato, ele
acaba sendo um dos pontos principais para compreender como o Python funciona. Na
verdade, não só Python! Muitas outras linguagens funcionam de maneira muito
similar. Você vai levar esse conhecimento para todas elas.&lt;/p&gt;
&lt;p&gt;Revisamos que:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Um &lt;strong&gt;escopo&lt;/strong&gt; define o local do código onde um nome estará visível e
acessível.&lt;/li&gt;
&lt;li&gt;Um &lt;strong&gt;namespace&lt;/strong&gt; é a estrutura que armazena essas associações entre nomes e
objetos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Escopo&lt;/strong&gt; e &lt;strong&gt;Namespace&lt;/strong&gt; são dois assuntos conectados em Python&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;regra LEGB&lt;/strong&gt; (Local, Enclosing, Global, Built-in) é a ordem pela qual o
Python busca por nomes, sempre de dentro para fora.&lt;/li&gt;
&lt;li&gt;Vimos como as funções &lt;code&gt;globals()&lt;/code&gt;, &lt;code&gt;locals()&lt;/code&gt;, &lt;code&gt;vars()&lt;/code&gt; e &lt;code&gt;dir()&lt;/code&gt; podem nos
ajudar a inspecionar esses &amp;quot;dicionários&amp;quot; de nomes em tempo real.&lt;/li&gt;
&lt;li&gt;E, finalmente, aprendemos sobre as palavras-chave &lt;code&gt;global&lt;/code&gt; e &lt;code&gt;nonlocal&lt;/code&gt;, que
nos permitem, quando necessário e com muita cautela, alterar nomes em escopos
externos ao imediato.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dominar esses conceitos não é apenas sobre &amp;quot;saber o que acontece&amp;quot;, mas sobre
&lt;strong&gt;entender por que seu código se comporta como se comporta&lt;/strong&gt;, evitar erros
comuns como o &lt;code&gt;UnboundLocalError&lt;/code&gt;, e escrever um código mais robusto e
previsível.&lt;/p&gt;
&lt;p&gt;Lembre-se sempre da lição sobre as &lt;strong&gt;boas práticas&lt;/strong&gt;: alterar nomes fora do seu
escopo imediato deve ser a exceção, não a regra. Priorize a clareza, o baixo
acoplamento e a previsibilidade do seu código.&lt;/p&gt;
&lt;p&gt;Com essa base sólida sobre escopo e namespace, você está pronto para mergulhar
em tópicos mais avançados do Python, como &lt;em&gt;closures&lt;/em&gt; e &lt;em&gt;decorators&lt;/em&gt;, que
dependem diretamente desse entendimento. Continue praticando e experimentando!&lt;/p&gt;
</content:encoded></item><item><title>Funções recursivas com Python</title><link>https://otaviomiranda.com.br/2020/funcoes-recursivas-com-python/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2020/funcoes-recursivas-com-python/</guid><description>Funções recursivas com Python (ou qualquer linguagem de programação) são funções que chamam a si mesmas de maneira direta ou indireta.</description><pubDate>Thu, 20 Aug 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Funções recursivas com Python (ou qualquer linguagem de programação) são funções
que chamam a si mesmas de maneira direta ou indireta. Infelizmente, não há
nenhum benefício em termos de desempenho ao usar funções recursivas em
&lt;a href=&quot;/#courses&quot;&gt;Python&lt;/a&gt;, já que laços podem resolver o problema com mais eficiência.
Porém, funções recursivas podem ser mais intuitivas para o programador quando um
problema pode ser dividido em problemas menores de mesmo tipo.&lt;/p&gt;
&lt;p&gt;Considere o conceito de
&lt;a href=&quot;https://www.todamateria.com.br/fatorial/&quot;&gt;fatorial da matemática&lt;/a&gt;: o fatorial
de um número é calculado pela multiplicação desse número por todos os seus
antecessores até chegar ao número 1.&lt;/p&gt;
&lt;p&gt;Esse é um problema extremamente simples para ser resolvido com recursão por dois
fatores:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;É um problema que pode ser dividido em sub-problemas menores e de mesmo tipo
(multiplicar um número pelos seus antecessores)&lt;/li&gt;
&lt;li&gt;Temos um &lt;em&gt;caso-base&lt;/em&gt; para parar a recursão, retornar um valor real e resolver
as equações (quando chegarmos em 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def fatorial(n: int) -&amp;gt; int:
    if n == 1 or n == 0:
        return 1
    return n * fatorial(n - 1)


if __name__ == &amp;quot;__main__&amp;quot;:
    fat5 = fatorial(5)
    print(fat5)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O resultado da execução da função acima será 120.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 5 * 4 * 3 * 2 * 1 = 120
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Observação:&lt;/strong&gt; você poderia escrever uma condição mais concisa eliminando o
&lt;code&gt;or&lt;/code&gt; da expressão com “&lt;code&gt;if n &amp;lt; 2&lt;/code&gt;” ao invés de “&lt;code&gt;if n == 1 or n == 0&lt;/code&gt;“.&lt;/p&gt;
&lt;h2&gt;Caso-base e caso recursivo&lt;/h2&gt;
&lt;p&gt;É muito fácil escrever uma função recursiva incorretamente e cair em uma
recursão infinita. Veja isso no código a seguir:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def recursao_infinita(numero: int = 100) -&amp;gt; int:
    return recursao_infinita(numero - 1)


if __name__ == &amp;quot;__main__&amp;quot;:
    recursao_infinita()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O Python não vai permitir que este código execute infinitamente, então você
deverá ver uma exceção:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# RecursionError: maximum recursion depth exceeded
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isso ocorre porque nunca dissemos para a função quando parar a recursão, mais
especificamente, não adicionamos um
&lt;a href=&quot;https://pt.wikipedia.org/wiki/Caso_base&quot;&gt;caso-base na função&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Toda função recursiva é composta de, pelo menos, duas partes: caso-base e caso
recursivo. O caso-base é quando a função &lt;em&gt;NÃO&lt;/em&gt; chama a si mesma, mas retorna um
valor real; já o caso recursivo, como o próprio nome indica, é onde a
recursividade ocorre (a função chama a si mesma).&lt;/p&gt;
&lt;p&gt;Veja uma nova função recursiva, porém com ambos os casos: caso-base e caso
recursivo.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def contagem_regressiva_recursiva(comeca_em: int = 10, termina_em: int = 0) -&amp;gt; int:
    &amp;quot;&amp;quot;&amp;quot;
    Contagem regressiva iniciando em &amp;#39;comeca_em&amp;#39; e terminando em &amp;#39;termina_em&amp;#39;
    &amp;quot;&amp;quot;&amp;quot;
    print(comeca_em)

    # Caso-base
    if comeca_em &amp;lt;= termina_em:
        # Perceba que aqui um valor real é retornado
        # e não há mais recursão
        return comeca_em

    # Caso recursivo
    # Esse código será executado sempre, até
    # &amp;#39;comeca_em&amp;#39; se tornar menor ou igual a &amp;#39;termina_em&amp;#39;
    return contagem_regressiva_recursiva(comeca_em - 1)


if __name__ == &amp;quot;__main__&amp;quot;:
    contagem_regressiva_recursiva()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Call stack&lt;/h2&gt;
&lt;p&gt;Sempre que invocamos uma função, dados do seu escopo interno (como variáveis e
parâmetros) precisam ser salvos em algum local. Além disso, também precisamos
saber quando a função retorna um valor para que o programa continue a seguir o
seu fluxo. Tudo isso é gerenciado pela &lt;em&gt;Call Stack&lt;/em&gt; (pilha de chamada ou pilha
de execução).&lt;/p&gt;
&lt;h3&gt;Como funciona a Call Stack&lt;/h3&gt;
&lt;p&gt;De forma simples e direta, funciona assim: quando meu programa está em execução
e encontra uma chamada de função, ele pausa temporariamente o que estava fazendo
e vai até o código interno da função para realizar sua execução. Após a
execução, a função precisa saber como retornar o programa para o local onde ele
parou antes da chamada para a execução. Então, após o retorno da função, o
programa sabe como resumir o código partindo exatamente de onde a função
retornou.&lt;/p&gt;
&lt;h3&gt;Exemplo de funcionamento da Call Stack&lt;/h3&gt;
&lt;p&gt;Veja um exemplo no gif abaixo como o fluxo do programa muda quando existe uma
chamada para função:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_image?href=%2F%40fs%2FUsers%2Fluizotavio%2FDesktop%2Ftutoriais_e_cursos%2Fotaviomiranda.com.br%2Fsrc%2Fcontent%2Fposts%2F2020%2Ffuncoes-recursivas-com-python%2Fimgs%2Frecursion-1.gif%3ForigWidth%3D940%26origHeight%3D558%26origFormat%3Dgif&amp;w=940&amp;h=558&amp;f=webp&quot; alt=&quot;Exemplo de recursão&quot;&gt;&lt;/p&gt;
&lt;p&gt;No trecho simples de código acima, existe uma definição de função (linha 1),
definição de variável (linha 5), uma chamada para função (linha 8) e em seguida
um “print” em uma variável (linha 11). Eu marquei breakpoints nas linhas 2, 5, 8
e 11, mas não é essa a ordem de execução. Ao executar o programa, a ordem dos
breakpoints não é a mesma. Ela é alterada para 5, 8, 2 e 11. Isso porque existe
uma chamada para função na linha 8. Então enquanto o interpretador não conferir
o que a função da linha 8 retorna, ele não tem como continuar a execução.&lt;/p&gt;
&lt;p&gt;Além disso, perceba que, na lateral esquerda do gif, a “Call Stack” está aberta.
Nela, existe o que está sendo executado no momento (stack frames). Nesse caso em
específico, começamos com o módulo que está sendo executado (&lt;code&gt;&amp;lt;module&amp;gt;&lt;/code&gt;). Tudo o
que estiver definido dentro do módulo, será exibido na Call Stack dele. Porém,
ao chamar a função, algo novo é adicionado ali, a chamada para função
“&lt;code&gt;funcao&lt;/code&gt;“. Isso ocorre após a execução da linha 8 (chamada da função) e termina
após o retorno da função.&lt;/p&gt;
&lt;h3&gt;Locals&lt;/h3&gt;
&lt;p&gt;Vamos observar o que existe dentro da chamada de função (após a execução da
linha 8).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_image?href=%2F%40fs%2FUsers%2Fluizotavio%2FDesktop%2Ftutoriais_e_cursos%2Fotaviomiranda.com.br%2Fsrc%2Fcontent%2Fposts%2F2020%2Ffuncoes-recursivas-com-python%2Fimgs%2Frec-2.png%3ForigWidth%3D1129%26origHeight%3D509%26origFormat%3Dpng&amp;w=1129&amp;h=509&amp;f=webp&quot; alt=&quot;Exemplo no VS Code&quot;&gt;&lt;/p&gt;
&lt;p&gt;Após a chamada para a função, a execução do módulo é pausada temporariamente até
que o interpretador verifique o que a função retorna. Nesse momento, ela é
adicionada na “Call Stack”, seus dados internos são salvos até que ela decida
retornar um valor. Perceba que o argumento enviado ao parâmetro “&lt;code&gt;nome&lt;/code&gt;” está em
“&lt;code&gt;Variables&lt;/code&gt;” como locals dessa função, essas são suas variáveis locais.&lt;/p&gt;
&lt;p&gt;Assim que o retorno for concluído, a execução do módulo continuará a seguir seu
fluxo e a chamada para a função será eliminada da “Call stack”.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_image?href=%2F%40fs%2FUsers%2Fluizotavio%2FDesktop%2Ftutoriais_e_cursos%2Fotaviomiranda.com.br%2Fsrc%2Fcontent%2Fposts%2F2020%2Ffuncoes-recursivas-com-python%2Fimgs%2Frec-3.png%3ForigWidth%3D1321%26origHeight%3D549%26origFormat%3Dpng&amp;w=1321&amp;h=549&amp;f=webp&quot; alt=&quot;Exemplo 3&quot;&gt;&lt;/p&gt;
&lt;p&gt;Após o retorno da função, ela é eliminada da Call Stack e o módulo pode
prosseguir com sua execução. Aliás, também preciso mencionar que capturei o
valor do seu retorno em uma variável &lt;code&gt;frase&lt;/code&gt; para fazer algo ela posteriormente
(como dar um simples print no terminal).&lt;/p&gt;
&lt;p&gt;Então, podemos resumir que “Call Stack” é exatamente o que sua tradução
descreve, uma &lt;em&gt;pilha de chamadas&lt;/em&gt;. Assim como existe uma pilha de livros na
prateleira, existe uma pilha de chamadas de funções no seu programa. Cada
elemento na call stack contém os dados do momento em que a funções foram
chamadas.&lt;/p&gt;
&lt;h3&gt;Funções dentro de funções&lt;/h3&gt;
&lt;p&gt;Assim como acontece com funções chamadas diretamente dentro de um módulo, também
ocorre com funções chamadas dentro de outras funções. Nesse caso, a pilha de
chamadas fica ainda maior, porque se existir outra chamada para função dentro de
uma função existente, o interpretador também precisará checar o retorno dessa
outra função.&lt;/p&gt;
&lt;p&gt;Considere o mesmo código anterior, porém com uma chamada dentro da função já
criada.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_image?href=%2F%40fs%2FUsers%2Fluizotavio%2FDesktop%2Ftutoriais_e_cursos%2Fotaviomiranda.com.br%2Fsrc%2Fcontent%2Fposts%2F2020%2Ffuncoes-recursivas-com-python%2Fimgs%2Frec4.png%3ForigWidth%3D1429%26origHeight%3D872%26origFormat%3Dpng&amp;w=1429&amp;h=872&amp;f=webp&quot; alt=&quot;Exemplo 4&quot;&gt;&lt;/p&gt;
&lt;p&gt;Agora os passos são um pouco diferentes. Mas, se você me acompanhou até aqui,
não terá dificuldade nenhuma para entender o que ocorreu.&lt;/p&gt;
&lt;p&gt;Lembra que eu te disse que quando há uma chamada de função, o interpretador
precisa verificar o que essa chamada retorna? Então, não é diferente aqui!&lt;/p&gt;
&lt;p&gt;Quando estamos dentro de uma &lt;code&gt;funcao_um&lt;/code&gt; realizando uma chamada para uma
&lt;code&gt;funcao_dois&lt;/code&gt;, o que ocorre é que a &lt;code&gt;funcao_um&lt;/code&gt; precisa pausar sua execução para
saber o que a &lt;code&gt;funcao_dois&lt;/code&gt; retorna. Só após isso, a &lt;code&gt;funcao_um&lt;/code&gt; poderá
continuar sua execução normal.&lt;/p&gt;
&lt;p&gt;Mas não termina aqui, isso tudo é registrado pela “Call Stack” (chamarei de
&lt;em&gt;pilha&lt;/em&gt; daqui em diante). Então, quanto mais chamadas de funções dentro de
funções, mais coisas existem acontecendo na pilha.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/luizomf/83c8f286a83ad6ddb1e7094aedabbef5&quot;&gt;No código da imagem anterior&lt;/a&gt;,
temos uma chamada para função na linha 19, então sabemos que o interpretador vai
conferir o retorno para essa chamada. Porém, ao acessar o código da função, o
interpretador encontra uma nova chamada para função na linha 13, então ele
também vai conferir o que essa outra função retorna.&lt;/p&gt;
&lt;p&gt;Veja na imagem, que a pilha agora tem o seguinte:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nova_funcao&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;funcao_anterior&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;module&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cada elemento na pilha tem suas próprias variáveis locais salvas em memória.&lt;/p&gt;
&lt;p&gt;Portanto, para resolver essa pilha o interpretador precisa voltar resolvendo
todos os retornos de cima para baixo. Ou seja, o resultado final será: retorno
da &lt;code&gt;nova_funcao&lt;/code&gt; + retorno da &lt;code&gt;funcao_anterior&lt;/code&gt; + continua executando o módulo.
Como a &lt;code&gt;funcao_anterior&lt;/code&gt; retorna a &lt;code&gt;nova_funcao&lt;/code&gt;, o retorno final será o que a
&lt;code&gt;nova_funcao&lt;/code&gt; retornar.&lt;/p&gt;
&lt;h2&gt;Funções recursivas com Python&lt;/h2&gt;
&lt;p&gt;As funções recursivas com Python ou com qualquer outra linguagem de programação,
funcionam exatamente como outras funções, porém, ao chamarem a si mesmas dentro
do seu código, a cada nova chamada um novo elemento é adicionado na pilha (Call
Stack, lembra?) contendo as variáveis locais daquele ponto na execução.&lt;/p&gt;
&lt;p&gt;Considere a função fatorial, que te mostrei mais acima nesse post:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def fatorial(n: int) -&amp;gt; int:
  if n == 1 or n == 0:
      return 1
  return n * fatorial(n - 1)


if __name__ == &amp;quot;__main__&amp;quot;:
  fat5 = fatorial(5)
  print(fat5)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A chamada dessa função (iniciando na linha 8) desencadeará mais 4 chamadas para
ela mesma (somando 5 no total) até atingir meu caso-base, que é quando n for
igual a 1.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_image?href=%2F%40fs%2FUsers%2Fluizotavio%2FDesktop%2Ftutoriais_e_cursos%2Fotaviomiranda.com.br%2Fsrc%2Fcontent%2Fposts%2F2020%2Ffuncoes-recursivas-com-python%2Fimgs%2Frec5.png%3ForigWidth%3D1037%26origHeight%3D507%26origFormat%3Dpng&amp;w=1037&amp;h=507&amp;f=webp&quot; alt=&quot;Exemplo 5&quot;&gt;&lt;/p&gt;
&lt;p&gt;Essas chamadas ocorreram na seguinte ordem:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fatorial(5) * fatorial(4) * fatorial(3) * fatorial(2) * fatorial(1)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Isso tudo está na pilha de chamadas e agora o interpretador precisa voltar
resolvendo todas as chamadas de cima para baixo (ou de trás pra frente).&lt;/p&gt;
&lt;p&gt;Dessa maneira (vou mostrar apenas os retornos):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 1 = 1
# 2 * 1 = 2
# 3 * 2 = 6
# 4 * 6 = 24
# 5 * 24 = 120
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O trecho descrito acima é exatamente como a pilha foi resolvida.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/_image?href=%2F%40fs%2FUsers%2Fluizotavio%2FDesktop%2Ftutoriais_e_cursos%2Fotaviomiranda.com.br%2Fsrc%2Fcontent%2Fposts%2F2020%2Ffuncoes-recursivas-com-python%2Fimgs%2F6.png%3ForigWidth%3D1025%26origHeight%3D411%26origFormat%3Dpng&amp;w=1025&amp;h=411&amp;f=webp&quot; alt=&quot;Exemplo 6&quot;&gt;&lt;/p&gt;
&lt;p&gt;Perceba que após retornar todos os valores e a pilha terminar de ser resolvida,
todas as chamadas e suas variáveis locais agora foram eliminadas da memória e
temos apenas o valor de retorno de toda a pilha.&lt;/p&gt;
&lt;p&gt;É assim que as Funções recursivas com Python (ou qualquer outra linguagem de
programação) funcionam. Exatamente como descrito em todo este artigo.&lt;/p&gt;
&lt;h2&gt;Problemas que podemos encontrar com Funções recursivas&lt;/h2&gt;
&lt;p&gt;Como você pôde perceber no texto que seguiu, cada chamada para função dentro de
uma função recursiva é adicionada à pilha (cada elemento na pilha, cujo retorno
ainda não foi finalizado, é chamado de &lt;em&gt;stack frame&lt;/em&gt;). Isso pode ser um problema
quando temos muitas recursões ocorrendo dentro de um programa.&lt;/p&gt;
&lt;p&gt;Imagine que eu peça o fatorial de 998, isso significa que a minha recursão
ocorreria 997 vezes até atingir o caso base (somando 998 chamadas). Isso também
significa que eu teria 998 elementos na minha pilha de chamadas (sem contar
qualquer outra chamada para funções no módulo e a chamada do módulo em si).
Talvez, se o custo de execução da função consumir muita memória, eu poderia
esgotar os recursos do computador facilmente apenas chamando uma função
recursiva.&lt;/p&gt;
&lt;p&gt;Existe uma técnica chamada de &lt;em&gt;Tail Call Optimization&lt;/em&gt; (ou &lt;em&gt;Tail Recursion
Elimination&lt;/em&gt; em casos recursivos) que resolveria este problema. Porém, Guido van
Rossum, criador do Python, foi um contra a adição disso no Python alegando ser
“Não Pythônico”.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I recently posted an entry in my Python History blog on the origins of
Python’s functional features. A side remark about not supporting tail
recursion elimination (TRE) immediately sparked several comments about what a
pity it is that Python doesn’t do this, including links to recent blog entries
by others trying to “prove” that TRE can be added to Python easily. So let me
defend my position (which is that I don’t want TRE in the language). If you
want a short answer, it’s simply unpythonic. Here’s the long answer.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html&quot;&gt;Por Guido van Rossum, em 22/04/2009, em Neopythonic&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Tradução Livre:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Recentemente, publiquei um post em meu blog Python History, sobre as origens
dos recursos funcionais do Python. Uma observação simples sobre não suportar
Tail Recursion Elimination (TRE) imediatamente provocou vários comentários
sobre ser uma pena Python suportar isso, incluindo links para posts recentes
de blogs de outros que tentaram “provar” que TRE pode ser adicionado ao Python
facilmente. Então, deixe-me defender minha posição (que não quero TRE na
linguagem). Se você quer uma resposta curta, simplesmente não é Pythônico.
Aqui está a resposta longa.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html&quot;&gt;Por Guido van Rossum, em 22/04/2009, em Neopythonic&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Se o próprio criador do Python mencionou isso, não iremos discutir isso por aqui
=).&lt;/p&gt;
&lt;h2&gt;RecursionError: maximum recursion depth exceeded in comparison&lt;/h2&gt;
&lt;p&gt;Esse erro apareceu pra você? Isso quer dizer que a pilha de elementos no seu
call stack passou de 1000 (limite padrão em Python).&lt;/p&gt;
&lt;p&gt;Você pode fazer três coisas para resolver este problema:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checar se você realmente queria fazer mais de 1000 chamadas recursivas.
Geralmente, quando criamos funções recursivas incorretamente, são realizadas
mais recursões do que gostaríamos;&lt;/li&gt;
&lt;li&gt;Trocar por um laço &lt;code&gt;for&lt;/code&gt;. Como Python não tem &lt;em&gt;Tail Recursion Elimination&lt;/em&gt;
(pelo menos até o momento da escrita deste post), talvez você poderia
reescrever o código usando &lt;code&gt;for&lt;/code&gt; ou até &lt;code&gt;while&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Por fim, um hack (aumentar o limite de recursão).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para aumentar o limite de recursão, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys
sys.setrecursionlimit(5000)
print(sys.getrecursionlimit())  # 5000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O padrão são 1000 recursões, no trecho acima aumentei para 5000.&lt;/p&gt;
&lt;h2&gt;Algoritmos usando recursão&lt;/h2&gt;
&lt;p&gt;Como vimos anteriormente, o algoritmo mais clichê que é implementado com
recursão, seria o fatorial, que vimos ao longo de todo o post. No entanto, há
uma gama enorme de algoritmos que podem se beneficiar da recursão.&lt;/p&gt;
&lt;p&gt;Eu não pretendo detalhar o que todos eles fazem (talvez fique pra um próximo
post), mas seguem alguns:&lt;/p&gt;
&lt;h3&gt;Sequência Fibonacci&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from functools import lru_cache


@lru_cache
def fibonacci_sequence(n: int) -&amp;gt; int:
    &amp;quot;&amp;quot;&amp;quot;Sequência Fibonacci with memoization&amp;quot;&amp;quot;&amp;quot;
    if n &amp;lt; 1:
        return 0
    if n &amp;lt;= 2:
        return 1
    return fibonacci_sequence(n - 1) + fibonacci_sequence(n - 2)


if __name__ == &amp;quot;__main__&amp;quot;:
    for i in range(1000):
        print(fibonacci_sequence(i))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A
&lt;a href=&quot;https://pt.wikipedia.org/wiki/Sequ%C3%AAncia_de_Fibonacci&quot;&gt;Sequência Fibonacci&lt;/a&gt;
pode se beneficiar da &lt;a href=&quot;https://en.wikipedia.org/wiki/Memoization&quot;&gt;memoization&lt;/a&gt;
(cache) de funções executadas anteriormente. O Python inclui
&lt;a href=&quot;https://docs.python.org/3/library/functools.html#functools.lru_cache&quot;&gt;lru_cache&lt;/a&gt;
no módulo &lt;a href=&quot;https://docs.python.org/3/library/functools.html&quot;&gt;functools&lt;/a&gt; que
serve justamente para isso.&lt;/p&gt;
&lt;p&gt;Adaptação do Livro
&lt;a href=&quot;https://amzn.to/2QXcx8W&quot;&gt;Estruturas de dados e algoritmos com JavaScript (por Loiane Groner)&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Quicksort&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;quot;&amp;quot;&amp;quot;
Quicksort algorithm

&amp;gt;&amp;gt;&amp;gt; print(quicksort(list_of_numbers))
[2, 2, 4, 5, 9, 10, 11, 122, 123, 321]
&amp;gt;&amp;gt;&amp;gt; print(quicksort(list_of_words))
[&amp;#39;Aline&amp;#39;, &amp;#39;Helena&amp;#39;, &amp;#39;João&amp;#39;, &amp;#39;Luiz&amp;#39;, &amp;#39;Maria&amp;#39;, &amp;#39;Zara&amp;#39;]
&amp;gt;&amp;gt;&amp;gt; print(quicksort([&amp;#39;A&amp;#39;]))
[&amp;#39;A&amp;#39;]
&amp;gt;&amp;gt;&amp;gt; print(quicksort([&amp;#39;B&amp;#39;, &amp;#39;A&amp;#39;]))
[&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;]
&amp;gt;&amp;gt;&amp;gt; print(quicksort([]))
[]
&amp;quot;&amp;quot;&amp;quot;

import doctest
from typing import List, TypeVar

TListValue = TypeVar(&amp;#39;TListValue&amp;#39;, int, float, str, bool)


list_of_numbers: List[int] = [10, 9, 5, 2, 11, 4, 2, 123, 321, 122]
list_of_words: List[str] = [&amp;#39;Luiz&amp;#39;, &amp;#39;Maria&amp;#39;, &amp;#39;João&amp;#39;, &amp;#39;Helena&amp;#39;, &amp;#39;Zara&amp;#39;, &amp;#39;Aline&amp;#39;]


def quicksort(a_list: List[TListValue]) -&amp;gt; List[TListValue]:
    if len(a_list) &amp;lt; 2:
        return a_list

    pivot_index = len(a_list) // 2
    pivot = a_list.pop(pivot_index)
    smaller_values: List = [item for item in a_list if item &amp;lt;= pivot]
    higher_values: List = [item for item in a_list if item &amp;gt; pivot]

    return quicksort(smaller_values) + [pivot] + quicksort(higher_values)


if __name__ == &amp;quot;__main__&amp;quot;:
    doctest.testmod(verbose=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adaptação do Livro
&lt;a href=&quot;https://amzn.to/39v5jPV&quot;&gt;Entendendo Algoritmos (por Aditya Bhargava)&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Um código personalizado&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import List


  class Caixa:
      def __init__(self, tem_chave=False) -&amp;gt; None:
          self.tem_chave = tem_chave

      def __repr__(self) -&amp;gt; str:
          return f&amp;#39;Caixa({self.tem_chave})&amp;#39;


  def encontra_chave(caixas: List[Caixa], index: int = 0) -&amp;gt; Caixa:
      if len(caixas) &amp;lt;= index:
          return Caixa()

      caixa = caixas[index]
      print(f&amp;#39;Procurando chave na caixa do índice {index} -&amp;gt; {caixa}&amp;#39;)

      if caixa.tem_chave:
          return caixa

      index += 1
      return encontra_chave(caixas, index)


  if __name__ == &amp;quot;__main__&amp;quot;:
      caixas: List[Caixa] = [
          Caixa(True), Caixa(), Caixa(), Caixa(),
          Caixa(), Caixa(), Caixa(), Caixa(),
          Caixa(), Caixa(), Caixa(), Caixa(),
      ]

      print(encontra_chave(caixas))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Meu código mesmo.&lt;/p&gt;
&lt;h3&gt;Torre de Hanoi&lt;/h3&gt;
&lt;p&gt;Adaptação do Livro
&lt;a href=&quot;https://amzn.to/2Jn18Lc&quot;&gt;O melhor do JavaScript (Por Douglas Crockford)&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Torre de Hanói
def hanoi(disco: int, origem: str, auxiliar: str, destino: str) -&amp;gt; None:
    if disco &amp;lt;= 0:
        return
    hanoi(disco - 1, origem, destino, auxiliar)
    print(f&amp;#39;Movendo disco {disco} de {origem} para {destino}&amp;#39;)
    hanoi(disco - 1, auxiliar, origem, destino)


if __name__ == &amp;quot;__main__&amp;quot;:
    hanoi(3, &amp;#39;Origem&amp;#39;, &amp;#39;Auxiliar&amp;#39;, &amp;#39;Destino&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adaptação do Livro
&lt;a href=&quot;https://amzn.to/2Jn18Lc&quot;&gt;O melhor do JavaScript (Por Douglas Crockford)&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Em vídeo&lt;/h2&gt;
&lt;p&gt;Também criei um vídeo sobre este conteúdo em meu canal do Youtube, segue abaixo:&lt;/p&gt;
&lt;p&gt;Link do vídeo no YouTube:
&lt;a href=&quot;https://youtu.be/0PwFwqiNfAI&quot;&gt;https://youtu.be/0PwFwqiNfAI&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Super resumo do resumo&lt;/h2&gt;
&lt;p&gt;Funções recursivas com Python (ou qualquer linguagem de programação) são funções
que chamam a si mesmas de maneira direta ou indireta.&lt;/p&gt;
</content:encoded></item><item><title>Normalização Unicode em Python</title><link>https://otaviomiranda.com.br/2020/normalizacao-unicode-em-python/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2020/normalizacao-unicode-em-python/</guid><description>Além da normalização Unicode e as formas de normalização NFC, NFD, NFKC e NFKD, você vai aprender tudo o que precisa saber sobre o padrão Unicode em si e Python.</description><pubDate>Thu, 20 Aug 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Além da &lt;strong&gt;normalização Unicode&lt;/strong&gt; e as formas de normalização NFC, NFD, NFKC e
NFKD, você vai aprender tudo o que precisa saber sobre o padrão Unicode em si e
Python.&lt;/p&gt;
&lt;p&gt;Falando sobre a normalização Unicode em si, que provavelmente é o que te trouxe
aqui: normalizar é o ato de transformar strings
(&lt;a href=&quot;https://pt.wikipedia.org/wiki/Unicode&quot;&gt;textos no padrão unicode&lt;/a&gt;) para uma
forma normal onde os caracteres sempre terão a mesma representação binária em
todo o seu programa. Isso facilita a comparação, indexação e ordenação de
strings já que, em um sistema “normalizado”, essas operações são é mais
confiáveis.&lt;/p&gt;
&lt;p&gt;Frequentemente você verá emojis no meio do texto com um código na frete. Eu
espero que você entenda isso ao terminar sua leitura, porque eu não costumo
escrever assim, ok? 🤐 (U+1F910).&lt;/p&gt;
&lt;h2&gt;Um contexto para iniciarmos&lt;/h2&gt;
&lt;p&gt;Vamos iniciar uma jornada longa neste momento. Portanto, vou te deixar um
contexto para discutirmos ao longo de todo o artigo. Porém, não se preocupe se
não entender nada agora. Prometo que vou explicar tudo o que você vai ver a
seguir 🙏 (U+1F64F).&lt;/p&gt;
&lt;h3&gt;Porque precisamos de normalização?&lt;/h3&gt;
&lt;p&gt;No padrão Unicode, caracteres são representados por &lt;strong&gt;code points&lt;/strong&gt; (códigos de
identidade do caractere). Mas, alguns desses caracteres são representados mais
de uma vez para que o padrão Unicode mantenha compatibilidade com outros padrões
que vieram antes dele.&lt;/p&gt;
&lt;p&gt;Por exemplo, a letra “&lt;code&gt;á&lt;/code&gt;” (a com acento agudo), representada por “&lt;code&gt;U+00E1&lt;/code&gt;” em
&lt;a href=&quot;https://en.wikipedia.org/wiki/Code_point&quot;&gt;code point&lt;/a&gt; Unicode, também pode ser
representada por &lt;code&gt;&amp;quot;U+0061&amp;quot;&lt;/code&gt; (a) + “&lt;code&gt;U+0301&lt;/code&gt;” (acento agudo). Na segunda
representação, o acento é algo que é chamado de
&lt;a href=&quot;https://en.wikipedia.org/wiki/Combining_character&quot;&gt;combining character&lt;/a&gt;, porque
combinado ao “&lt;strong&gt;a&lt;/strong&gt;“, forma “&lt;strong&gt;á&lt;/strong&gt;“. No entanto, “&lt;code&gt;U+00E1&lt;/code&gt;” (&lt;strong&gt;á&lt;/strong&gt;) não é igual
a “&lt;code&gt;U+0061&lt;/code&gt;” + “&lt;code&gt;U+0301&lt;/code&gt;” (&lt;strong&gt;á&lt;/strong&gt;) do ponto de vista do seu programa em Python,
mesmo que visualmente o caractere final seja exatamente o mesmo (&lt;strong&gt;á&lt;/strong&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &amp;#39;\u00e1&amp;#39;
&amp;#39;á&amp;#39;
&amp;gt;&amp;gt;&amp;gt; &amp;#39;\u0061\u0301&amp;#39;
&amp;#39;á&amp;#39;
&amp;gt;&amp;gt;&amp;gt; &amp;#39;\u00e1&amp;#39; == &amp;#39;\u0061\u0301&amp;#39;
False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;strong&gt;normalização unicode&lt;/strong&gt; vai resolver este problema mantendo apenas uma forma
normal dos “&lt;strong&gt;á&lt;/strong&gt;s” apresentados acima. Ou “&lt;code&gt;U+00E1&lt;/code&gt;” ou “&lt;code&gt;U+0061&lt;/code&gt;” +
“&lt;code&gt;U+0301&lt;/code&gt;“. No entanto, para entender porque precisamos de normalização unicode
em nosso sistema, precisamos entender o Padrão Unicode como um todo.&lt;/p&gt;
&lt;h2&gt;Unicode – o básico do básico&lt;/h2&gt;
&lt;p&gt;O Unicode é um padrão que permite aos computadores representar e manipular texto
de qualquer sistema de escrita existente utilizando códigos para caracteres
individuais. Cada caractere é mapeado para um código específico chamado de
“&lt;em&gt;code point&lt;/em&gt;“.&lt;/p&gt;
&lt;p&gt;Code Points são representados por um &lt;strong&gt;U+&lt;/strong&gt; seguido de &lt;strong&gt;4 a 6 dígitos
hexadecimais&lt;/strong&gt; (de 0 a 0x10FFFF). Por exemplo: o code point &lt;strong&gt;U+0041&lt;/strong&gt;
representa a letra “&lt;strong&gt;A&lt;/strong&gt;“; o &lt;strong&gt;U+0042&lt;/strong&gt;, a letra “&lt;strong&gt;B&lt;/strong&gt;“, o &lt;strong&gt;U+1F40D&lt;/strong&gt;, uma
cobra verde “🐍”, e assim por diante.&lt;/p&gt;
&lt;p&gt;Para que um sistema possa representar um &lt;strong&gt;code point&lt;/strong&gt; como um caractere
“normal”, ele precisa de um sistema de
&lt;a href=&quot;https://pt.wikipedia.org/wiki/Codifica%C3%A7%C3%A3o_de_caracteres&quot;&gt;codificação de caracteres&lt;/a&gt;.
Este sistema de codificação também é provido pelo padrão Unicode e é responsável
por representar uma sequência de &lt;strong&gt;code points&lt;/strong&gt; (qualquer string no padrão
Unicode) como um conjunto de &lt;strong&gt;code units&lt;/strong&gt; na memória do computador, que então
são mapeados para bytes de 8-bits.&lt;/p&gt;
&lt;p&gt;Apesar do padrão Unicode disponibilizar um conjunto razoavelmente grande de
sistemas de codificação de caracteres, como UTF-7, UTF-8, UTF-EBCDIC, UTF-16 e
UTF32, a codificação mais usada atualmente é a &lt;strong&gt;UTF-8&lt;/strong&gt; (UTF sendo
&lt;a href=&quot;https://pt.wikipedia.org/wiki/UTF-8&quot;&gt;Unicode Transformation Format&lt;/a&gt; e 8 sendo o
número de bits por código). No momento da escrita deste post, o site
&lt;a href=&quot;https://w3techs.com/technologies/history_overview/character_encoding&quot;&gt;W3Techs – Historical trends in the usage statistics of character encodings for websites&lt;/a&gt;,
mostra o padrão UTF-8 sendo usado em &lt;strong&gt;94.7%&lt;/strong&gt; dos sites analisados até
15/04/2020 👀 (U+1F440).&lt;/p&gt;
&lt;p&gt;É uma boa ideia manter seu editor de códigos ou IDE no padrão &lt;strong&gt;UTF-8&lt;/strong&gt; para
digitar seus códigos em Python 🤷‍♂️ (U+1F937).&lt;/p&gt;
&lt;h2&gt;Python e Unicode&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Dica 💡&lt;/strong&gt; (U+1F4A1)&lt;strong&gt;:&lt;/strong&gt; Boa parte do trecho a seguir foi baseada na
&lt;a href=&quot;https://docs.python.org/3.9/howto/unicode.html&quot;&gt;documentação oficial do Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As strings (&lt;code&gt;str&lt;/code&gt;) em Python contém caracteres Unicode desde a versão 3.0. Isso
quer dizer que qualquer valor entre aspas simples, duplas ou triplas são salvas
em Unicode. De fato, o Python 🐍 suporta até mesmo identificadores com
caracteres Unicode.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; atenção = &amp;#39;Um teste unicode&amp;#39;
&amp;gt;&amp;gt;&amp;gt; atenção
&amp;#39;Um teste unicode&amp;#39;
&amp;gt;&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Usando caracteres unicode&lt;/h3&gt;
&lt;p&gt;Também existem várias maneiras para usar caracteres Unicode dentro do código 💻
(U+1F4BB).&lt;/p&gt;
&lt;p&gt;Por exemplo, você pode usar o caractere literal (como de costume), mas também
pode usar o nome do caractere Unicode, um hexadecimal ou até um número decimal
que representaria o caractere usando função
&lt;a href=&quot;https://docs.python.org/3/library/functions.html#chr&quot;&gt;chr&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &amp;#39;A&amp;#39; # Caractere literal A
&amp;#39;A&amp;#39;
&amp;gt;&amp;gt;&amp;gt; chr(0x41) # Usando a função chr com hexadecimal
&amp;#39;A&amp;#39;
&amp;gt;&amp;gt;&amp;gt; chr(65) # Usando a função chr com decimal
&amp;#39;A&amp;#39;
&amp;gt;&amp;gt;&amp;gt; &amp;#39;\N{Latin Capital Letter A}&amp;#39; # Usando o nome do caractere
&amp;#39;A&amp;#39;
&amp;gt;&amp;gt;&amp;gt; &amp;#39;\u0041&amp;#39; # Usando um hexadecimal 16-bit
&amp;#39;A&amp;#39;
&amp;gt;&amp;gt;&amp;gt; &amp;#39;\U00000041&amp;#39; # Usando um hexadecimal 32-bit
&amp;#39;A&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Veja acima, que representei a letra “A” de várias maneiras diferentes.&lt;/p&gt;
&lt;h3&gt;Obtendo valores Unicode dos caracteres&lt;/h3&gt;
&lt;p&gt;Além do que descrevi anteriormente, você também pode fazer o inverso, ou seja,
pegar os valores decimal e hexadecimal que representam o caractere desejado.&lt;/p&gt;
&lt;p&gt;Para isso, você pode usar as funções
&lt;a href=&quot;https://docs.python.org/3/library/functions.html#ord&quot;&gt;ord&lt;/a&gt; e
&lt;a href=&quot;https://docs.python.org/3/library/functions.html#hex&quot;&gt;hex&lt;/a&gt;, dependendo do que
deseja (talvez seja necessário combiná-las).&lt;/p&gt;
&lt;p&gt;Por exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; ord(&amp;#39;A&amp;#39;) # Obtém o valor decimal que representa A
65
&amp;gt;&amp;gt;&amp;gt; hex(ord(&amp;#39;A&amp;#39;)) # Obtém o valor hexadecimal que representa A
&amp;#39;0x41&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Encode (str) e Decode (bytes)&lt;/h3&gt;
&lt;p&gt;É possível converter uma string em bytes ou bytes em string usando os métodos
&lt;code&gt;encode&lt;/code&gt; da &lt;a href=&quot;https://docs.python.org/pt-br/3/library/stdtypes.html#str&quot;&gt;string&lt;/a&gt;
ou &lt;code&gt;decode&lt;/code&gt; de
&lt;a href=&quot;https://docs.python.org/pt-br/3/library/stdtypes.html#bytes&quot;&gt;bytes&lt;/a&gt;. Esses
métodos recebem dois argumentos. O primeiro argumento especifica a codificação
de caracteres desejada (&lt;code&gt;utf-8&lt;/code&gt; ou qualquer outra disponível em
&lt;a href=&quot;https://docs.python.org/3/library/codecs.html#standard-encodings&quot;&gt;Standard Encodings&lt;/a&gt;
– use &lt;code&gt;utf-8&lt;/code&gt; sempre que possível 🕵). O segundo informa como os erros devem ser
tratados (falaremos desse argumento mais adiante neste post).&lt;/p&gt;
&lt;p&gt;Porém, é importante tomar cuidado ao converter uma codificação de caracteres
para outra (exemplo, de &lt;strong&gt;ASCII&lt;/strong&gt; para &lt;strong&gt;UTF-8&lt;/strong&gt;). Pode não ser possível mapear
o código de um caractere para outro em determinadas circunstâncias.&lt;/p&gt;
&lt;p&gt;Veja exemplos a seguir.&lt;/p&gt;
&lt;h4&gt;Encode (str)&lt;/h4&gt;
&lt;p&gt;Suponha que eu queira converter uma string &lt;strong&gt;UTF-8&lt;/strong&gt; para bytes &lt;strong&gt;UTF-8&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Eu posso fazer isso da seguinte maneira:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; meu_nome = &amp;#39;Otávio&amp;#39;
&amp;gt;&amp;gt;&amp;gt; meu_nome_em_bytes = meu_nome.encode(&amp;#39;utf-8&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; meu_nome_em_bytes
b&amp;#39;Ot\xc3\xa1vio&amp;#39;
&amp;gt;&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bytes são representados por &lt;code&gt;b&amp;#39;valores&amp;#39;&lt;/code&gt; em Python.&lt;/p&gt;
&lt;p&gt;De acordo com o código anterior, tudo ocorreu perfeitamente. Isso porque
converti uma string sabendo que ela tinha caracteres &lt;strong&gt;UTF-8&lt;/strong&gt; para bytes em
&lt;strong&gt;UTF-8&lt;/strong&gt;. Mantendo a mesma codificação de caractere, não terei problemas.&lt;/p&gt;
&lt;p&gt;Mas, eu também poderia querer converter minha string &lt;strong&gt;UTF-8&lt;/strong&gt; para
&lt;a href=&quot;https://pt.wikipedia.org/wiki/ASCII&quot;&gt;ASCII&lt;/a&gt; (um outro tipo de codificação de
caracteres). Dependendo do que você estiver convertendo, não terá problemas,
porque o UTF-8 foi feito para ser compatível com outras codificações de
caracteres existentes. Porém, assim que um caractere sair do range suportado
pelo ASCII (de 0 a 127 em base 10), terei um erro:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; meu_nome_em_bytes = meu_nome.encode(&amp;#39;ascii&amp;#39;)
Traceback (most recent call last):
...
UnicodeEncodeError: &amp;#39;ascii&amp;#39; codec can&amp;#39;t encode character &amp;#39;\xe1&amp;#39; in position 2: ordinal not in range(128)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Veja que no erro é descrito o problema. Não foi possível codificar o caractere
&lt;code&gt;&amp;#39;\xe1&amp;#39;&lt;/code&gt; na posição 2.&lt;/p&gt;
&lt;p&gt;Lembra que te mostrei como exibir o caractere utilizando a função &lt;code&gt;chr&lt;/code&gt;? Então,
o caractere &lt;code&gt;\xe1&lt;/code&gt; é o mesmo que &lt;code&gt;chr(0xe1)&lt;/code&gt;, ou “&lt;strong&gt;á&lt;/strong&gt;” de “&lt;strong&gt;Otávio&lt;/strong&gt;“. Esse
caractere não faz parte da tabela &lt;strong&gt;ascii&lt;/strong&gt;, portanto o erro.&lt;/p&gt;
&lt;p&gt;Logo mais veremos o segundo argumento e você poderá selecionar o que acontece
quando um erro assim ocorrer.&lt;/p&gt;
&lt;h4&gt;Decode (bytes)&lt;/h4&gt;
&lt;p&gt;Se o método &lt;code&gt;encode&lt;/code&gt; é usado para converter string em bytes, o método &lt;code&gt;decode&lt;/code&gt; é
usado para fazer o inverso disso, converter bytes em strings.&lt;/p&gt;
&lt;p&gt;Por exemplo, suponha que eu tenha apenas os bytes e queira resgatar seu valor
para uma string &lt;strong&gt;UTF-8&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; meu_nome_em_bytes = b&amp;#39;Ot\xc3\xa1vio&amp;#39;
&amp;gt;&amp;gt;&amp;gt; meu_nome_str = meu_nome_em_bytes.decode(&amp;#39;utf-8&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; meu_nome_str
&amp;#39;Otávio&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Novamente, aqui ocorreu tudo perfeitamente, porque eu sabia que a codificação
dos bytes era &lt;strong&gt;UTF-8&lt;/strong&gt; e eu os decodifiquei adequadamente. Então tudo ocorreu
como esperado. Mas, e se eu quisesse decodificar esses bytes em &lt;strong&gt;ASCII&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;Inicialmente, não tem como (😒 – U+1F612)!&lt;/p&gt;
&lt;p&gt;Para isso você precisa saber a codificação de caracteres usada na codificação
para decodificar.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dica:&lt;/strong&gt; &lt;a href=&quot;https://pypi.org/project/chardet/&quot;&gt;chardet&lt;/a&gt; ajuda a tentar descobrir
a codificação de caracteres usada em bytes que você não saberia de outra forma.
Mas a maneira mais simples continua sendo: “pergunte ao dono dos bytes qual a
codificação”. &lt;strong&gt;UTF-8&lt;/strong&gt; é um bom chute inicial.&lt;/p&gt;
&lt;h4&gt;Erros de codificação em decode (bytes)&lt;/h4&gt;
&lt;p&gt;Imagine que eu não saiba a codificação usada em determinados bytes e tente
chutar &lt;strong&gt;ascii&lt;/strong&gt;, por exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; meu_nome_str = meu_nome_em_bytes.decode(&amp;#39;ascii&amp;#39;) # Otávio (em UTF-8)
Traceback (most recent call last):
...
UnicodeDecodeError: &amp;#39;ascii&amp;#39; codec can&amp;#39;t decode byte 0xc3 in position 2: ordinal not in range(128)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que aqui, além de um erro de &lt;code&gt;UnicodeDecodeError&lt;/code&gt;, eu também tenho um
&lt;strong&gt;code point&lt;/strong&gt; incorreto. Se você observar, &lt;code&gt;0xc3&lt;/code&gt; aponta para “&lt;strong&gt;Ã&lt;/strong&gt;” , que nem
existia na minha string anterior.&lt;/p&gt;
&lt;p&gt;O motivo disso é simples, “&lt;strong&gt;á&lt;/strong&gt;” da minha string anterior usa dois bytes em
&lt;strong&gt;UTF-8&lt;/strong&gt; e o codec &lt;strong&gt;ASCII&lt;/strong&gt; não sabe disso. Então ele tenta decodificar byte
por byte e gera esse erro estranho. Se isso passasse sem erros, o resultado
seria um monte de caracteres que não fariam sentido algum. Por exemplo, se eu
usasse o codec &lt;a href=&quot;https://pt.wikipedia.org/wiki/ISO/IEC_8859-1&quot;&gt;latin1&lt;/a&gt; ao invés
de &lt;strong&gt;ascii&lt;/strong&gt;, o resultado seria &lt;strong&gt;‘OtÃ¡vio’&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Para contornar essa situação, eu preciso saber qual a codificação de caracteres
foi usada para codificar os bytes anteriormente. Sabendo disso, eu deveria
decodificar esses bytes usando a codificação correta antes de fazer qualquer
outra coisa.&lt;/p&gt;
&lt;p&gt;Depois de decodificar, eu poderia codificar novamente usando o &lt;strong&gt;codec&lt;/strong&gt; que eu
preferir, contando que ele suporte os caracteres que eu estiver usando (usa
sempre &lt;strong&gt;UTF-8&lt;/strong&gt;, pelo amor de Deus 😬 – U+1F62C).&lt;/p&gt;
&lt;p&gt;Por exemplo, se eu quero codificar de UTF-8 para
&lt;a href=&quot;https://pt.wikipedia.org/wiki/ISO/IEC_8859-1&quot;&gt;ISO-8859-1 (Latin1)&lt;/a&gt;, que é algo
que vejo muito aqui no Brasil, principalmente em sistemas públicos, poderia
fazer assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; meu_nome_str = meu_nome_em_bytes.decode(&amp;#39;utf8&amp;#39;) # Otávio
&amp;gt;&amp;gt;&amp;gt; meu_nome_em_bytes_latin = meu_nome_str.encode(&amp;#39;latin_1&amp;#39;) # Otávio
&amp;gt;&amp;gt;&amp;gt; meu_nome_em_bytes_latin
b&amp;#39;Ot\xe1vio&amp;#39; # Otávio
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Basicamente, isso foi uma conversão de &lt;strong&gt;UTF-8&lt;/strong&gt; para &lt;strong&gt;latin_1&lt;/strong&gt;. Tenha noção
de que sempre que essas conversões ocorrem, uma codificação de caracteres deve
suportar a outra. O Unicode foi criado para ser compatível com todas as
codificações existentes. No entanto, apenas no sentido de “qualquer codificação”
convertido para “Unicode”. Você pode ter problemas ao converter no sentido
contrário, de Unicode para “qualquer codificação”, porque o padrão Unicode
suporta muito mais caracteres do que qualquer outra codificação de caracteres
que você quiser utilizar.&lt;/p&gt;
&lt;p&gt;No nosso exemplo, tudo funcionou perfeitamente porque todas as letras de
“&lt;strong&gt;Otávio&lt;/strong&gt;” estão presentes na tabela
&lt;a href=&quot;https://pt.wikipedia.org/wiki/ISO/IEC_8859-1&quot;&gt;ISO-8859-1 (Latin1)&lt;/a&gt;, caso
contrário ocorreriam erros também.&lt;/p&gt;
&lt;h4&gt;Dicas&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Dica número 1:&lt;/strong&gt; Sempre que possível use a codificação de caracteres
&lt;strong&gt;UTF-8&lt;/strong&gt;, na grande maioria das vezes isso é possível 😅 (U+1F605).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mais dicas:&lt;/strong&gt; se você precisa detectar a codificação de caracteres de algo que
não tem a mínima ideia como foi codificado, use
&lt;a href=&quot;https://pypi.org/project/chardet/&quot;&gt;chardet.detect&lt;/a&gt;. Ele não vai acertar em 100%
dos casos, mas já me salvou de muitas enrascadas; Se você precisa saber quais
codecs de codificação o Python suporta, veja
&lt;a href=&quot;https://docs.python.org/3.9/library/codecs.html#standard-encodings&quot;&gt;Python Specific Encodings&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Erros em encode e decode&lt;/h4&gt;
&lt;p&gt;Como te contei anteriormente, &lt;code&gt;encode&lt;/code&gt; e &lt;code&gt;decode&lt;/code&gt; recebem um argumento com a
codificação desejada e outro especificando como os erros devem ser tratados.
Para o segundo argumento você pode enviar os seguintes valores:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;#39;strict&amp;#39;&lt;/code&gt; – É o padrão. O que levanta uma exceção de
&lt;a href=&quot;https://docs.python.org/3/library/exceptions.html#UnicodeEncodeError&quot;&gt;UnicodeEncodeError&lt;/a&gt;
ou
&lt;a href=&quot;https://docs.python.org/3/library/exceptions.html#UnicodeDecodeError&quot;&gt;UnicodeDecodeError&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;#39;replace&amp;#39;&lt;/code&gt; – Usa o caractere U+FFFD (REPLACEMENT CHARACTER) no lugar do
caractere que não pôde ser convertido;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;#39;ignore&amp;#39;&lt;/code&gt; – Simplesmente pula o caractere que não pode ser convertido;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;#39;backslashreplace&amp;#39;&lt;/code&gt; – que insere uma sequência &lt;code&gt;\xNN&lt;/code&gt; no lugar do caractere
que não pode ser convertido;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;#39;xmlcharrefreplace&amp;#39;&lt;/code&gt; – que insere uma referência para um caractere
&lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references&quot;&gt;XML&lt;/a&gt;
(isso só funciona com &lt;code&gt;encode&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &amp;#39;Otávio&amp;#39;.encode(&amp;#39;utf-8&amp;#39;)
b&amp;#39;Ot\xc3\xa1vio&amp;#39;
&amp;gt;&amp;gt;&amp;gt; b&amp;#39;Ot\xc3\xa1vio&amp;#39;.decode(&amp;#39;ascii&amp;#39;, &amp;#39;ignore&amp;#39;) # aqui usei ignore
&amp;#39;Otvio&amp;#39; # e perdi o &amp;quot;á&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Normalização Unicode em Python&lt;/h2&gt;
&lt;p&gt;Nós demos várias voltas até chegar aqui, mas é importante conhecer o que você
está fazendo, não é mesmo (😏 – U+1F60F)?&lt;/p&gt;
&lt;p&gt;Então, só pra recapitular tudo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Você conheceu os code points do Unicode;&lt;/li&gt;
&lt;li&gt;Também sabe que Unicode foi feito pensando em compatibilidade com padrões já
existente (ascii, latin, etc). Vamos voltar nesse assunto já já;&lt;/li&gt;
&lt;li&gt;Viu que UTF-8 é uma das codificações de caracteres do Unicode;&lt;/li&gt;
&lt;li&gt;Está ciente que UTF-8 é, de longe, uma das codificações mais usadas no mundo;&lt;/li&gt;
&lt;li&gt;E deveria estar usando UTF-8 nos seus código (é muito provável que já esteja).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Uma coisa que eu ainda não te falei é sobre a normalização e o porquê isso
existe.&lt;/p&gt;
&lt;p&gt;Na verdade, todas as voltas foram para fazer você entender o que é Unicode de
verdade. Se já sabia, melhor ainda!&lt;/p&gt;
&lt;p&gt;Então agora podemos ter uma conversa mais “complexa”.&lt;/p&gt;
&lt;h3&gt;Unicode e outros padrões&lt;/h3&gt;
&lt;p&gt;Lembra que lá no comecinho ☝ (U+261D) te falei que eu poderia escrever a letra
“&lt;strong&gt;á&lt;/strong&gt;” de maneiras diferentes em Unicode?&lt;/p&gt;
&lt;p&gt;Só pra te lembrar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &amp;#39;\u00e1&amp;#39;
&amp;#39;á&amp;#39;
&amp;gt;&amp;gt;&amp;gt; &amp;#39;\u0061\u0301&amp;#39;
&amp;#39;á&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Este pode não ser um problema no seu programa caso não tenha tido a necessidade
de comparar esses dois “&lt;strong&gt;á&lt;/strong&gt;s”. No entanto, em algum momento este problema pode
aparecer e você vai demorar um tempo considerável até descobrir que isso está
relacionado com a falta de normalização de caracteres. Nós buscamos recursos de
várias fontes externas ao nosso programa e não sabemos qual forma normal
utilizaram no sistema deles, e essa bomba pode explodir na sua mão.&lt;/p&gt;
&lt;p&gt;O Unicode foi criado pensando em compatibilidade, por isso alguns caracteres
aparecem mais de uma vez. Por exemplo, se você olhar na tabela
&lt;a href=&quot;https://pt.wikipedia.org/wiki/ASCII&quot;&gt;ASCII&lt;/a&gt;, vai ver que a letra “&lt;strong&gt;A&lt;/strong&gt;” é
representada pelo mesmo hexadecimal que o Unicode (&lt;strong&gt;41&lt;/strong&gt; vs &lt;strong&gt;U+0041&lt;/strong&gt;). Se
olhar na tabela &lt;a href=&quot;https://pt.wikipedia.org/wiki/ISO/IEC_8859-1&quot;&gt;ISO/IEC 8859-1&lt;/a&gt;,
vai ver que a letra “&lt;strong&gt;á&lt;/strong&gt;” é representada exatamente pelo mesmo hexadecimal que
o Unicode (&lt;strong&gt;00E1&lt;/strong&gt; vs &lt;strong&gt;U+00E1&lt;/strong&gt;). Isso quer dizer que o range de 0 a 127
(base 10) no Unicode é compatível com &lt;strong&gt;ASCII&lt;/strong&gt;, o range de 0 a 255 (base 10) no
Unicode é compatível com &lt;strong&gt;ISO/IEC 8859-1&lt;/strong&gt; (ou latin1) e assim por diante. O
Unicode tenta ser compatível com todos os padrões existentes.&lt;/p&gt;
&lt;p&gt;Isso vai acabar nos levando a um problema, vai vendo 🤨 (U+1F928)!&lt;/p&gt;
&lt;h3&gt;Caracteres pré-compostos e caracteres combinados&lt;/h3&gt;
&lt;p&gt;Pelo motivo que te expliquei anteriormente, existem caracteres que são chamados
de &lt;strong&gt;pré-compostos&lt;/strong&gt;, como: &lt;strong&gt;á&lt;/strong&gt;, &lt;strong&gt;é&lt;/strong&gt;, &lt;strong&gt;À&lt;/strong&gt;, &lt;strong&gt;Á&lt;/strong&gt; e vários outros. Esses
caracteres pré-compostos existem para manter compatibilidade com padrões que já
existiam antes do Unicode.&lt;/p&gt;
&lt;p&gt;Por outro lado, o Unicode também dispõe de um sistema de combinação para
estender o repositório de caractere suportados, e isso é genial (🥰 – U+1F970)!&lt;/p&gt;
&lt;p&gt;Pensa comigo 🤓 (U+1F913), se eu posso ter um “&lt;strong&gt;a&lt;/strong&gt;” e um “&lt;strong&gt;acento agudo&lt;/strong&gt;” em
dois caracteres diferentes, não seria inteligente permitir que o &lt;strong&gt;acento
agudo&lt;/strong&gt; pudesse ser combinado com esse “&lt;strong&gt;a&lt;/strong&gt;” ou com qualquer outro caractere
formando um caractere único? Também acho!&lt;/p&gt;
&lt;p&gt;É exatamente esse o mecanismo que foi usado no Unicode. Ao invés de ter um
&lt;strong&gt;code point&lt;/strong&gt; único para cada caractere do planeta, fizeram um sistema de
combinação de caracteres para formar esses símbolos loucos que a gente acaba
usando e nem percebe.&lt;/p&gt;
&lt;p&gt;Esses caracteres que podem ser combinados com outros caracteres são chamados de
&lt;a href=&quot;https://en.wikipedia.org/wiki/Combining_character&quot;&gt;combining character&lt;/a&gt; e
existem muitos deles.&lt;/p&gt;
&lt;p&gt;Mas, como nem tudo são flores (🥀 – U+1F940), isso gerou o problema de ter mais
de um caractere representando a mesma coisa. Aquela probleminha que te mostrei
no início, sobre os “&lt;strong&gt;á&lt;/strong&gt;s”. Te falei que ia dar problema, não falei 😁
(U+1F601)?&lt;/p&gt;
&lt;p&gt;É aqui que entra a normalização e uma outra coisa que é chamada de
&lt;strong&gt;equivalência canônica&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Equivalência canônica&lt;/h3&gt;
&lt;p&gt;Como os criadores do Unicode são bem inteligentes 🧐 (U+1F9D0), eles criaram
algo chamado de “&lt;strong&gt;equivalência canônica&lt;/strong&gt;“. Isso é só uma maneira bonita de
falar “esses dois caracteres são iguais”. Então, na equivalência canônica,
&lt;strong&gt;U+00E1&lt;/strong&gt; (&lt;strong&gt;á&lt;/strong&gt; pré-composto) é igual a &lt;strong&gt;U+0041 + U+0301&lt;/strong&gt; (&lt;strong&gt;a&lt;/strong&gt; com
&lt;strong&gt;acento agudo&lt;/strong&gt; combinados). Isso acontece com todos os caracteres acentuados e
mais outros milhares de caracteres que podem ser combinados em vários idiomas
diferentes.&lt;/p&gt;
&lt;p&gt;Sabendo disso, você poder utilizar mais de uma forma normal em todo o seu
programa: &lt;strong&gt;NFC&lt;/strong&gt; e &lt;strong&gt;NFD&lt;/strong&gt; (tem mais duas, mas é questão de compatibilidade,
segura aí que a gente já fala sobre isso).&lt;/p&gt;
&lt;h3&gt;NFC – Normalization Form Canonical Composition&lt;/h3&gt;
&lt;p&gt;Esse tipo de normalização Unicode visa &lt;strong&gt;manter os caracteres pré-compostos&lt;/strong&gt; no
seu programa (sem a separação de caractere + caractere combinado). Tais
caracteres são unidos por equivalência canônica.&lt;/p&gt;
&lt;p&gt;Lembra dos &lt;strong&gt;á&lt;/strong&gt;s? Aqui eles serão iguais, porque somente o &lt;strong&gt;U+00E1&lt;/strong&gt; (&lt;strong&gt;á&lt;/strong&gt;
pré-composto) será mantido, os caracteres separados serão convertidos em
pré-compostos. Por exemplo, &lt;strong&gt;U+0061 + U+0301&lt;/strong&gt; (&lt;strong&gt;a&lt;/strong&gt; com &lt;strong&gt;acento agudo&lt;/strong&gt;
combinados) se tornaria sempre &lt;strong&gt;U+00E1&lt;/strong&gt; (&lt;strong&gt;á&lt;/strong&gt; pré-composto).&lt;/p&gt;
&lt;p&gt;Por exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; import unicodedata
&amp;gt;&amp;gt;&amp;gt; nome = &amp;#39;Ot\u0061\u0301vio&amp;#39;
&amp;gt;&amp;gt;&amp;gt; nome_normalizado = unicodedata.normalize(&amp;#39;NFC&amp;#39;, nome)
&amp;gt;&amp;gt;&amp;gt; [&amp;#39;U+&amp;#39; + hex(ord(letra))[2:].zfill(4).upper() for letra in nome_normalizado]
[&amp;#39;U+004F&amp;#39;, &amp;#39;U+0074&amp;#39;, &amp;#39;U+00E1&amp;#39;, &amp;#39;U+0076&amp;#39;, &amp;#39;U+0069&amp;#39;, &amp;#39;U+006F&amp;#39;]
# O         t         á         v	      i         o
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Da pra perceber ali que a letra “&lt;strong&gt;á&lt;/strong&gt;” do meu nome, sempre será mantida como
&lt;strong&gt;U+00E1&lt;/strong&gt; com esse tipo de normalização. Mesmo eu dizendo explicitamente que
quero a string &lt;code&gt;&amp;#39;Ot\u0061\u0301vio&amp;#39;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Resumidamente: isso não fará nada com caracteres pré-compostos, mas combinará
caracteres equivalentes que estiverem separados em sua forma pré-composta por
equivalência canônica.&lt;/p&gt;
&lt;h3&gt;NFD – Normalization Form Canonical Decomposition&lt;/h3&gt;
&lt;p&gt;Esse tipo de normalização unicode visa manter os caracteres separados (com a
separação entre caractere e caractere combinado). Os caracteres serão separados
por equivalência canônica.&lt;/p&gt;
&lt;p&gt;Aqui os “&lt;strong&gt;á&lt;/strong&gt;s” serão iguais, porque somente os caracteres &lt;strong&gt;U+0061 + U+0301&lt;/strong&gt;
(&lt;strong&gt;a&lt;/strong&gt; com &lt;strong&gt;acento agudo&lt;/strong&gt; combinados) serão mantidos. Os “&lt;strong&gt;á&lt;/strong&gt;s”
pré-compostos (&lt;strong&gt;U+00E1&lt;/strong&gt;) serão convertidos em &lt;strong&gt;U+0061 + U+0301&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Por exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; import unicodedata
&amp;gt;&amp;gt;&amp;gt; nome = &amp;#39;Ot\u00e1vio&amp;#39;
&amp;gt;&amp;gt;&amp;gt; nome_normalizado = unicodedata.normalize(&amp;#39;NFD&amp;#39;, nome)
&amp;gt;&amp;gt;&amp;gt; [&amp;#39;U+&amp;#39; + hex(ord(letra))[2:].zfill(4).upper() for letra in nome_normalizado]
[&amp;#39;U+004F&amp;#39;, &amp;#39;U+0074&amp;#39;, &amp;#39;U+0061&amp;#39;, &amp;#39;U+0301&amp;#39;, &amp;#39;U+0076&amp;#39;, &amp;#39;U+0069&amp;#39;, &amp;#39;U+006F&amp;#39;]
# O         t         a         acento    v         i         o
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que agora eu consegui manter ambos os caracteres, tanto o “&lt;strong&gt;a&lt;/strong&gt;” quanto
o “&lt;strong&gt;acento agudo combinado&lt;/strong&gt;“. Mesmo especificando que eu queria a string
&lt;code&gt;&amp;#39;Ot\u00e1vio&amp;#39;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Resumidamente: isso não fará nada com aqueles dois caracteres combinados, porém
vai separar caracteres pré-compostos para sua forma combinada por equivalência
canônica. Basicamente é &lt;strong&gt;U+00E1&lt;/strong&gt; (&lt;strong&gt;á&lt;/strong&gt; pré-composto) se transformando em
&lt;strong&gt;U+0061 + U+0301&lt;/strong&gt; (&lt;strong&gt;a&lt;/strong&gt; com &lt;strong&gt;acento agudo&lt;/strong&gt; combinados).&lt;/p&gt;
&lt;h2&gt;NFKC e NFKD – Normalization Form Compatibility Composition/Decomposition&lt;/h2&gt;
&lt;p&gt;Para complicar um pouquinho mais a sua vida na normalização unicode, também
existem caracteres que &lt;strong&gt;não&lt;/strong&gt; são definidos por &lt;strong&gt;equivalência canônica&lt;/strong&gt;, mas
por &lt;strong&gt;compatibilidade&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Por exemplo, em alguns contextos, o símbolo &lt;strong&gt;TM&lt;/strong&gt; pode ter o mesmo significado
que ™ (TRADE MARK SIGN, U+2122). Nesse caso, ambos TM e ™ são definidos como
caracteres compatíveis, mas que NÃO TEM &lt;strong&gt;equivalência canônica&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Isso quer dizer que nem &lt;strong&gt;NFC&lt;/strong&gt;, nem &lt;strong&gt;NFD&lt;/strong&gt; vão normalizar esses dois valores.&lt;/p&gt;
&lt;p&gt;E só pra deixar claro isso pra você, caso ainda não tenha ficado:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NF – Normalization Form (formato de normalização);&lt;/li&gt;
&lt;li&gt;C – Composition (composição – une);&lt;/li&gt;
&lt;li&gt;D – Decomposition (decomposição – separa);&lt;/li&gt;
&lt;li&gt;K – Compatibility (separa por compatibilidade).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Agora que vem a pergunta de 1 milhão de dólares: qual a forma normal entre TM e
™? Depende! Em qual contexto?&lt;/p&gt;
&lt;p&gt;Vou te dar um exemplo: nós sabemos que seres humanos tem uma preguiça danada de
digitar as coisas corretamente, certo? Imagine que a minha marca fosse &lt;strong&gt;OM™&lt;/strong&gt; e
eu quisesse que no meu sistema de busca, essa marca fosse encontrada. Você acha
que as pessoas digitariam &lt;strong&gt;OMTM&lt;/strong&gt; ou &lt;strong&gt;OM™&lt;/strong&gt;? Eu acho que OMTM (caso não
encontrassem antes apenas digitando OM). Mas podemos garantir as duas com a
normalização.&lt;/p&gt;
&lt;p&gt;Então nesse caso, eu usaria a compatibilidade para transformar &lt;strong&gt;™&lt;/strong&gt; em TM
apenas para realizar uma comparação. Por exemplo, meu texto na base de dados
seria normalizado temporariamente com NFKD e o texto enviado pelo usuário também
seria normalizado para NFKD. Assim eu consigo encontrar &lt;strong&gt;OMTM&lt;/strong&gt; ou &lt;strong&gt;OM™&lt;/strong&gt;
independente de como isso foi digitado pelo usuário.&lt;/p&gt;
&lt;p&gt;Para fazer a normalização unicode de &lt;strong&gt;™&lt;/strong&gt; para TM, você vai precisar usar
NF&lt;strong&gt;K&lt;/strong&gt;(C ou D):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; import unicodedata
&amp;gt;&amp;gt;&amp;gt; nome = &amp;#39;OM™&amp;#39;
&amp;gt;&amp;gt;&amp;gt; nome_normalizado = unicodedata.normalize(&amp;#39;NFKC&amp;#39;, nome)
&amp;gt;&amp;gt;&amp;gt; [&amp;#39;U+&amp;#39; + hex(ord(letra))[2:].zfill(4).upper() for letra in nome_normalizado]
[&amp;#39;U+004F&amp;#39;, &amp;#39;U+004D&amp;#39;, &amp;#39;U+0054&amp;#39;, &amp;#39;U+004D&amp;#39;]
# O         M         T         M
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que “&lt;strong&gt;C&lt;/strong&gt;” e “&lt;strong&gt;D&lt;/strong&gt;” aqui vão fazer o mesmo trabalho descrito
anteriormente, mas o “&lt;strong&gt;K&lt;/strong&gt;” vai trabalhar na compatibilidade que te falei
antes.&lt;/p&gt;
&lt;p&gt;Então só pra resumir: O &lt;strong&gt;K&lt;/strong&gt; significa &lt;strong&gt;compatibility&lt;/strong&gt; e vai converter
valores que estariam em apenas um &lt;strong&gt;code point&lt;/strong&gt; Unicode em caracteres que
seriam compatíveis (de acordo com as regras deles, que eu não sei quais são).
Esses caracteres devem se comportar da mesma maneira em pesquisa, comparação,
ordenação e indexação, mas podem mudar o significado e também podem parecer
visualmente diferentes em vários contextos. Como no nosso exemplo, &lt;strong&gt;OMTM&lt;/strong&gt; ou
&lt;strong&gt;OM™&lt;/strong&gt; deveria retornar os mesmos valores no meu sistema de pesquisa ou
comparação, mas eles são bem diferentes visualmente.&lt;/p&gt;
&lt;h3&gt;Um ponto de atenção para K&lt;/h3&gt;
&lt;p&gt;Tenha em mente que a partir do momento que eu separei os valores por
compatibilidade, não consigo mais uni-los novamente. Por exemplo, se eu
normalizar com &lt;strong&gt;K&lt;/strong&gt; (NFKC ou NFKD) o valor ™ (TRADE MARK SIGN, U+2122), vou
obter TM (como vimos). Porém TM não voltará a ser ™.&lt;/p&gt;
&lt;p&gt;Por este motivo, é super importante que você &lt;strong&gt;não&lt;/strong&gt; salve os valores
permanentemente utilizando &lt;strong&gt;K&lt;/strong&gt;. Você deve normalizar temporariamente no
momento que precisar realizar alguma comparação e eliminar esse valor após
terminar o que estava fazendo. Salve os valores como eles realmente são.&lt;/p&gt;
&lt;h2&gt;Usando chardet para detectar a codificação de caracteres&lt;/h2&gt;
&lt;p&gt;Na grande maioria das vezes que nosso sistema gerar algum problema de
codificação de caracteres, esse problema virá de algum recurso externo.
Portanto, para simular isso, suponha que eu tenha um arquivo em &lt;strong&gt;ISO-8859-1&lt;/strong&gt;
(latin1). Qualquer editor de textos decente vai te permitir criar o mesmo texto
com a mesma codificação de caracteres. Por exemplo, o
&lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;![Exemplo de codificação no VS Code](./imgs/python-1.png)&lt;/p&gt;
&lt;p&gt;Nós sabemos qual a codificação de caracteres foi usada neste arquivo (latin1),
mas finja que não. Vamos carregar esse arquivo pelo Python usando “&lt;code&gt;rb&lt;/code&gt;” (read
bytes) e solicitar ao &lt;a href=&quot;https://pypi.org/project/chardet/&quot;&gt;chardet&lt;/a&gt; para detectar
a codificação de caracteres.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; você precisa instalar o chardet com “&lt;code&gt;pip install chardet&lt;/code&gt;“.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; import chardet
&amp;gt;&amp;gt;&amp;gt; with open(&amp;#39;text.txt&amp;#39;, &amp;#39;rb&amp;#39;) as file:
...     raw_content = file.read()
...     encoding = chardet.detect(raw_content)
...     encoding[&amp;quot;encoding&amp;quot;]
...
&amp;#39;ISO-8859-9&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ele detectou como ‘ISO-8859-9’, isso seria &lt;strong&gt;latin5&lt;/strong&gt; e não &lt;strong&gt;latin1&lt;/strong&gt;, mas
lembra que te falei que ele não iria acertar 100% das vezes, certo? Bom, vamos
tentar converter esse arquivo de ISO-8859-9 (mesmo não sendo a codificação exata
do arquivo) para UTF-8 e ver o que ocorre.&lt;/p&gt;
&lt;p&gt;Vamos abrir o arquivo novamente, decodificar com a codificação que o &lt;strong&gt;chardet&lt;/strong&gt;
quiser (no caso ISO-8859-9, latin5), depois vamos abrir um novo arquivo com
&lt;code&gt;&amp;#39;wb&amp;#39;&lt;/code&gt; e salvar como UTF-8. Veja:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&amp;gt;&amp;gt;&amp;gt; with open(&amp;#39;text.txt&amp;#39;, &amp;#39;rb&amp;#39;) as file:
...     # Vamos ler apenas bytes do arquivo
...     raw_content = file.read()
...     # Agora a gente decodifica
...     content_string = raw_content.decode(encoding[&amp;quot;encoding&amp;quot;])
&amp;gt;&amp;gt;&amp;gt;
# Perfeito, agora vamos tentar pegar o conteúdo
# da content_string e salvar em outro arquivo
# porém, agora vamos codificar em UTF-8
&amp;gt;&amp;gt;&amp;gt; with open(&amp;#39;text2.txt&amp;#39;, &amp;#39;wb&amp;#39;) as file:
...     file.write(content_string.encode(&amp;#39;utf8&amp;#39;))
...
192
&amp;gt;&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perfeito, sem erros! Agora vamos ver como está o nosso arquivo “&lt;strong&gt;text2.txt&lt;/strong&gt;”
(o novo arquivo gerado). Será que os caracteres se mantiveram?&lt;/p&gt;
&lt;p&gt;![UTF-8](./imgs/python-2.png)&lt;/p&gt;
&lt;p&gt;Perfeito! Viu como a aproximação que o &lt;strong&gt;chardet&lt;/strong&gt; encontrou me ajudou muito?
Mesmo que ele não tenha detectado com 100% de certeza qual a codificação de
caracteres usada no arquivo, ele me passou uma que provavelmente iria funcionar.&lt;/p&gt;
&lt;p&gt;Se você fosse tentar decodificar direto com UTF-8, isso ocorreria:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UnicodeDecodeError: &amp;#39;utf-8&amp;#39; codec can&amp;#39;t decode byte 0xe7 in position 4: invalid continuation byte&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;E se ignorasse os erros, seu texto ficaria assim:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Ateno Exceo Impresso Concesso Presuno&lt;/code&gt;&lt;br&gt;&lt;code&gt;Voc Pur Croch Metr&lt;/code&gt;&lt;br&gt;&lt;code&gt;Plstico Grfico Espcie Clebre&lt;/code&gt;&lt;br&gt;&lt;code&gt;quelas s&lt;/code&gt;&lt;br&gt;&lt;code&gt;Acar ACAR CABEA CAROO&amp;#39;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Eu acho que deu pra você entender, não é?&lt;/p&gt;
&lt;h2&gt;Funções interessantes com normalização unicode&lt;/h2&gt;
&lt;p&gt;Você, como programador(a), já pode ter imaginado milhares de coisas
interessantes que pode fazer com a normalização unicode, não é mesmo? Se não,
vou te dar algumas ideias:&lt;/p&gt;
&lt;h3&gt;Obtendo code points Unicode&lt;/h3&gt;
&lt;p&gt;Suponha que eu queira obter uma lista com todos os code points de uma frase.
Veja que legal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import List
from unicodedata import normalize


def get_unicode_code_points(string: str) -&amp;gt; List[str]:
    string_normalized = normalize(&amp;#39;NFD&amp;#39;, string)
    code_points: List[str] = [
        &amp;#39;U+&amp;#39; + hex(ord(letter))[2:].zfill(4).upper()
        for letter in string_normalized
    ]
    return code_points


if __name__ == &amp;quot;__main__&amp;quot;:
    text = &amp;#39;Python 🐍™&amp;#39;
    code_points = get_unicode_code_points(text)
    print(code_points)

    &amp;quot;&amp;quot;&amp;quot;
    [&amp;#39;U+0050&amp;#39;, &amp;#39;U+0079&amp;#39;, &amp;#39;U+0074&amp;#39;, &amp;#39;U+0068&amp;#39;,
    &amp;#39;U+006F&amp;#39;, &amp;#39;U+006E&amp;#39;, &amp;#39;U+0020&amp;#39;, &amp;#39;U+1F40D&amp;#39;,
    &amp;#39;U+2122&amp;#39;]
    &amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Obtendo caracteres de code points&lt;/h3&gt;
&lt;p&gt;Mas, e o inverso? Dado um code point, como converto em caractere? Você já viu
isso ao longo do texto todo, mas aqui vai.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import List


def get_char_from_code_point(code_points: List[str]) -&amp;gt; str:
    chars = [chr(int(c.replace(&amp;#39;U+&amp;#39;, &amp;#39;0x&amp;#39;), 16)) for c in code_points]
    return &amp;#39;&amp;#39;.join(chars)


if __name__ == &amp;quot;__main__&amp;quot;:
    code_points = [
        &amp;#39;U+0050&amp;#39;, &amp;#39;U+0079&amp;#39;, &amp;#39;U+0074&amp;#39;, &amp;#39;U+0068&amp;#39;,
        &amp;#39;U+006F&amp;#39;, &amp;#39;U+006E&amp;#39;, &amp;#39;U+0020&amp;#39;, &amp;#39;U+1F40D&amp;#39;,
        &amp;#39;U+2122&amp;#39;
    ]

    print(get_char_from_code_point(code_points))
    # Python 🐍™
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Removendo caracteres fora da tabela ASCII&lt;/h3&gt;
&lt;p&gt;Em alguns casos, pode ser interessante manter apenas caracteres compatíveis com
a tabela “ASCII”. Além disso, nós também podemos converter caracteres que seriam
compatíveis se não fossem pré-compostos. Por exemplo, ‘&lt;strong&gt;á&lt;/strong&gt;‘, ‘&lt;strong&gt;ã&lt;/strong&gt;‘, &lt;strong&gt;à&lt;/strong&gt; e
&lt;strong&gt;â&lt;/strong&gt; se tornariam simplesmente ‘&lt;strong&gt;a&lt;/strong&gt;‘ e assim por diante para todos os
caracteres. Porém, caracteres como 🐍 e 😀 não estariam presentes, porque não
existem na tabela ASCII e também não existem compatíveis.&lt;/p&gt;
&lt;p&gt;Vamos ver como faríamos isso:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import unicodedata


def non_ascii_to_ascii(string: str) -&amp;gt; str:
    ascii_only = unicodedata.normalize(&amp;#39;NFKD&amp;#39;, string)\
        .encode(&amp;#39;ascii&amp;#39;, &amp;#39;ignore&amp;#39;)\
        .decode(&amp;#39;ascii&amp;#39;)
    return ascii_only


if __name__ == &amp;quot;__main__&amp;quot;:
    string = &amp;#39;Atenção 🐍 😀&amp;#39;
    print(non_ascii_to_ascii(string))  # Atencao
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que caracteres como “&lt;strong&gt;ç&lt;/strong&gt;” e “&lt;strong&gt;ã&lt;/strong&gt;” de “&lt;strong&gt;Atenção&lt;/strong&gt;” foram mantidos
sem acento (porque existem na tabela ASCII), porém 🐍 e 😀 foram ignorados.&lt;/p&gt;
&lt;h3&gt;Removendo acentos das palavras&lt;/h3&gt;
&lt;p&gt;Na função anterior, a gente meio que removeu os acentos, porém também removemos
outras coisas que não queríamos. Mas, suponha que eu queira remover apenas o
&lt;strong&gt;combining character&lt;/strong&gt; mantendo o restante (o combining character seria o
acento propriamente dito).&lt;/p&gt;
&lt;p&gt;Isso me retornaria palavras sem acento.&lt;/p&gt;
&lt;p&gt;Eu posso fazer isso, como o
&lt;a href=&quot;https://twitter.com/ramalhoorg?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor&quot;&gt;Luciano Ramalho&lt;/a&gt;
descreve em seu livro &lt;a href=&quot;https://amzn.to/34HdIPs&quot;&gt;Python Fluente&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import unicodedata

def remove_accents(string: str) -&amp;gt; str:
    normalized = unicodedata.normalize(&amp;#39;NFKD&amp;#39;, string)
    return &amp;#39;&amp;#39;.join([c for c in normalized if not unicodedata.combining(c)])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Porém, eu também posso fazer isso com expressões regulares. O que funcionar
melhor pra você:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import unicodedata
import re


def remove_accents_regex(string: str) -&amp;gt; str:
    regex = re.compile(r&amp;#39;[\u0300-\u036F]&amp;#39;, flags=re.DOTALL)
    normalized = unicodedata.normalize(&amp;#39;NFKD&amp;#39;, string)
    return regex.sub(&amp;#39;&amp;#39;, normalized)


if __name__ == &amp;quot;__main__&amp;quot;:
    string = &amp;#39;Atenção 🐍 😀&amp;#39;
    print(remove_accents_regex(string))  # Atencao 🐍 😀
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Por falar nisso, eu tenho um detalhe sobre expressões regulares pra te informar:
eu tenho um curso inteiro e gratuito na
&lt;a href=&quot;https://www.udemy.com/course/expressoes-regulares-com-python-3-curso-gratuito/&quot;&gt;Udemy&lt;/a&gt;
e no
&lt;a href=&quot;https://www.youtube.com/watch?v=wBI0yv2FG6U&amp;list=PLbIBj8vQhvm1VnTa2Np5vDzCxVtyaYLMr&quot;&gt;Youtube&lt;/a&gt;
sobre isso.&lt;/p&gt;
&lt;p&gt;Portanto, não há motivos para entrarmos em detalhes sobre
&lt;a href=&quot;https://pt.wikipedia.org/wiki/Express%C3%A3o_regular&quot;&gt;regex&lt;/a&gt; aqui.&lt;/p&gt;
&lt;h2&gt;Mais sobre Unicode e Normalização Unicode&lt;/h2&gt;
&lt;p&gt;Eu sei que esse assunto talvez passe despercebido para vários desenvolvedores e
desenvolvedoras mundo a fora e não é culpa deles (ou nossa, também passei por
isso). Em nosso meio, a maioria dos cursos, faculdades e livros que você lê para
aprender a programar, infelizmente não tratam desse assunto, ou, se tratam, é de
maneira superficial. Porém, como você pôde ver, eu fiz questão de deixar todos
os links de onde removi todas as informações que escrevi aqui. Assim, é
extremamente necessário que você leia esses links também.&lt;/p&gt;
&lt;p&gt;Além disso, ainda faltaram algumas coisas que não consegui falar neste post. Por
exemplo, casefold e tratamento de arquivos, foram coisas que não mencionei aqui,
mas que são mencionadas no
&lt;a href=&quot;https://docs.python.org/3/howto/unicode.html&quot;&gt;Unicode HOWTO oficial do Python&lt;/a&gt;.
Então, dá um jeito de ler esse artigo também.&lt;/p&gt;
&lt;p&gt;Então é isso, te deixo aqui com um pouco mais de serviço pela frente.&lt;/p&gt;
&lt;p&gt;Te espero no próximo post.&lt;/p&gt;
</content:encoded></item><item><title>Filas em Python com deque (queue)</title><link>https://otaviomiranda.com.br/2020/filas-em-python-com-deque-queue/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2020/filas-em-python-com-deque-queue/</guid><description>Filas em Python podem ser implementadas utilizando a estrutura de dados deque (do módulo collections) para garantirmos complexidade de tempo constante para inserção e remoção de elementos em qualquer uma das pontas, cabeça (head) ou cauda (tail).</description><pubDate>Mon, 08 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Filas em Python podem ser implementadas utilizando a estrutura de dados &lt;code&gt;deque&lt;/code&gt;
(do módulo
&lt;a href=&quot;https://docs.python.org/pt-br/3.9/library/collections.html&quot;&gt;collections&lt;/a&gt;) para
garantirmos complexidade de tempo constante para inserção e remoção de elementos
em qualquer uma das pontas, cabeça (head) ou cauda (tail). &lt;strong&gt;Deque&lt;/strong&gt; –
abreviação de &lt;em&gt;Double Ended Queue&lt;/em&gt; (ou Fila de duas pontas) – pode ser usada
para implementar filas FIFO (que veremos a seguir) e
&lt;a href=&quot;/2020/pilhas-em-python-com-listas-stack/&quot;&gt;pilhas LIFO&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;O que são filas?&lt;/h2&gt;
&lt;p&gt;Quando falamos em filas, estamos tratando de uma estrutura de dados abstrata que
se comporta exatamente como uma fila na vida real. Elementos são adicionados à
cauda (tail) e removidos da frente (head).&lt;/p&gt;
&lt;p&gt;Porém, remover elementos da frente (com índice 0) pode ser problemático do ponto
de vista de desempenho do programa. Por exemplo, poderíamos usar listas para
criar filas
(&lt;a href=&quot;/2020/pilhas-em-python-com-listas-stack/&quot;&gt;como fizemos com as pilhas&lt;/a&gt;), porém
elas não são otimizadas para isso e acabam movendo os índices de todos os
elementos posteriores ao elemento removido. Infelizmente, isso nos dá uma
complexidade de tempo linear O(n), ou seja, ao remover um item do começo da
lista, o interpretador precisa navegar em todos os seus elementos, reajustando
os índices dos elementos restantes.&lt;/p&gt;
&lt;p&gt;No entanto, isso não ocorre com &lt;code&gt;deque&lt;/code&gt;. Segundo a
&lt;a href=&quot;https://docs.python.org/pt-br/3.9/library/collections.html#collections.deque&quot;&gt;documentação oficial do Python&lt;/a&gt;,
&lt;code&gt;deque&lt;/code&gt; adiciona e remove em ambas as direções com complexidade de tempo de
aproximadamente O(1). Isso quer dizer que requer aproximadamente uma operação
para a adição e remoção de elementos.&lt;/p&gt;
&lt;p&gt;De forma resumida, &lt;code&gt;deque&lt;/code&gt; é mais rápido para inserir e remover elementos da
frente da fila (no índice 0).&lt;/p&gt;
&lt;h2&gt;Filas são FIFO&lt;/h2&gt;
&lt;p&gt;Na programação, o comportamento de uma fila é conhecido como
&lt;a href=&quot;https://pt.wikipedia.org/wiki/FIFO&quot;&gt;FIFO&lt;/a&gt; (First In First Out), ou seja, o
primeiro que entrar será o primeiro a sair. Isso é exatamente o que acontece em
uma fila da vida real, a primeira pessoa a entrar na fila, será a primeira a ser
atendida. É claro, se ninguém furar a fila!&lt;/p&gt;
&lt;p&gt;Imagine uma fila de letras: A, B, C.&lt;/p&gt;
&lt;p&gt;Agora, suponha que eu quero adicionar um novo elemento, o D. Para as filas, esse
elemento deve ser adicionado na cauda, como mostra o diagrama a seguir:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Queues-1.png&quot; alt=&quot;Queues em Python - Exemplo 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;Mas, se eu precisasse remover um elemento da fila anterior, o elemento removido
deveria sair na cabeça (head).&lt;/p&gt;
&lt;p&gt;Este elemento é representado pelo A na imagem a seguir:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Queues-2-1.png&quot; alt=&quot;Queues em Python - Exemplo 2&quot;&gt;&lt;/p&gt;
&lt;p&gt;Além disso, perceba que agora, além da minha fila não ter mais o elemento A, os
índices foram reorganizados. B passou a ter o índice 0, C, índice 1 e D,
índice 2. Lembre-se que &lt;code&gt;deque&lt;/code&gt; é otimizada para isso.&lt;/p&gt;
&lt;p&gt;Outra informação que você deve ter percebido, é que a cabeça (head) e a cauda
(tail) da minha fila também mudaram. B é a cabeça e D a cauda.&lt;/p&gt;
&lt;h2&gt;Filas em Python com deque&lt;/h2&gt;
&lt;p&gt;Se fossemos representar a fila apresentada nas imagens anteriores com os
elementos A, B e C, em seguida acrescentar o elemento D, o código ficaria da
seguinte maneira:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from collections import deque

fila = deque([&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;])

# Inserindo o elemento D
fila.append(&amp;#39;D&amp;#39;)


# deque([&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;, &amp;#39;D&amp;#39;])
print(fila)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que &lt;code&gt;deque&lt;/code&gt; tem quase os mesmos métodos de uma lista normal no Python
(por isso podemos usá-las como
&lt;a href=&quot;/2020/pilhas-em-python-com-listas-stack/&quot;&gt;pilhas&lt;/a&gt; se quisermos). Então, para
inserir um novo elemento na cauda da fila, basta usarmos o método &lt;code&gt;append&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Para a remoção do elemento A (como fizemos na seção anterior desse post),
poderíamos utilizar o método &lt;code&gt;popleft&lt;/code&gt; (remover da esquerda).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from collections import deque

fila = deque([&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;])

# Inserindo o elemento D
fila.append(&amp;#39;D&amp;#39;)

# Removendo A
fila.popleft()

# deque([&amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;, &amp;#39;D&amp;#39;])
print(fila)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;É bem simples, não é mesmo?&lt;/p&gt;
&lt;h2&gt;popleft retorna o elemento removido&lt;/h2&gt;
&lt;p&gt;Um comportamento padrão sempre que usamos métodos “&lt;code&gt;pop&lt;/code&gt;” (de remoção) é que o
elemento removido é retornado por essa função. Isso não é diferente como
&lt;code&gt;popleft&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Por exemplo, se eu estou removendo um elemento da minha fila, é natural querer
usá-lo em algum momento do meu código. Então, eu poderia usar uma variável para
isso.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from collections import deque

fila = deque([&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;])

# Inserindo o elemento D
fila.append(&amp;#39;D&amp;#39;)

# Removendo A e usando seu valor
head = fila.popleft()

# A
print(head)

# deque([&amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;, &amp;#39;D&amp;#39;])
print(fila)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que no código acima (linha 9), &lt;code&gt;popleft&lt;/code&gt; faz duas coisas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Remove o primeiro elemento da fila (cabeça);&lt;/li&gt;
&lt;li&gt;Retorna o valor do elemento removido para a variável &lt;code&gt;head&lt;/code&gt; (A).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Assim, é possível utilizar o valor removido conforme preferir no seu código.&lt;/p&gt;
&lt;h2&gt;Criando uma filas em Python&lt;/h2&gt;
&lt;p&gt;Assim como fizemos com as pilhas (no
&lt;a href=&quot;/2020/pilhas-em-python-com-listas-stack/&quot;&gt;artigo anterior&lt;/a&gt;), podemos encapsular
&lt;code&gt;deque&lt;/code&gt; em uma classe para manter o controle sobre os métodos que deverão ser
utilizados para manter o comportamento &lt;strong&gt;FIFO&lt;/strong&gt; das filas. Afinal, &lt;code&gt;deque&lt;/code&gt; não
tem apenas os métodos &lt;code&gt;append&lt;/code&gt; e &lt;code&gt;popleft&lt;/code&gt;. De fato, se você conferir a
&lt;a href=&quot;https://docs.python.org/3.9/library/collections.html#collections.deque&quot;&gt;documentação oficial&lt;/a&gt;,
vai ver que existem vários outros métodos possíveis para &lt;code&gt;deque&lt;/code&gt;, incluindo os
que quebram o princípio &lt;strong&gt;FIFO&lt;/strong&gt;, removendo elementos da cauda ou inserindo na
cabeça, por exemplo.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Deque, Any
from collections import deque


class Queue:
    &amp;quot;&amp;quot;&amp;quot;Uma classe representando uma fila&amp;quot;&amp;quot;&amp;quot;

    def __init__(self, maxlen=None) -&amp;gt; None:
        # Deque permite enviar maxlen
        # para criar um tamanho máximo para
        # a fila
        self.__items: Deque[Any] = deque(maxlen=maxlen)

    def enqueue(self, *items: Any) -&amp;gt; None:
        &amp;quot;&amp;quot;&amp;quot;Enqueue (enfileirar) é o mesmo que append&amp;quot;&amp;quot;&amp;quot;
        for item in items:
            self.__items.append(item)

    def dequeue(self) -&amp;gt; Any:
        &amp;quot;&amp;quot;&amp;quot;Dequeue (desenfileirar) é o mesmo que popleft&amp;quot;&amp;quot;&amp;quot;
        if not self:
            raise IndexError(&amp;#39;pop from empty queue&amp;#39;)

        return self.__items.popleft()

    def __repr__(self) -&amp;gt; str:
        return str(self.__items)

    def __bool__(self) -&amp;gt; bool:
        return bool(self.__items)

    def __len__(self) -&amp;gt; int:
        return len(self.__items)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Na classe anterior, criei apenas os métodos que precisava para minha fila. Nesse
sentido, também mudei o nome de algumas coisas: &lt;code&gt;append&lt;/code&gt; passou a ser &lt;code&gt;enqueue&lt;/code&gt;
(enfileirar); &lt;code&gt;popleft&lt;/code&gt; passou a ser &lt;code&gt;dequeue&lt;/code&gt; (desenfileirar). Contudo, eles
continuam fazendo o mesmo trabalho.&lt;/p&gt;
&lt;p&gt;Também permiti adicionar o argumento “&lt;code&gt;maxlen&lt;/code&gt;” de &lt;code&gt;deque&lt;/code&gt;, para fixar um
tamanho máximo para nossa fila.&lt;/p&gt;
&lt;p&gt;Além disso, nós precisamos saber de três coisas muito importantes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quais valores estão na fila? Por isso o método &lt;code&gt;__repr__&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Existem valores na minha fila? Por isso o método &lt;code&gt;__bool__&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Quantos valores tem na minha fila? Por isso o método &lt;code&gt;__len__&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Você pode enfeitar essa classe com os métodos que preferir. Aqui eu quis manter
as coisas o mais simples que foi possível.&lt;/p&gt;
&lt;p&gt;Agora podemos usar a classe:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if __name__ == &amp;quot;__main__&amp;quot;:
# Instanciando
fila = Queue()

# Enfileirando A, B e C
fila.enqueue(&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;)

# Enfileirando D
fila.enqueue(&amp;#39;D&amp;#39;)

# Recuperando A
head = fila.dequeue()
print(head)

# Mostrando a fila
print(fila)

&amp;quot;&amp;quot;&amp;quot;
Resultado:

A
deque([&amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;, &amp;#39;D&amp;#39;])
&amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Indo além do básico&lt;/h3&gt;
&lt;p&gt;Em algum momento, você pode querer iterar sobre a sua fila ou então obter um
item diretamente. Pra isso podemos repassar o método &lt;code&gt;__iter__&lt;/code&gt; da nossa classe
para o método &lt;code&gt;__iter__&lt;/code&gt; do &lt;code&gt;deque&lt;/code&gt; da mesma (&lt;code&gt;__items&lt;/code&gt;). Veja a implementação
do método &lt;code&gt;__iter__&lt;/code&gt; por si só.&lt;/p&gt;
&lt;p&gt;Este método deve estar dentro da classe, é claro (não se preocupe, vou mostrar o
código completo logo abaixo).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def __iter__(self) -&amp;gt; Iterator:
    return self.__items.__iter__()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Por fim, se você quiser permitir que um item da fila seja obtido pelo seu
índice, também pode implementar o método __getitem__, dessa maneira:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def __getitem__(self, index: int) -&amp;gt; Any:
    return self.__items[index]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Veja como ficou nossa classe completa:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Deque, Any, Iterator
from collections import deque


class Queue:
    &amp;quot;&amp;quot;&amp;quot;Uma classe representando uma fila&amp;quot;&amp;quot;&amp;quot;

    def __init__(self, maxlen=None) -&amp;gt; None:
        # Deque permite enviar maxlen
        # para criar um tamanho máximo para
        # a fila
        self.__items: Deque[Any] = deque(maxlen=maxlen)

    def enqueue(self, *items: Any) -&amp;gt; None:
        &amp;quot;&amp;quot;&amp;quot;Enqueue (enfileirar) é o mesmo que append&amp;quot;&amp;quot;&amp;quot;
        for item in items:
            self.__items.append(item)

    def dequeue(self) -&amp;gt; Any:
        &amp;quot;&amp;quot;&amp;quot;Dequeue (desenfileirar) é o mesmo que popleft&amp;quot;&amp;quot;&amp;quot;
        if not self:
            raise IndexError(&amp;#39;pop from empty queue&amp;#39;)

        return self.__items.popleft()

    def __repr__(self) -&amp;gt; str:
        return str(self.__items)

    def __bool__(self) -&amp;gt; bool:
        return bool(self.__items)

    def __len__(self) -&amp;gt; int:
        return len(self.__items)

    def __iter__(self) -&amp;gt; Iterator:
        return self.__items.__iter__()

    def __getitem__(self, index: int) -&amp;gt; Any:
        return self.__items[index]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que agora temos os métodos __iter__ e __getitem__. Isso nos
permite iterar sobre nossa fila e também obter itens por índice, veja:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if __name__ == &amp;quot;__main__&amp;quot;:
# Instanciando
fila = Queue()

# Enfileirando A, B, C e D
fila.enqueue(&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;, &amp;#39;D&amp;#39;)

# Obtendo o elemento com índice 1 (B)
print(&amp;#39;Item com índice 1:&amp;#39;, fila[1], end=&amp;#39;\n\n&amp;#39;)

# Iterando com for em nossa fila
for item in fila:
    print(&amp;#39;Iteração:&amp;#39;, item)

&amp;quot;&amp;quot;&amp;quot;
Resultado:

Item com índice 1: B

Iteração: A
Iteração: B
Iteração: C
Iteração: D
&amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nossa classe ficou assim, simples mas útil e eficaz. Todavia, você pode
adicionar os métodos que preferir. Apenas adicionei métodos que acho que podem
ser úteis.&lt;/p&gt;
&lt;h2&gt;Resumo&lt;/h2&gt;
&lt;p&gt;Em conclusão, você viu o seguinte ao decorrer desse post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Filas em Python podem ser implementadas utilizando &lt;code&gt;deque&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Filas têm o comportamento &lt;strong&gt;FIFO&lt;/strong&gt; (First In First Out), assim como na vida
real;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;append&lt;/code&gt; adiciona elementos na cauda (tail);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;popleft&lt;/code&gt; remove elementos da cabeça (head);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;deque&lt;/code&gt; é muito genérica, por isso criamos nossa própria classe para permitir
apenas os métodos que queremos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Como resultado, agora você já sabe criar filas em Python. Além disso, também
sabe usar &lt;code&gt;deque&lt;/code&gt;, que é outra estrutura de dados abstrata conhecida em
programação.&lt;/p&gt;
&lt;p&gt;Te vejo no próximo post!&lt;/p&gt;
</content:encoded></item><item><title>Pilhas em Python com listas (stack)</title><link>https://otaviomiranda.com.br/2020/pilhas-em-python-com-listas-stack/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2020/pilhas-em-python-com-listas-stack/</guid><description>Pilhas em Python são comumente criadas usando listas, porque estamos interessados em manipular apenas a extremidade do topo da estrutura (o final da lista).</description><pubDate>Mon, 03 Sep 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Pilhas em Python são comumente criadas usando
&lt;a href=&quot;https://docs.python.org/pt-br/3.8/tutorial/datastructures.html&quot;&gt;listas&lt;/a&gt;, porque
estamos interessados em manipular apenas a extremidade do topo da estrutura (o
final da lista). Isso nos garante
&lt;a href=&quot;https://wiki.python.org/moin/TimeComplexity&quot;&gt;complexidade de tempo O(1)&lt;/a&gt; (tempo
constante) com métodos &lt;code&gt;append&lt;/code&gt; e &lt;code&gt;pop&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Uma pilha é uma coleção de itens que segue o princípio &lt;strong&gt;LIFO&lt;/strong&gt; (&lt;strong&gt;L&lt;/strong&gt;ast &lt;strong&gt;I&lt;/strong&gt;n
&lt;strong&gt;F&lt;/strong&gt;irst &lt;strong&gt;O&lt;/strong&gt;ut) , ou seja, o último item a entrar, será o primeiro a sair. A
adição e remoção de novos itens ocorre sempre na mesma ponta da pilha, o final
da lista. O final da lista (último índice), representa o topo da pilha, o começo
da lista (índice 0) representa a base da pilha.&lt;/p&gt;
&lt;p&gt;Itens mais novos são sempre adicionados no topo da pilha e se tornam mais
antigos a medida que adicionamos outros itens. Quanto maior o índice na lista,
mais novo é o item e mais cedo ele será removido da pilha.&lt;/p&gt;
&lt;p&gt;Pilhas são estruturas de dados muito simples, mas podem ser usadas para
representar várias coisas no mundo real, por exemplo: uma pilha de pratos, uma
pilha de livros ou qualquer coisa que você coloque uma sobre a outra formando
uma pilha. Além disso, programas também usam pilhas, como a
&lt;a href=&quot;https://otaviomiranda.com.br/2020/funcoes-recursivas-com-python/#Call_stack&quot;&gt;pilha de chamadas&lt;/a&gt;
para funções que já vimos anteriormente nesse blog.&lt;/p&gt;
&lt;h2&gt;Listas são pilhas muito genéricas em Python&lt;/h2&gt;
&lt;p&gt;Como pilhas só precisam de métodos para adicionar e remover itens do seu topo,
as listas do Python já fazem esse trabalho com excelência e complexidade de
tempo O(1), ou tempo constante. Então, é super rápido.&lt;/p&gt;
&lt;p&gt;Porém, ou você só usa esses dois métodos (&lt;code&gt;append&lt;/code&gt; e &lt;code&gt;pop&lt;/code&gt;), ou você cria sua
própria estrutura de dados chamada de &lt;code&gt;Stack&lt;/code&gt; (uma classe que você verá ao final
desse post) para outros desenvolvedores não usarem sua pilha de maneira genérica
(como filas, por exemplo).&lt;/p&gt;
&lt;p&gt;Se você quiser trabalhar diretamente com as listas, pode fazer o seguinte:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Para Type annotation
from typing import List

# Pilha de livros com type annotation
stack_of_books: List[str] = []  # {1}

# Adicionando livros no topo da pilha
stack_of_books.append(&amp;#39;Livro 1&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 2&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 3&amp;#39;)  # {2}

# Obtendo o elemento mais novo
book = stack_of_books.pop()  # {3}

print(book)  # Livro 3 {4}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que no código acima, criei uma pilha de livros (&lt;code&gt;stack_of_books&lt;/code&gt;) como
uma lista vazia {1}. Quando precisei adicionar livros na pilha, usei o método
&lt;code&gt;append&lt;/code&gt;, que é padrão de listas em Python para adicionar itens ao final da
lista {2}. Além disso, quando precisei obter o livro mais novo (do topo da
pilha), usei o método &lt;code&gt;pop&lt;/code&gt;, que também é padrão de listas em Python para obter
o último item da lista (ou o elemento do topo da pilha) {3}.&lt;/p&gt;
&lt;p&gt;Na verdade, o método &lt;code&gt;pop&lt;/code&gt; faz duas coisas. Além de remover o item do topo da
pilha, ele também retorna este valor. Então, Isso me permitiu fazer duas coisas
em {3}: remover o item do topo da pilha e coletar seu valor em uma variável.&lt;/p&gt;
&lt;p&gt;Por fim {4}, imprimimos o valor de book, que foi o “Livro 3”.&lt;/p&gt;
&lt;h3&gt;Cuidados com pop&lt;/h3&gt;
&lt;p&gt;Note que, a cada execução do método &lt;code&gt;pop&lt;/code&gt;, o item mais novo será removido da
pilha. Em algum momento, sua pilha poderá estar vazia e esse método levantará a
exceção “&lt;code&gt;IndexError: pop from empty list&lt;/code&gt;“, ou “&lt;em&gt;Erro de índice: pop de uma
lista vazia&lt;/em&gt;” (em tradução livre).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Para Type annotation
from typing import List

# Pilha de livros com type annotation
stack_of_books: List[str] = []  # {1}

# Adicionando livros no topo da pilha
stack_of_books.append(&amp;#39;Livro 1&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 2&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 3&amp;#39;)  # {2}

# Obtendo o elemento mais novo
book_1 = stack_of_books.pop()  # Livro 3 {3}
book_2 = stack_of_books.pop()  # Livro 2 {3}
book_3 = stack_of_books.pop()  # Livro 1 {3}

# IndexError: pop from empty list
book = stack_of_books.pop()  # Não há mais livros {4}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isso ocorre porque não há mais valores para serem removidos e recuperados da sua
pilha {4}.&lt;/p&gt;
&lt;p&gt;Para prevenir esse tipo de situação, podemos envolver nosso código em um bloco
&lt;code&gt;try&lt;/code&gt; e &lt;code&gt;except&lt;/code&gt;, tratando a exceção “&lt;code&gt;IndexError&lt;/code&gt;“. Veja:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Para Type annotation
from typing import List

# Pilha de livros com type annotation
stack_of_books: List[str] = []  # {1}

# Adicionando livros no topo da pilha
stack_of_books.append(&amp;#39;Livro 1&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 2&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 3&amp;#39;)  # {2}

try:
    # Obtendo o elemento mais novo
    book_1 = stack_of_books.pop()  # Livro 3 {3}
    print(book_1)  # Livro 3

    book_2 = stack_of_books.pop()  # Livro 2 {3}
    print(book_2)  # Livro 2

    book_3 = stack_of_books.pop()  # Livro 1 {3}
    print(book_3)  # Livro 1

    # IndexError: pop from empty list
    book = stack_of_books.pop()  # Não há mais livros {4}
    print(book)  # Seu código não chegará aqui
except IndexError:
    print(&amp;#39;Trate o erro como preferir aqui.&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Assim, a partir do momento que a exceção ocorrer {4}, seu código pulará
imediatamente para o bloco except. Se não houver exceções, seu código não
executará o bloco except.&lt;/p&gt;
&lt;h3&gt;Não use listas como filas&lt;/h3&gt;
&lt;p&gt;Além das pilhas, outra estrutura de dados muito usada em programação são as
filas (falamos sobre elas em outro artigo). Basicamente, filas trabalham no lado
oposto das pilhas. Então, se nas pilhas você trabalha na extremidade final
(adicionando e removendo itens do topo), nas filas você trabalha nas duas
extremidades, enfileirando itens no final (topo) e desenfileirando itens do
início (base).&lt;/p&gt;
&lt;p&gt;As listas podem ser usadas para filas também, porém, não é recomendável. Isso
torna sua complexidade de tempo O(n), ou tempo linear, o que torna as listas
mais lentas para isso.&lt;/p&gt;
&lt;p&gt;O método pop, pode receber o índice que você pretende remover da pilha, por
exemplo (mas não faça isso):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Para Type annotation
from typing import List

# Pilha de livros com type annotation
stack_of_books: List[str] = []  # {1}

# Adicionando livros no topo da pilha
stack_of_books.append(&amp;#39;Livro 1&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 2&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 3&amp;#39;)  # {2}

book_1 = stack_of_books.pop(0)  # {3} Livro 1

print(book_1)  # Livro 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que, no código acima, eu consigo passar um índice para &lt;code&gt;pop&lt;/code&gt; descrevendo
qual livro quero remover da pilha {3}. Assim, isso me permite remover o item da
base da minha pilha (índice 0). Mas, o problema ao fazer algo assim, é que como
as listas não foram otimizadas para tal, todos os outros itens da lista agora
precisam ter seus índices alterados. O que tinha índice 1, passa a ter índice 0,
o que tinha índice 2, passa a ter índice 1 e assim por diante.&lt;/p&gt;
&lt;p&gt;A complexidade de tempo O(n) significa que, na pior das hipóteses, quando eu
removo um item da base da minha pilha (como nas filas), todos os outros items
agora precisam ser modificados, e eu assumo que não era essa a sua intenção.&lt;/p&gt;
&lt;p&gt;Nós já falamos sobre filas aqui, mas para resumir, use
&lt;a href=&quot;https://docs.python.org/pt-br/3.8/library/collections.html#collections.deque&quot;&gt;collections.deque&lt;/a&gt;
para filas e listas para pilhas.&lt;/p&gt;
&lt;h2&gt;Iterando pilhas&lt;/h2&gt;
&lt;p&gt;Em Python a iteração dos iteráveis é feita com &lt;code&gt;for&lt;/code&gt;. Se você não quer remover
nenhum elemento, mas apenas iterar sobre toda a pilha, faça o seguinte:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Para Type annotation
from typing import List

# Pilha de livros com type annotation
stack_of_books: List[str] = []  # {1}

# Adicionando livros no topo da pilha
stack_of_books.append(&amp;#39;Livro 1&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 2&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 3&amp;#39;)  # {2}

# Laço for
for book in stack_of_books[::-1]:
    # Faça o que preferir com o Livro
    print(book)

&amp;quot;&amp;quot;&amp;quot;
Saída:
Livro 3
Livro 2
Livro 1
&amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que no código acima, na linha 13, temos um laço for que vai iterar em
todos os elementos da lista. Então eu posso fazer o que preferir com a variável
&lt;code&gt;book&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Detalhe importante:&lt;/strong&gt; para manter a ordem da pilha (do topo para a base), eu
preciso fazer uma iteração em ordem reversa na pilha quando uso &lt;code&gt;for&lt;/code&gt;. Por isso
adicionei &lt;code&gt;[::-1]&lt;/code&gt; em &lt;code&gt;stack_of_books&lt;/code&gt;. Isso inverte a ordem de listas em
Python.&lt;/p&gt;
&lt;p&gt;Um outro tipo de iteração sobre pilha, seria removendo os elementos da pilha ao
mesmo tempo que itero sobre eles. Podemos fazer isso com o laço &lt;code&gt;while&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Para Type annotation
from typing import List

# Pilha de livros com type annotation
stack_of_books: List[str] = []  # {1}

# Adicionando livros no topo da pilha
stack_of_books.append(&amp;#39;Livro 1&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 2&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 3&amp;#39;)  # {2}

# Laço while
while stack_of_books:
    book = stack_of_books.pop()
    print(book)

&amp;quot;&amp;quot;&amp;quot;
Saída:
Livro 3
Livro 2
Livro 1
&amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que usando o laço &lt;code&gt;while&lt;/code&gt; com o método &lt;code&gt;pop&lt;/code&gt;, a ordem é mantida. Porém,
é importante saber que estou removendo cada um dos elementos da minha pilha.&lt;/p&gt;
&lt;h2&gt;Copiando pilhas&lt;/h2&gt;
&lt;p&gt;Trabalhar com dados mutáveis, como as listas, pode ser perigoso em qualquer
linguagem de programação porque manipulamos o dado diretamente. Então, é uma boa
ideia manter uma cópia do dado original sempre que o manipulamos (se for
possível, é claro). Por outro lado, pode ser caro manter cópia de dados que são
muito grandes, então você precisa ponderar as duas coisas: segurança e peso.&lt;/p&gt;
&lt;p&gt;Manter a cópia de uma lista, pode ter uma complexidade de tempo linear O(n) se
você fizer uma cópia rasa (shallow copy) ou mais do que isso se você mantiver
uma cópia profunda (deep copy).&lt;/p&gt;
&lt;h3&gt;Shallow copy:&lt;/h3&gt;
&lt;p&gt;Esse tipo de cópia é um clone superficial dos itens da pilha. Ela tem
complexidade de tempo linear – O(n) – o que significa que é necessário passar
por todos os itens para realizar a cópia destes. A parte interessante desse tipo
de cópia é que dados imutáveis (como str, int, float e bool) serão realmente
copiados para uma nova pilha. Porém, dados mutáveis (como outras listas,
dicionários e sets) não serão copiados, a nova pilha terá apenas uma referência
para os dados mutáveis da pilha original.&lt;/p&gt;
&lt;p&gt;Para realizar uma cópia do tipo shallow copy da sua pilha, faça o seguinte:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Para Type annotation
from typing import List

# Pilha de livros com type annotation
stack_of_books: List[str] = []  # {1}

# Adicionando livros no topo da pilha
stack_of_books.append(&amp;#39;Livro 1&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 2&amp;#39;)  # {2}
stack_of_books.append(&amp;#39;Livro 3&amp;#39;)  # {2}

# A cópia (shallow copy)
stack_of_books_copy = stack_of_books.copy()

# Agora não estou mais alterando os dados
# da pilha original.
while stack_of_books_copy:
    print(stack_of_books_copy.pop())

# A original continua intacta
print(stack_of_books)  # [&amp;#39;Livro 1&amp;#39;, &amp;#39;Livro 2&amp;#39;, &amp;#39;Livro 3&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Deep copy&lt;/h3&gt;
&lt;p&gt;Quando falamos em pilhas que contém dados mutáveis, como por exemplo, outras
listas dentro da nossa pilha. É preciso tomar cuidado com a shallow copy, porque
ela não vai copiar esses elementos, vai fazer apenas uma referência apontando
para os dados da lista original. Isso significa que meus dados ainda continuarão
mutáveis internamente, mesmo na cópia.&lt;/p&gt;
&lt;p&gt;A complexidade de tempo de uma deep copy vai depender da profundidade dos itens
mutáveis da sua pilha, quanto maior a profundidade, maior a complexidade (mais
tempo leva).&lt;/p&gt;
&lt;p&gt;Imagine essa estrutura de dados:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Para Type annotation
from typing import List

# Pilha de listas
stack_of_lists: List[List[str]] = []

# Adicionando elementos
stack_of_lists.append([&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;])
stack_of_lists.append([&amp;#39;C&amp;#39;, &amp;#39;D&amp;#39;])
stack_of_lists.append([&amp;#39;E&amp;#39;, &amp;#39;F&amp;#39;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esse é um tipo de estrutura que devo tomar cuidado, porque existem listas dentro
da minha pilha.&lt;/p&gt;
&lt;p&gt;Veja o problema:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Para Type annotation
from typing import List

# Pilha de listas
stack_of_lists: List[List[str]] = []

# Adicionando elementos
stack_of_lists.append([&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;])
stack_of_lists.append([&amp;#39;C&amp;#39;, &amp;#39;D&amp;#39;])
stack_of_lists.append([&amp;#39;E&amp;#39;, &amp;#39;F&amp;#39;])

# Shallow copy
stack_of_lists_clone = stack_of_lists.copy()

# Obtendo o elemento do topo da pilha
# Isso não altera a lista original
item = stack_of_lists_clone.pop()

# Mas isso sim
item[0] = &amp;#39;ALTERANDO O DADO&amp;#39;

# Veja o resultado na lista original
print(stack_of_lists)

&amp;quot;&amp;quot;&amp;quot;
Saída:
[[&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;], [&amp;#39;C&amp;#39;, &amp;#39;D&amp;#39;], [&amp;#39;ALTERANDO O DADO&amp;#39;, &amp;#39;F&amp;#39;]]
&amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Os comentários do código acima, explicam o que ocorre em cada trecho. Mas, aqui
vai uma breve explicação:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linha 13 – Faço uma shallow copy da minha pilha. Como te descrevi
anteriormente, dados mutáveis NÃO são copiados de dentro da pilha original,
eles passados apenas como referência. Isso significa que a lista cópia terá
uma referência desses dados (você consegue acessá-los por ela) mas eles ainda
são os dados da lista original e não uma cópia real;&lt;/li&gt;
&lt;li&gt;Linha 17 – Obtém o item do topo da lista (esse ainda é o dado da lista
original);&lt;/li&gt;
&lt;li&gt;Linha 20 – Altero o item. Porém, como esse item é uma referência ao item da
lista original, quem será alterado efetivamente será o item da lista original;&lt;/li&gt;
&lt;li&gt;Linha 23 – Percebo a besteira que fiz.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para solucionar esse tipo de coisa, usamos a deep copy, que é um tipo de cópia
recursiva. Com isso, todos os dados, mutáveis e imutáveis serão copiados para a
lista cópia.&lt;/p&gt;
&lt;p&gt;Veja:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Para Type annotation
from typing import List

# Preciso importar deepcopy
from copy import deepcopy

# Pilha de listas
stack_of_lists: List[List[str]] = []

# Adicionando elementos
stack_of_lists.append([&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;])
stack_of_lists.append([&amp;#39;C&amp;#39;, &amp;#39;D&amp;#39;])
stack_of_lists.append([&amp;#39;E&amp;#39;, &amp;#39;F&amp;#39;])

# Deep copy
stack_of_lists_clone = deepcopy(stack_of_lists)

# Obtendo o elemento do topo da pilha
# Isso não altera a lista original
item = stack_of_lists_clone.pop()

# Mas isso sim
item[0] = &amp;#39;ALTERANDO O DADO&amp;#39;

# Veja o resultado na lista original
print(stack_of_lists)

&amp;quot;&amp;quot;&amp;quot;
Saída:
[[&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;], [&amp;#39;C&amp;#39;, &amp;#39;D&amp;#39;], [&amp;#39;E&amp;#39;, &amp;#39;F&amp;#39;]]
&amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Veja que as únicas coisas alteradas foram na linha 5, porque eu preciso importar
“deepcopy” do módulo “copy”. E a linha 16, porque ao invés de “shallow copy”,
agora estou fazendo uma “deep copy”.&lt;/p&gt;
&lt;h2&gt;Classe Stack&lt;/h2&gt;
&lt;p&gt;A maneira mais segura e correta de se trabalhar com listas como pilhas em
Python, é proteger o código para que os métodos genéricos das listas não possam
ser chamados diretamente. Assim protegeremos o nosso código para que outros
desenvolvedores não possam chamar um &lt;code&gt;pop(0)&lt;/code&gt; na nossa pilha, dentre vários
outros.&lt;/p&gt;
&lt;p&gt;Criando uma classe, podemos adicionar exclusivamente os métodos que queremos.
Como append, pop e alguns métodos mágicos para iteração e visualização de dados.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import List, Any
from copy import deepcopy


class Stack:
    &amp;quot;&amp;quot;&amp;quot;Uma classe representando uma pilha&amp;quot;&amp;quot;&amp;quot;

    def __init__(self) -&amp;gt; None:
        # Essa stack é genérica, por isso
        # a lista poderá receber qualquer
        # tipo de dados
        self.__data: List[Any] = []

        # Representa o índice para iterações
        # com for
        self.__index = 0

    def append(self, item: Any) -&amp;gt; None:
        &amp;quot;&amp;quot;&amp;quot;Representa o append da lista&amp;quot;&amp;quot;&amp;quot;
        self.__data.append(item)

    def pop(self) -&amp;gt; Any:
        &amp;quot;&amp;quot;&amp;quot;Representa o pop da lista sem parâmetros&amp;quot;&amp;quot;&amp;quot;
        if not self.__data:
            return

        return self.__data.pop()

    def peek(self) -&amp;gt; Any:
        &amp;quot;&amp;quot;&amp;quot;Mostra o último elemento adicionado à pilha&amp;quot;&amp;quot;&amp;quot;
        if not self.__data:
            return

        return self.__data[-1]

    def __repr__(self) -&amp;gt; str:
        &amp;quot;&amp;quot;&amp;quot;Representação dos dados&amp;quot;&amp;quot;&amp;quot;
        return str(self.__data)

    def __iter__(self):
        &amp;quot;&amp;quot;&amp;quot;Para iteração com for&amp;quot;&amp;quot;&amp;quot;
        self.__index = len(self.__data)
        return self

    def __next__(self):
        &amp;quot;&amp;quot;&amp;quot;Para iteração com for (next item)&amp;quot;&amp;quot;&amp;quot;
        if self.__index == 0:
            raise StopIteration

        self.__index -= 1
        return self.__data[self.__index]

    def __bool__(self):
        &amp;quot;&amp;quot;&amp;quot;Para iteração com while&amp;quot;&amp;quot;&amp;quot;
        return bool(self.__data)


if __name__ == &amp;quot;__main__&amp;quot;:
    stack = Stack()

    stack.append(&amp;#39;A&amp;#39;)
    stack.append(&amp;#39;B&amp;#39;)
    stack.append(&amp;#39;C&amp;#39;)

    print(&amp;#39;FOR:&amp;#39;)
    for item in stack:
        print(item)
    print()

    print(&amp;#39;POP:&amp;#39;)
    last_item = stack.pop()
    print(stack, last_item)
    print()

    print(&amp;#39;WHILE:&amp;#39;)
    stack_copy = deepcopy(stack)
    while stack_copy:
        print(stack_copy.pop())
    print()

    print(&amp;#39;ORIGINAL STACK:&amp;#39;)
    print(stack)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Resumo&lt;/h2&gt;
&lt;p&gt;Em Python, usamos as listas como pilhas (nunca como filas). Elas são estruturas
de dados bem simples que precisam de apenas dois métodos para funcionarem
corretamente. Um método de inserção e outro de remoção de valores (&lt;code&gt;append&lt;/code&gt; e
&lt;code&gt;pop&lt;/code&gt;). Geralmente, o método de remoção (pop) também retorna o valor removido e
você pode usá-lo com o que preferir.&lt;/p&gt;
&lt;p&gt;As pilhas trabalham apenas na extremidade final da lista, por isso têm
comportamento LIFO (Last In First Out), ou, o último a entrar é o primeiro a
sair. O último elemento adicionado é considerado o elemento do topo da pilha; o
primeiro elemento adicionado é o elemento da base da pilha.&lt;/p&gt;
&lt;p&gt;Podemos iterar sobre pilhas usando laços &lt;code&gt;for&lt;/code&gt; (invertendo seus valores) ou laço
&lt;code&gt;while&lt;/code&gt; combinado com &lt;code&gt;pop&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;É possível copiar listas utilizando shallow copy ou deep copy.&lt;/p&gt;
&lt;p&gt;É só isso… Te vejo no próximo artigo.&lt;/p&gt;
</content:encoded></item><item><title>TypeScript - uma longa introdução</title><link>https://otaviomiranda.com.br/2020/typescript-uma-longa-introducao/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2020/typescript-uma-longa-introducao/</guid><description>Sou Luiz Otávio Miranda e trabalho com desenvolvimento de softwares desde 2009 usando várias linguagens, bibliotecas e frameworks diferentes.</description><pubDate>Sun, 02 Sep 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Esse post vai te ajudar a entender melhor o que é o TypeScript de maneira geral.
Vamos entrar um pouco mais na parte conceitual sem aprofundarmos em detalhes
para que você saiba como e quando usá-lo.&lt;/p&gt;
&lt;p&gt;Atenção: não tenho o intuito de falar sobre tudo o que é possível sobre o TS.
Para isso, veja a
&lt;a href=&quot;https://www.typescriptlang.org/docs/home.html&quot;&gt;documentação oficial&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;O que é o TypeScript?&lt;/h2&gt;
&lt;p&gt;Segundo o site oficial, o &lt;em&gt;TypeScript é um superconjunto do JavaScript com
tipagem que é compilado para JavaScript simples&lt;/em&gt;
(&lt;a href=&quot;https://www.typescriptlang.org/&quot;&gt;texto original&lt;/a&gt;: &lt;em&gt;TypeScript is a typed
superset of JavaScript that compiles to plain JavaScript&lt;/em&gt;), ou seja, algo que é
adicionado sobre o JavaScript.&lt;/p&gt;
&lt;p&gt;No entanto, como poderíamos imaginar um “superconjunto” (&lt;em&gt;superset&lt;/em&gt;)? Isso é uma
linguagem de programação? Uma versão do JS? Uma quarta dimensão? De fato, um
&lt;em&gt;superset&lt;/em&gt; não explica muita coisa pra quem conhece o termo (&lt;em&gt;e eu não estou
brincando, existe uma outra dimensão no TS, você vai descobrir já já&lt;/em&gt;).&lt;/p&gt;
&lt;h3&gt;Comparação com versões do ECMAScript&lt;/h3&gt;
&lt;p&gt;Assim como cada versão do ECMAScript adiciona novos recursos ao JavaScript, o
TypeScript também o fará.&lt;/p&gt;
&lt;p&gt;Por exemplo, imagine as versões do ECMAScript (ES3, ES5, ES6, …, ES2020):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/JavaScript-Versions@2x-2048x1648.png&quot; alt=&quot;Figura 1. Cada nova versão do ECMAScript adiciona novos recursos ao JavaScript.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Figura 1. Cada nova versão do ECMAScript adiciona novos recursos ao JavaScript.&lt;/p&gt;
&lt;p&gt;Como podemos ver na &lt;strong&gt;Figura 1&lt;/strong&gt;, novas versões do ECMAScript adicionam novos
recursos ao JavaScript.&lt;/p&gt;
&lt;p&gt;Além disso, cada uma das versões é compatível com a versão anterior. Portanto,
você pode facilmente rodar um script ES5 em ambientes ES7, mas não o contrário.
Não é possível usar os recursos do ES7 em ambientes ES5, porque tais recursos
ainda não existem no ES5 (salvo se usarmos algum
&lt;a href=&quot;https://developer.mozilla.org/pt-BR/docs/Glossario/Polyfill&quot;&gt;polyfill&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Mas, mesmo tendo várias versões do ECMAScript, chamamos todas elas de Javascript
(a menos que você queira falar ou usar determinada especificidade de uma
versão).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Nota:&lt;/strong&gt; existem várias versões do ECMAScript, separei apenas três para
exemplo. Você pode ver todas elas
&lt;a href=&quot;https://en.wikipedia.org/wiki/ECMAScript&quot;&gt;aqui&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Onde o TypeScript se encaixa?&lt;/h3&gt;
&lt;p&gt;Se adicionarmos o TS como um layer, como fizemos na &lt;strong&gt;Figura 1&lt;/strong&gt;, esse diagrama
ficaria assim:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Typescript-is-a-javascript-superset-2048x1946.png&quot; alt=&quot;Figura 2. O TypeScript é um superset do JavaScript.&quot;&gt;
Figura 2. O TypeScript é um superset do JavaScript.&lt;/p&gt;
&lt;p&gt;Então, podemos perceber que o TS adiciona funcionalidades sobre o JS simples.
Portanto, é isso que o site oficial quer dizer quando detalha o TypeScript como
um superset do JavaScript.&lt;/p&gt;
&lt;h3&gt;O que realmente é o TypeScript&lt;/h3&gt;
&lt;p&gt;Aqui segue um resumão sobre o que eu acho que o TS realmente é: &lt;em&gt;O TypeScript é
um superset do JS criado pela Microsoft. Um JavaScript mais moderno e seguro,
que adiciona as últimas versões do ECMAScript, muitos recursos próprios e um
sistema extremamente rico para tipagem&lt;/em&gt;. Além disso, ele também adiciona
recursos muito úteis ao editor de código para facilitar a vida do desenvolvedor
(falaremos mais sobre isso mais adiante).&lt;/p&gt;
&lt;p&gt;Por falar em tipagem, uma das partes mais importantes do TypeScript são os tipos
(&lt;strong&gt;types&lt;/strong&gt;), por isso o nome &lt;strong&gt;Type&lt;/strong&gt;Script (acho que você já tinha percebido,
né?). De fato, esse é um dos fatores que costuma levar o TS para projetos de
larga escala em bases de código gigantescas. Vamos falar mais sobre a tipagem
mais adiante neste post também.&lt;/p&gt;
&lt;h2&gt;Linguagem interpretada ou linguagem compilada?&lt;/h2&gt;
&lt;p&gt;Depende do seu ponto de vista e do seu ambiente de desenvolvimento!&lt;/p&gt;
&lt;p&gt;Quando falamos em TypeScript, geralmente estamos imaginando o ambiente mais
comum atualmente (06/2020): &lt;a href=&quot;https://nodejs.org/en/download/&quot;&gt;Node.js&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Assim, se você pensar neste ambiente, é meio incomum falar de algo como o TS,
porque ele não seria uma
&lt;a href=&quot;https://pt.wikipedia.org/wiki/Linguagem_interpretada#:~:text=Linguagem%20interpretada%20%C3%A9%20uma%20linguagem,pelo%20sistema%20operacional%20ou%20processador.&quot;&gt;linguagem interpretada&lt;/a&gt;
(como &lt;a href=&quot;categoria/python/&quot;&gt;Python&lt;/a&gt;, Ruby, PHP…), nem uma
&lt;a href=&quot;https://pt.wikipedia.org/wiki/Linguagem_compilada#:~:text=Linguagem%20compilada%20%C3%A9%20uma%20linguagem,linguagem%20de%20baixo%20n%C3%ADvel%2C%20como&quot;&gt;linguagem que compila&lt;/a&gt;
para bytecode ou código de máquina como outras linguagens de mais baixo nível
fazem. O TS com Node.js seria uma linguagem que compila diretamente para outra
linguagem de alto nível, o JavaScript.&lt;/p&gt;
&lt;p&gt;Lembra da &lt;strong&gt;Figura 2&lt;/strong&gt;? Pois é, ao terminar de escrever o meu código TypeScript,
eu faria a compilação diretamente para JavaScript simples. Por isso, o
JavaScript gerado seria o meu código de produção, que ambos, Node.js e o
Browser, entendem. Portanto, minha aplicação em produção rodaria apenas
JavaScript.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/TypeScript-compilado-para-javascript-1-2048x1690.png&quot; alt=&quot;Figura 3. Processo de compilação do TypeScript em JavaScript usando Node.js&quot;&gt;&lt;/p&gt;
&lt;p&gt;Figura 3. Processo de compilação do TypeScript em JavaScript usando Node.js&lt;/p&gt;
&lt;h3&gt;Outra dimensão?&lt;/h3&gt;
&lt;p&gt;Lembra da nova dimensão que falei anteriormente? Então, é aqui que ela entra.
Tudo o que o TypeScript adiciona de recursos no momento do desenvolvimento, será
removido no código final compilado. Afinal, o Javascript não entende muitos dos
recursos adicionados pelo TS, no final das contas, o que a gente quer mesmo é o
código compilado, ou seja, o JS puro.&lt;/p&gt;
&lt;p&gt;A “dimensão do TypeScript” existirá apenas no seu código de desenvolvimento.&lt;/p&gt;
&lt;h3&gt;Um cenário diferente&lt;/h3&gt;
&lt;p&gt;Este acima seria o cenário Node.js, onde você precisa compilar o código para ter
o JS puro no back-end. Certamente, é o cenário mais usado hoje em dia e acho que
isso ainda vai perdurar por alguns anos.&lt;/p&gt;
&lt;p&gt;Porém, em 13 de maio de 2020, a
&lt;a href=&quot;https://deno.land/v1&quot;&gt;versão 1.0 do Deno foi lançada&lt;/a&gt; e ele interpreta
TypeScript puro, sem a necessidade de compilação. Então, olhando por este ponto
de vista, o &lt;strong&gt;TS seria uma linguagem de programação&lt;/strong&gt; com tipagem estática e
interpretada. Você ainda precisaria compilar o código para o front-end, mas não
para o back-end.&lt;/p&gt;
&lt;p&gt;Neste post, vou focar mais no lado “Node.js” da coisa, com o processo de
compilação.&lt;/p&gt;
&lt;h2&gt;Extensão .ts e .tsx&lt;/h2&gt;
&lt;p&gt;Arquivos TypeScript tem a extensão &lt;code&gt;.ts&lt;/code&gt; ou &lt;code&gt;.tsx&lt;/code&gt; ao invés de &lt;code&gt;.js&lt;/code&gt; ou &lt;code&gt;.jsx&lt;/code&gt; e
aqui cabe uma dica interessante: converter um arquivo JavaScript válido em
TypeScript válido é um processo relativamente simples, basta renomear a extensão
de &lt;code&gt;.js(x)&lt;/code&gt; para &lt;code&gt;.ts(x)&lt;/code&gt;. Dependendo da configuração do seu ambiente, o
TypeScript poderá compilar o seu código JavaScript sem nenhum problema (talvez
com alguns alertas, mas o código JS será gerado normalmente).&lt;/p&gt;
&lt;p&gt;Este é um dos muitos fatores que dão tanta popularidade ao TypeScript
atualmente. Muitos desenvolvedores estão migrando suas bases de código de
JavaScript para TypeScript por conta dessa simplicidade. Apenas configure seu
ambiente, renomeie um arquivo &lt;strong&gt;.js&lt;/strong&gt; para &lt;strong&gt;.ts&lt;/strong&gt; e pronto, estará em ambiente
TypeScript!&lt;/p&gt;
&lt;p&gt;Por exemplo, isso é TypeScript:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function greet(name) {
  console.log(`Olá, ${name}!`);
}

greet(&amp;#39;Otávio Miranda&amp;#39;); // Olá, Otávio Miranda!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alguma diferença com o JS que você conhece? É claro que não usei nenhum recurso
do TypeScript neste código, mas eu poderia facilmente (e não acho que isso
dificultaria seu entendimento):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function greet(name: string): void {
  console.log(`Olá, ${name}!`);
}

greet(&amp;#39;Otávio Miranda&amp;#39;); // Olá, Otávio Miranda!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isto só é possível, porque se observarmos nossa &lt;strong&gt;Figura 2&lt;/strong&gt; o &lt;strong&gt;TypeScript É
JavaScript&lt;/strong&gt;. Portanto, assim como podemos rodar scripts ES5 em ambientes ES7
(ou superiores), o compilador do TypeScript não terá nenhum problema em entender
JavaScript puro. Por outro lado, assim como não conseguimos usar recursos do ES7
no ES5 sem um polyfill, também não conseguimos rodar TypeScript diretamente em
ambiente JavaScript. Nem o Browser, nem o Node.js entenderiam TS.&lt;/p&gt;
&lt;p&gt;O primeiro código (sem tipagem) rodaria perfeitamente tanto em ambientes
JavaScript quanto TypeScript; o segundo (com tipagem) não poderia ser
interpretado pelo JavaScript.&lt;/p&gt;
&lt;h2&gt;Por que o TypeScript é mais seguro?&lt;/h2&gt;
&lt;p&gt;Considere este código JavaScript abaixo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const name = &amp;#39;Otávio Miranda&amp;#39;;
const upperCaseName = name.toUppercase();
console.log(upperCaseName);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agora, me diga: encontrou o erro? Sim! Ele tem um erro que só seria detectado no
momento da execução (runtime). Porém, quase posso garantir que, ao passar o olho
sobre este código, você não o detectou facilmente, estou certo?&lt;/p&gt;
&lt;p&gt;Se um código assim fosse para produção, ao ser executado seus usuários veriam
algo como:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;Uncaught TypeError: name.toUppercase is not a function
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ou, para um usuário comum, ele simplesmente veria seu programa parar de
funcionar sem saber o motivo.&lt;/p&gt;
&lt;p&gt;Mas, se você colocar este código em um ambiente TypeScript, antes mesmo da
execução do seu código este erro seria detectado.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/typescript-error-1.png&quot; alt=&quot;Figura 4. O TypeScript detecta o erro antes que você possa executar seu
código.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Figura 4. O TypeScript detecta o erro antes que você possa executar seu código.&lt;/p&gt;
&lt;h3&gt;Conveniência da &lt;em&gt;Intelligent code completion&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;Além de analisar o meu código enquanto eu digito sem deixar o meu editor lento,
a mensagem do TypeScript ainda me informa qual o erro, qual o tipo da variável e
ainda me da uma sugestão sobre o que eu quis dizer. Nesse caso, se você ainda
não detectou, &lt;code&gt;toUppercase&lt;/code&gt; não existe no tipo &lt;code&gt;string&lt;/code&gt;, o correto seria
&lt;code&gt;toUpperCase&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Por fim, veja esse outro código (aqui o erro é explícito)?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;console.log([1, 2, 3] * 2); // NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Viu o erro? No entanto, o JavaScript não tem problema em permitir que meu código
rode assim, multiplicando um array por um número.&lt;/p&gt;
&lt;p&gt;Porém, veja o que acontece quando migramos para TypeScript:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;console.log([1, 2, 3] * 2); // NaN
//          ^ The left-hand side of an
//            arithmetic operation must
//            be of type &amp;#39;any&amp;#39;, &amp;#39;number&amp;#39;,
//            &amp;#39;bigint&amp;#39; or an enum
//            type.ts(2362)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Por isso que o TypeScript deixa nossos códigos mais seguros.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; vou representar esses erros com &lt;strong&gt;comentários de código&lt;/strong&gt; (se
necessário) ao invés de imagens pra facilitar minha vida. O &lt;code&gt;^&lt;/code&gt; representa onde
o erro será exibido.&lt;/p&gt;
&lt;p&gt;Esses erros são bem simples e fáceis de encontrar e corrigir. Mas, você
provavelmente já sabe que um ambiente de produção é muito mais complexo que
isso: objetos podem ser aninhados, funções, classes e variáveis podem vir de
outros módulos, bases de dados e o ambiente podem prover APIs (como a DOM, do
browser por exemplo), e assim por diante. Esses são os casos onde o TypeScript
brilha ainda mais.&lt;/p&gt;
&lt;p&gt;Mas, espera aí! Como o TypeScript está vendo erros no meu código sendo que eu
ainda nem o compilei? Este, minha amiga ou meu amigo, é trabalho do &lt;code&gt;tsserver&lt;/code&gt; e
do Typechecker do TypeScript. Que vamos falar logo a seguir.&lt;/p&gt;
&lt;h2&gt;TypeScript – Typechecker e tsserver&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; você realmente não precisa saber de nada disso pra programar em
TypeScript ou JavaScript.&lt;/p&gt;
&lt;p&gt;Programas são feitos por humanos e para humanos
(&lt;a href=&quot;https://amzn.to/2B1IZlg&quot;&gt;pelo menos alguns&lt;/a&gt; rsrs), isso quer dizer que a
linguagem de programação que escolhemos funciona exatamente como um “Idioma”
qualquer que será traduzido para que o dispositivo entenda e execute de alguma
forma. Mas, seu código precisa chegar na engine do JS de alguma forma, certo?
Vamos ver.&lt;/p&gt;
&lt;h3&gt;Como seu código Javascript vai parar na engine do JS&lt;/h3&gt;
&lt;p&gt;Este processo é feito no JavaScript seguindo os seguintes passos (de forma bem
resumida):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Seu código JavaScript é convertido em uma
&lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot;&gt;AST&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;A AST é convertida em &lt;a href=&quot;https://pt.wikipedia.org/wiki/Bytecode&quot;&gt;Bytecode&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;O Bytecode é avaliado pela engine.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Perceba que entre você criar seu código e ele ser executado pela engine do JS,
nada além de uma conversão ocorre do seu código. Se ocorrer um erro (algo
digitado incorretamente, por exemplo), seu código vai ser executado com erro e a
engine vai parar no ato do erro. Por isso vemos
&lt;code&gt;Uncaught TypeError: Bla bla bla&lt;/code&gt; por não tratar essa exceção, só depois de
executar o código.&lt;/p&gt;
&lt;h3&gt;Como seu código TypeScript vai parar na engine do JS&lt;/h3&gt;
&lt;p&gt;Ao adicionar o TypeScript, esse processo fica bem maior, veja:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Seu código TypeScript é convertido em uma AST (TS)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A AST é analisada pelo Typechecker (TS)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A AST é convertida em código JavaScript (TS)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;O código JavaScript é convertido em uma AST (JS)&lt;/li&gt;
&lt;li&gt;A AST é convertida em Bytecode (JS);&lt;/li&gt;
&lt;li&gt;O Bytecode é avaliado pela engine (JS);&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Perceba que as partes em negrito foram adicionadas ao processo. Passos de 1 a 3
são executados pelo TypeScript; passos de 4 a 6 são executados pelo JavaScript.
A tipagem é checada nos passos 1 e 2 (do TS). Do passo 3 em diante, o TypeScript
não vai mais checar seu código. Em outras palavras, a última coisa que o
compilador do TypeScript faz é compilar o seu código para Javascript, toda a
checagem é feita antes da compilação.&lt;/p&gt;
&lt;p&gt;Por este motivo, eu posso compilar meus códigos JavaScript sem tipagem pelo
TypeScript (como eu te disse, TypeScript é JavaScript). Também é por este
motivo, que eu posso configurar o nível de restrição do TypeScript (eu poderia,
por exemplo, não permitir a compilação se meu código tiver algum tipo de alerta
ou falta de tipagem).&lt;/p&gt;
&lt;h3&gt;tsserver&lt;/h3&gt;
&lt;p&gt;Eu falei, falei e falei, e acabei não respondendo sua pergunta: &lt;strong&gt;Como o
TypeScript está vendo erros no meu código sendo que eu ainda nem o compilei?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Quando instalamos o TypeScript, ganhamos duas coisas: o compilador (&lt;code&gt;tsc&lt;/code&gt;) e um
servidor que provê serviços da linguagem (&lt;code&gt;tsserver&lt;/code&gt;). De fato, você pode ver
eles na pasta “bin” do “node_modules”. Em nosso dia a dia como programador ou
programadora, não nos preocupamos com o &lt;code&gt;tsserver&lt;/code&gt; porque ele geralmente é usado
pelo seu editor ou IDE. Porém, ele é super importante pra gente. É ele que nos
permite ter auto completar, inspeções de código, navegação, refatoração, e muito
mais, diretamente no editor que usamos para criar nosso código.&lt;/p&gt;
&lt;p&gt;Então, respondendo a sua pergunta, o &lt;code&gt;tsserver&lt;/code&gt; roda em background em alguns
editores de código fazendo essa checagem. Quando ele encontra algo que não bate
com a minha configuração do TypeScript, ele grifa o trecho de código com aquela
linha vermelha ondulada dizendo o que está incorreto. Dessa forma, eu não
preciso executar meu código pra saber que existe um erro nele.&lt;/p&gt;
&lt;h3&gt;vscode&lt;/h3&gt;
&lt;p&gt;Eu não sei qual editor de códigos você usa, mas gosto bastante do
&lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VSCode&lt;/a&gt; porque ele já vem com o &lt;code&gt;tsserver&lt;/code&gt;
embutido (muito editores também já fazem isso). Com isso, eu posso digitar o meu
código tranquilamente, sabendo que enquanto o sublinhado vermelho não aparecer,
provavelmente meu código não tem nenhum erro (a não ser de lógica rsrs).&lt;/p&gt;
&lt;p&gt;Outra coisa interessante é que se eu passar o mouse sobre determinada variável,
eu sei exatamente qual o tipo dela. Isso em bases de código maiores é uma mão na
roda.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/typescript-2.png&quot; alt=&quot;Figura 5. VSCode + tsserver exibindo o tipo de uma variável&quot;&gt;&lt;/p&gt;
&lt;p&gt;Figura 5. VSCode + tsserver exibindo o tipo de uma variável&lt;/p&gt;
&lt;h4&gt;Fatos interessantes:&lt;/h4&gt;
&lt;p&gt;O VSCode foi criado pela Microsoft e foi escrito em TypeScript e JavaScript com
o &lt;a href=&quot;https://www.electronjs.org/&quot;&gt;Electron&lt;/a&gt;. Ele usa
&lt;a href=&quot;https://github.com/DefinitelyTyped/DefinitelyTyped&quot;&gt;type definitions&lt;/a&gt; do
TypeScript para a maioria das funções do seu
&lt;a href=&quot;https://code.visualstudio.com/docs/editor/intellisense&quot;&gt;IntelliSense&lt;/a&gt; tanto
para JavaScript quanto para TypeScript. Portanto, se você usa o VSCode para
editar seus códigos Javascript, você já faz uso extensivo de TypeScript (mesmo
sem saber).&lt;/p&gt;
&lt;h2&gt;Instalando e configurando o TypeScript&lt;/h2&gt;
&lt;p&gt;Se você chegou até aqui, provavelmente eu já despertei seu interesse em
TypeScript (ou talvez você já o tinha). De qualquer forma, que bom! Na minha
opinião, se você começar a usar o TS nos seus projetos, vai ser difícil voltar
atrás, vai por mim.&lt;/p&gt;
&lt;p&gt;Contudo, isso pode ser algo desafiador em meio a tantas opções, não é mesmo? A
seguir, vamos ver como iniciar um projeto TypeScript do zero. Embora eu não vá
conseguir te explicar tudo o que existe sobre ele em apenas um post, percebo que
o mais difícil é iniciar, o resto você pode ir aprendendo com o tempo e a
&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/basic-types.html&quot;&gt;documentação&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Instalação de programas&lt;/h3&gt;
&lt;p&gt;Eu gosto de usar algumas coisas nos meus projetos, por isso, a configuração que
você vai ver a seguir será a mesma que utilizo em muitos dos meus projetos.
Entretanto, tenha em mente que isso vai variar muito de desenvolvedor para
desenvolvedor e de projeto para projeto. Assim, é provável que você verá outras
configurações muito diferentes por aí, ou talvez você tenha que conversar com
seu time antes de sair configurando o ambiente de um projeto como um todo.&lt;/p&gt;
&lt;p&gt;Se você tem experiencia com isso, provavelmente poderá pular para
“&lt;strong&gt;&lt;a href=&quot;#Instalando_o_TypeScript,_tsnode_e_eslint&quot;&gt;Instalando o TypeScript, ts-node e eslint&lt;/a&gt;&lt;/strong&gt;“.&lt;/p&gt;
&lt;p&gt;Antes de começar, você vai precisar do seguinte (se quiser o mesmo ambiente que
o meu):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Baixar e &lt;a href=&quot;https://nodejs.org/en/download/&quot;&gt;instalar o Node.js&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Baixar e &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;instalar o VSCode&lt;/a&gt;;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Criando a pasta do projeto&lt;/h3&gt;
&lt;p&gt;No seu sistema operacional, crie uma pasta exclusiva para o seu projeto com o
TS. Isso é importante para termos uma pasta node_modules e package.json
exclusivos para este projeto.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; você pode migrar uma base de código existente apenas renomeando
arquivos &lt;strong&gt;.js&lt;/strong&gt; para &lt;strong&gt;.ts&lt;/strong&gt; e fazendo as configurações abaixo. Nesse caso use
&lt;code&gt;strict&lt;/code&gt; como &lt;code&gt;false&lt;/code&gt; (você vai ver isso abaixo). Além disso, verifique se todos
os módulos que você utiliza no projeto JS dão suporte para TS. Tenha em mente
que pode ser necessário modificar coisas complicadas para fazer essa migração.&lt;/p&gt;
&lt;p&gt;Para um projeto novo (recomendável), apenas abra a nova pasta pasta no VSCode,
indo em &lt;code&gt;File &amp;gt; Open Folder&lt;/code&gt; e escolhendo a pasta que você acabou de criar.&lt;/p&gt;
&lt;h3&gt;Instalando o TypeScript, ts-node e eslint&lt;/h3&gt;
&lt;p&gt;Abra o terminal do VSCode em &lt;code&gt;View &amp;gt; Terminal&lt;/code&gt; e digite o seguinte:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;npm init -y
npm i typescript ts-node eslint @types/node -D
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;typescript&lt;/code&gt; – é o próprio TypeScript (&lt;code&gt;tsc&lt;/code&gt; e &lt;code&gt;tsserver&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ts-node&lt;/code&gt; – permite executar scripts do TypeScript diretamente;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eslint&lt;/code&gt; – É o meu
&lt;a href=&quot;https://pt.stackoverflow.com/questions/330821/o-que-significa-executar-lint-no-c%C3%B3digo#:~:text=Um%20linter%20ou%20lint%20%C3%A9%20uma%20ferramenta%20de%20an%C3%A1lise%20est%C3%A1tica%20de%20c%C3%B3digo.&amp;text=Um%20linter%20ou%20lint%20se,c%C3%B3digo%2Dfonte%20em%20linguagem%20C.&quot;&gt;linter&lt;/a&gt;
preferido, algumas pessoas preferem o
&lt;a href=&quot;https://palantir.github.io/tslint/&quot;&gt;tslint&lt;/a&gt; (você pode conferir depois);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@types/node&lt;/code&gt; – São as definições de tipo para o Node.js&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Extensões do VSCode&lt;/h3&gt;
&lt;p&gt;Para uso do ESLint e do ts-node, eu gosto de duas extensões no VSCode.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=formulahendry.code-runner&quot;&gt;Code Runner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint&quot;&gt;ESLint&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instale ambas as extensões no seu VSCode&lt;/p&gt;
&lt;p&gt;O &lt;strong&gt;Code Runner&lt;/strong&gt; habilita a possibilidade executar um comando apenas
pressionando um botão de “Play” no canto superior direito do seu VSCode. Assim,
ao invés de digitar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;npx ts-node index.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eu posso simplesmente configurar o &lt;strong&gt;Code Runner&lt;/strong&gt; para executar automaticamente
este comando para todos os meus arquivos TypeScript assim que eu pressionar o
play. Isso é fantástico para executar rapidamente seus código e focar no
aprendizado.&lt;/p&gt;
&lt;p&gt;O &lt;strong&gt;Eslint&lt;/strong&gt; (a extensão) faz a integração do &lt;strong&gt;ESLint&lt;/strong&gt; (linter) com o VSCode.&lt;/p&gt;
&lt;h3&gt;Configurando o ESLint&lt;/h3&gt;
&lt;p&gt;Vamos instalar alguns plugins para que o ESLint funcione com o TypeScript. Para
isso, digite no terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;npm i @typescript-eslint/eslint-plugin @typescript-eslint/parser -D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Crie um arquivo com o nome .eslintrc.js na raiz do seu projeto e cole o seguinte
nele:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;module.exports = {
  env: {
    browser: true,
    es6: true,
    node: true,
  },
  extends: [
    &amp;#39;eslint:recommended&amp;#39;,
    &amp;#39;plugin:@typescript-eslint/eslint-recommended&amp;#39;,
    &amp;#39;plugin:@typescript-eslint/recommended&amp;#39;,
  ],
  globals: {
    Atomics: &amp;#39;readonly&amp;#39;,
    SharedArrayBuffer: &amp;#39;readonly&amp;#39;,
  },
  parser: &amp;#39;@typescript-eslint/parser&amp;#39;,
  parserOptions: {
    ecmaVersion: 11,
    sourceType: &amp;#39;module&amp;#39;,
  },
  plugins: [&amp;#39;@typescript-eslint&amp;#39;],
  rules: {},
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E é só, agora você já tem o ESLint integrado ao seu VSCode e configurado. No
entanto, não configurei nenhuma regra do ESLint justamente para que você possa
fazer suas próprias escolhas. Seu projeto usa aspas simples ou duplas? Requer
ponto e vírgula? Usa tabs ou espaços? Quantos? Enfim, essas são decisões que
você precisa tomar.&lt;/p&gt;
&lt;p&gt;Eu gosto de usar as regras do &lt;a href=&quot;https://prettier.io/&quot;&gt;Prettier&lt;/a&gt;, com ponto e
vírgula, aspas simples e 2 espaços (vou mostrar como configurar mais abaixo).&lt;/p&gt;
&lt;h3&gt;Configurando o Code Runner&lt;/h3&gt;
&lt;p&gt;No seu projeto, crie uma pasta chamada de &lt;code&gt;.vscode&lt;/code&gt;. Nessa pasta, crie um
arquivo chamado de &lt;code&gt;settings.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Cole o seguinte neste arquivo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;code-runner.executorMap&amp;quot;: {
    &amp;quot;typescript&amp;quot;: &amp;quot;npx ts-node --files&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note que este arquivo pode já existir. Caso positivo, adicione a configuração
acima junto com a configuração anterior que pode já estar no arquivo
settings.json.&lt;/p&gt;
&lt;p&gt;Por exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;window.zoomLevel&amp;quot;: 0,
  &amp;quot;code-runner.executorMap&amp;quot;: {
    &amp;quot;typescript&amp;quot;: &amp;quot;npx ts-node --files&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No arquivo acima, estou simulando que já existia a configuração
&lt;code&gt;&amp;quot;window.zoomLevel&amp;quot;&lt;/code&gt;, então adicionei as configurações do &lt;strong&gt;Code Runner&lt;/strong&gt; junto.&lt;/p&gt;
&lt;p&gt;Agora você pode clicar no “Play” no canto superior direito sempre que quiser
executar o seu arquivo TypeScript.&lt;/p&gt;
&lt;h3&gt;tsconfig.json&lt;/h3&gt;
&lt;p&gt;Você pode compilar seus arquivos TypeScript em JavaScript de duas maneiras
diferentes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;com parâmetros via linha de comando&lt;/li&gt;
&lt;li&gt;com parâmetros via &lt;code&gt;tsconfig.json&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No entanto, não recomendo o uso da primeira opção.&lt;/p&gt;
&lt;p&gt;Assim, crie um arquivo chamado &lt;code&gt;tsconfig.json&lt;/code&gt; na raiz do seu projeto e cole o
seguinte:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;compilerOptions&amp;quot;: {
    &amp;quot;lib&amp;quot;: [&amp;quot;es2015&amp;quot;],
    &amp;quot;module&amp;quot;: &amp;quot;commonjs&amp;quot;,
    &amp;quot;outDir&amp;quot;: &amp;quot;dist&amp;quot;,
    &amp;quot;strict&amp;quot;: true,
    &amp;quot;target&amp;quot;: &amp;quot;es2015&amp;quot;,
    &amp;quot;esModuleInterop&amp;quot;: true
  },
  &amp;quot;include&amp;quot;: [&amp;quot;src&amp;quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vamos entender o que fizemos, afinal, é pra isso que você está aqui:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lib&lt;/code&gt; – configura o ambiente que o TSC vai assumir que você está usando.
Assim, es2015 assume que o ambiente no qual você vai rodar seu código é
compatível com o ECMAScript 2015.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;module&lt;/code&gt; – configura qual o sistema de módulos o TSC vai usar para compilar
seu código. Geralmente, escolho ‘&lt;code&gt;common.js&lt;/code&gt;‘ e ativo ‘&lt;code&gt;esModuleInterop&lt;/code&gt;‘.
Isso me permite usar Import/Export em qualquer ambiente. No entanto, se eu
fosse usar algum ambiente front-end, usaria o
&lt;a href=&quot;https://webpack.js.org/&quot;&gt;webpack&lt;/a&gt; para fazer meu &lt;em&gt;bundle&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;outDir&lt;/code&gt; – É a pasta de saída do seu código. Quando você compilar, a pasta de
saída terá a mesma estrutura da pasta de entrada. Então, seus arquivos de
produção estarão em “dist” após a compilação.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strict&lt;/code&gt; – Esse é o modo recomendável para uso do TS, ativa várias
&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/compiler-options.html&quot;&gt;flags restritivas no TSC&lt;/a&gt;.
Qualquer coisa que não estiver em conformidade, o compilador vai reclamar.
Porém, isso só é recomendável se você estiver iniciando um projeto do zero com
TS, se for migrar um código antigo de JS para TS, configure como &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;target&lt;/code&gt; – configura a versão ECMASCript que o TSC vai usar para compilar seu
código (ES3, ES5, …, ES2020, ESNext). Entretanto, o ambiente de produção do
seu código precisará suportar a versão escolhida.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;esModuleInterop&lt;/code&gt; – Ver &lt;code&gt;module&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;include&lt;/code&gt; – quais pastas o &lt;code&gt;tsc&lt;/code&gt; vai analisar para compilar.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Testando tudo&lt;/h3&gt;
&lt;p&gt;Como você pôde perceber na configuração anterior, nossa pasta de entrada será
“&lt;strong&gt;src&lt;/strong&gt;” e de saída “&lt;strong&gt;dist&lt;/strong&gt;“. Embora você possa usar a configuração que
preferir, essa é uma convenção muito utilizada atualmente. Então, prefira
mantê-la.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; a pasta &lt;strong&gt;src&lt;/strong&gt; precisa ser criada manualmente, assim como os arquivos
&lt;strong&gt;.ts&lt;/strong&gt; dentro dela.&lt;/p&gt;
&lt;p&gt;Vamos criar todos os nossos arquivos com extensões &lt;strong&gt;.ts&lt;/strong&gt; na pasta &lt;strong&gt;src&lt;/strong&gt;.
Eventualmente, vamos compilar nosso código simplesmente digitando “&lt;strong&gt;tsc&lt;/strong&gt;” (sem
aspas) no terminal.&lt;/p&gt;
&lt;p&gt;Você terá duas opções para executar seu código:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Usando o ts-node&lt;/li&gt;
&lt;li&gt;Usando o tsc&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Código de exemplo&lt;/h4&gt;
&lt;p&gt;Embora eu ainda não tenha especificado como escrever códigos TS. Crie o arquivo
&lt;code&gt;src/index.ts&lt;/code&gt; (arquivo &lt;em&gt;index.ts&lt;/em&gt; dentro da pasta &lt;em&gt;src&lt;/em&gt;) e digite o seguinte
para testar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const name = &amp;#39;Otávio Miranda&amp;#39;;
console.log(name);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Conforme configuramos o &lt;code&gt;tsconfig.json&lt;/code&gt; com &lt;code&gt;strict = true&lt;/code&gt;, você não
conseguiria executar qualquer JS sem tipagem adequada (alguns sim, outros não).
No trecho acima estou usando a inferência de tipos do TypeScript. Porém, se
quiser executar qualquer JS normal (sem tipagem) dentro do seu script TS,
modifique &lt;code&gt;strict&lt;/code&gt; para &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;ts-node&lt;/h4&gt;
&lt;p&gt;Para usar o &lt;code&gt;ts-node&lt;/code&gt;, digite seu código dentro de um arquivo (suponha
&lt;code&gt;index.ts&lt;/code&gt;) ou use meu código de exemplo (acima) e pressione o play no canto
superior direito da tela. Similarmente, você pode simplesmente digitar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;npx ts-node src/index.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se o arquivo &lt;code&gt;index.ts&lt;/code&gt; importar qualquer outro módulo, ele será compilado
temporariamente apenas para exibir o resultado no terminal. Embora isso não
compile códigos reais, é super interessante para execução rápida de códigos
&lt;em&gt;&lt;a href=&quot;https://www.mairovergara.com/on-the-fly-o-que-significa-esta-expressao/&quot;&gt;on the fly&lt;/a&gt;&lt;/em&gt;.
Contudo, você vai precisar compilar seu código em algum momento. Para isso, use
&lt;code&gt;tsc&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;tsc&lt;/h4&gt;
&lt;p&gt;Anteriormente, vimos uma maneira simples para compilar códigos temporariamente
(sem criar novos arquivos) com &lt;code&gt;ts-node&lt;/code&gt;. Contudo, você vai precisar dos
arquivos para produção, afinal, o código JS é que será executado no ambiente de
produção. Portanto, para compilar realmente seu código (gerando os arquivos na
pasta &lt;strong&gt;dist&lt;/strong&gt;), simplesmente digite no terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;npx tsc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O compilador irá usar as configurações no &lt;code&gt;tsconfig.json&lt;/code&gt; a fim de gerar nossos
arquivos JS. Além disso, ele criará e atualizará a pasta “&lt;strong&gt;dist&lt;/strong&gt;” com os
arquivos de saída. Embora não necessário, dê uma olhada nos arquivos gerados na
pasta &lt;strong&gt;dist&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Depois que compilei o código &lt;strong&gt;TS&lt;/strong&gt;, meu arquivo &lt;code&gt;dist/index.js&lt;/code&gt; ficou assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&amp;#39;use strict&amp;#39;;
const name = &amp;#39;Otávio Miranda&amp;#39;;
console.log(name);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ou seja, quase nenhuma diferença do arquivo original. Todavia, os arquivos reais
de produção devem ficar extremamente diferentes dos originais, vai por mim.&lt;/p&gt;
&lt;p&gt;Uma outra forma de compilação é o modo “&lt;strong&gt;watch&lt;/strong&gt;“, onde o &lt;code&gt;tsc&lt;/code&gt; fica
“&lt;em&gt;assistindo&lt;/em&gt;” mudanças no seu código a fim de gerar automaticamente os arquivos
de saída. Para usar o modo “&lt;strong&gt;watch&lt;/strong&gt;“, digite:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;npx tsc -w
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Você vai perceber que o &lt;code&gt;tsc&lt;/code&gt; vai ficar assistindo modificações no seu código e
atualizando a saída assim que você salvar seu arquivo. Para parar, pressione
CTRL + C no terminal.&lt;/p&gt;
&lt;h3&gt;Prettier e formatação automática (opcional)&lt;/h3&gt;
&lt;p&gt;Como eu te disse anteriormente, gosto bastante de usar as configurações do
“Prettier” e a formatação automática em meus códigos. Embora opcional, você pode
fazer essa configuração como mostro abaixo.&lt;/p&gt;
&lt;h4&gt;Instalando os pacotes do Prettier&lt;/h4&gt;
&lt;p&gt;Digite o seguinte no terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;npm i prettier eslint-config-prettier eslint-plugin-prettier -D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Não tenho muito o que te explicar sobre isso, são pacotes necessários para o
funcionamento do Prettier com o ESLint.&lt;/p&gt;
&lt;p&gt;Crie um arquivo chamado .prettierrc.js na raiz do seu projeto e cole o seguinte:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;module.exports = {
  semi: true,
  trailingComma: &amp;#39;all&amp;#39;,
  singleQuote: true,
  printWidth: 80,
  tabWidth: 2,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As configurações são as seguintes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;semi&lt;/code&gt; – força o uso de ponto e vírgula;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;trailingComma&lt;/code&gt; – deixa uma vírgula ao final de arrays, objetos, etc;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;singleQuote&lt;/code&gt; – prefere o uso de aspas simples&lt;/li&gt;
&lt;li&gt;&lt;code&gt;printWidth&lt;/code&gt; – tenta quebrar linhas com 80 caracteres ou mais;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tabWidth&lt;/code&gt; – usa dois espaços para tab;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Configurando a formatação automática&lt;/h4&gt;
&lt;p&gt;O legal é que agora você pode acessar as configurações do VSCode e solicitar a
formatação automática afim de formatar seu arquivo assim que você salvá-lo. Para
isso, clique em &lt;code&gt;&amp;quot;File&amp;quot; &amp;gt; &amp;quot;Preferences&amp;quot; &amp;gt; &amp;quot;Settings&amp;quot;&lt;/code&gt;. No canto superior direito
da tela, clique no ícone &lt;code&gt;&amp;quot;Open settings (JSON)&amp;quot;&lt;/code&gt;. Por fim, cole o seguinte nas
configurações:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsonc&quot;&gt;{
  // Configurações já existentes
  &amp;quot;editor.codeActionsOnSave&amp;quot;: {
    &amp;quot;source.fixAll.eslint&amp;quot;: true,
    &amp;quot;source.fixAll&amp;quot;: true,
  },
  // Mais configurações existentes
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Assim, quando você salvar o seu arquivo, ele será automaticamente formatado com
o Prettier e o ESLint.&lt;/p&gt;
&lt;h4&gt;Ajustando o eslint&lt;/h4&gt;
&lt;p&gt;Agora, precisamos adicionar o &lt;em&gt;prettier&lt;/em&gt; na configuração “&lt;em&gt;extends&lt;/em&gt;” do seu
&lt;code&gt;.eslintrc.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Abra o &lt;code&gt;.eslintrc.js&lt;/code&gt;, que atualmente deve estar assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;module.exports = {
  // outras configs
  extends: [
    &amp;#39;eslint:recommended&amp;#39;,
    &amp;#39;plugin:@typescript-eslint/eslint-recommended&amp;#39;,
    &amp;#39;plugin:@typescript-eslint/recommended&amp;#39;,
  ],
  // outras configs
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Em extends, adicione &amp;#39;plugin:prettier/recommended&amp;#39;, dessa maneira:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;module.exports = {
  // outras configs
  extends: [
    &amp;#39;eslint:recommended&amp;#39;,
    &amp;#39;plugin:@typescript-eslint/eslint-recommended&amp;#39;,
    &amp;#39;plugin:@typescript-eslint/recommended&amp;#39;,
    &amp;#39;plugin:prettier/recommended&amp;#39;,
  ],
  // outras configs
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Como na &lt;strong&gt;linha 7&lt;/strong&gt; do exemplo acima.&lt;/p&gt;
&lt;p&gt;Se você adicionou tudo corretamente, agora o &lt;em&gt;prettier&lt;/em&gt; e a &lt;em&gt;formatação
automática&lt;/em&gt; já devem estar funcionando.&lt;/p&gt;
&lt;h2&gt;Tipos básicos e inferência de tipos do TypeScript&lt;/h2&gt;
&lt;p&gt;Até aqui você leu coisas superficiais sobre o TypeScript. Você sabe o que é,
como configurar, mas ainda não viu uma das coisas que faz ele ser o que é e ter
a popularidade que tem atualmente, os &lt;strong&gt;Tipos&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Um perigo: A tipagem fraca e dinâmica do JS&lt;/h3&gt;
&lt;p&gt;O Javascript é uma linguagem de tipagem fraca e dinâmica. Fraca porque você
consegue fazer coisas como &lt;code&gt;10 / &amp;#39;20&amp;#39;&lt;/code&gt; e o JavaScript se vira de alguma maneira
pra retornar um &lt;code&gt;0.5&lt;/code&gt;. Quem não conhece JS pode assustar com esse resultado (mas
está certo).&lt;/p&gt;
&lt;p&gt;Isso pode ser algo ruim do ponto de vista lógico, quando &lt;code&gt;10 / &amp;#39;20&amp;#39;&lt;/code&gt; um seria
&lt;code&gt;0.5&lt;/code&gt;? Só podemos presumir que isso foi uma coerção de tipos, portanto,
entendemos um dos valores foi convertido pra outro tipo automaticamente. A
string 20 foi convertida em number. Isso caracteriza uma linguagem de &lt;strong&gt;tipagem
fraca&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Linguagens de tipagem forte, provavelmente levantariam uma exceção neste ponto
do seu código.&lt;/p&gt;
&lt;p&gt;E dinâmica porque você não precisa declarar os tipos com antecedência e o tipo
pode mudar ao longo do código, uma variável que muda de valor, também pode mudar
de tipo. Em momento algum eu preciso falar que &lt;code&gt;10&lt;/code&gt; é um &lt;code&gt;number&lt;/code&gt;, que &lt;code&gt;true&lt;/code&gt; é
um &lt;code&gt;boolean&lt;/code&gt;, que &lt;code&gt;&amp;#39;Luiz&amp;#39;&lt;/code&gt; é uma &lt;code&gt;string&lt;/code&gt;. Ele faz isso automaticamente também.
Isso caracteriza uma linguagem de &lt;strong&gt;tipagem dinâmica&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Essas duas coisas combinadas podem trazer um benefício excelente, a
&lt;strong&gt;flexibilidade&lt;/strong&gt;. É algo extremamente simples escrever códigos em Javascript.
Mas, aqui também mora um perigo iminente. Programadores precisam tomar um
&lt;strong&gt;cuidado extremo&lt;/strong&gt; e escrever testes além do necessário apenas para garantir
que os tipos dos valores estão corretos. Do contrário, um erro assim poderia
chegar em produção sem que soubéssemos:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;10 / [20, 10]; // NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;A tipagem forte, estática e inferida do TypeScript&lt;/h3&gt;
&lt;p&gt;Por outro lado, o TS tem tipagem forte, estática e inferida (a inferência é
muito importante aqui, atenção).&lt;/p&gt;
&lt;p&gt;Se eu escrevo um código assim no TypeScript, imediatamente tenho um erro, veja:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;let number1 = 10; // number
let number2 = &amp;#39;20&amp;#39;; // string
let number3 = number1 / number2;
//                      ^
// The right-hand side of an arithmetic
// operation must be of type &amp;#39;any&amp;#39;,
// &amp;#39;number&amp;#39;, &amp;#39;bigint&amp;#39; or an enum type.ts(2363)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aqui o TS usa um recurso muito interessante chamado de &lt;strong&gt;inferência de tipos&lt;/strong&gt;.
Perceba que eu deixei comentado os tipos das duas primeiras variáveis (a última
foi um erro). Esses tipos foram inferidos automaticamente pelo TypeScript, em
momento algum eu disse que &lt;code&gt;10&lt;/code&gt; era um &lt;code&gt;number&lt;/code&gt; e que &lt;code&gt;&amp;#39;20&amp;#39;&lt;/code&gt; era uma &lt;code&gt;string&lt;/code&gt;.
Mas está igual ao JS, tipagem dinâmica? Não!&lt;/p&gt;
&lt;p&gt;A inferência de tipos é uma forma do TypeScript modelar o comportamento do
JavaScript e também de deixar o seu código mais limpo. Você não precisa
adicionar tipagem em coisas óbvias (salvo em casos onde ele não consegue inferir
um tipo). No entanto, uma vez que o tipo for inferido, ele não poderá mais ser
alterado (e aqui estamos falando em tipo, não em valor).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;let number1 = 10; // number
number1 = &amp;#39;20&amp;#39;;
//        ^
// Type &amp;#39;&amp;quot;20&amp;quot;&amp;#39; is not assignable to type &amp;#39;number&amp;#39;.ts(2322)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Em conclusão, minha dica pra você que está iniciando com o TS seria: só coloque
tipos em coisas que não são óbvias, evite sair adicionando tipo em todas as suas
variáveis. Quando o TS não conseguir inferir um tipo, ele vai te avisar. Use o
seu editor para saber qual tipo foi inferido. Geralmente basta passar o mouse
sobre a variável e o editor (como o VSCode) irá lhe informar o tipo da variável.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Peek-14-06-2020-17-38.gif&quot; alt=&quot;Figura 6. A inferência de tipos consegue saber o tipo de retorno da função e repassar isso para a variável.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Figura 6. A inferência de tipos consegue saber o tipo de retorno da função e
repassar isso para a variável.&lt;/p&gt;
&lt;h3&gt;Tipos mais básicos&lt;/h3&gt;
&lt;p&gt;O TS suporta todos os tipos que você tem o costume de usar em JS, como: boolean,
number, bigint, symbol, string, array, null, undefined e object. Além disso, ele
também adiciona seus próprios tipos, como: any, tuple, void, never e Enum.&lt;/p&gt;
&lt;p&gt;A maneira que eu tenho para informar qual o tipo da minha variável, parâmetro ou
retorno de funções e métodos é com &lt;code&gt;:&lt;/code&gt; (dois pontos). Existem algumas outras
maneiras, mas vamos deixar isso de lado por enquanto.&lt;/p&gt;
&lt;p&gt;Por exemplo, aqui eu vou &lt;strong&gt;quebrar minha própria dica (sobre inferência)&lt;/strong&gt; e vou
adicionar os tipos em tudo o que for possível só pra você ver a estrutura:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;/* eslint-disable */
// Se você não desativar o ESLint aqui, não conseguirá
// declarar esses tipos que seriam inferidos naturalmente
const name: string = &amp;#39;Luiz&amp;#39;; // string
const age: number = 30; // number
const birthday: Date = new Date(&amp;#39;1990-06-14T00:00:00-03:00&amp;#39;); // Date
const addresses: Array&amp;lt;string&amp;gt; = [&amp;#39;Rua 1&amp;#39;, &amp;#39;Rua 2&amp;#39;]; // array de strings
const parentNames: string[] = [&amp;#39;João&amp;#39;, &amp;#39;Maria&amp;#39;]; // array de strings
const isEmployed: boolean = true; // boolean

console.log(`Meu nome é ${name} e tenho ${age} anos.`);
console.log(
  `Nasci em ${birthday.toLocaleString()}, moro em ${addresses.join(&amp;#39;, &amp;#39;)}.`,
);
console.log(`Meus pais são ${parentNames.join(&amp;#39; e &amp;#39;)}`);
console.log(`Eu ${isEmployed ? &amp;#39;estou&amp;#39; : &amp;#39;não estou&amp;#39;} empregado atualmente.`);

/*
Saída:
Meu nome é Luiz e tenho 30 anos.
Nasci em 14/06/1990 00:00:00, moro em Rua 1, Rua 2.
Meus pais são João e Maria
Eu estou empregado atualmente.
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que todos esses tipos seriam inferidos naturalmente por serem óbvios,
basta o TS olhar o valor para saber o tipo. Então, eu poderia limpar
drasticamente meu código os removendo.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const name = &amp;#39;Luiz&amp;#39;; // string
const age = 30; // number
const birthday = new Date(&amp;#39;1990-06-14T00:00:00-03:00&amp;#39;); // Date
const addresses = [&amp;#39;Rua 1&amp;#39;, &amp;#39;Rua 2&amp;#39;]; // array de strings
const parentNames = [&amp;#39;João&amp;#39;, &amp;#39;Maria&amp;#39;]; // array de strings
const isEmployed = true; // boolean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Menos em objetos um pouco mais complexos, como array e date, o TypeScript foi
capaz de inferir os tipos.&lt;/p&gt;
&lt;p&gt;O ideal seria você adicionar tipos em coisas onde o tipo não é tão óbvio assim.
Por exemplo, os parâmetros de uma função não são óbvios, eu poderia passar
literalmente qualquer coisa sem que o JS reclamasse. Esses parâmetros devem ter
tipos em TypeScript.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Essa função precisa receber um array de números
// posso representar isso com number[] ou Array&amp;lt;number&amp;gt;
const sumNumbers = (arrayOfNumbers: number[]) =&amp;gt; {
  return arrayOfNumbers.reduce((s, v) =&amp;gt; s + v);
};

const result = sumNumbers([1, 2, 3]); // number
console.log(result); // 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Além disso, eu também poderia representar isso com o
&lt;a href=&quot;https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Functions/rest_parameters&quot;&gt;rest operator (…)&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Agora qualquer parâmetro dessa função deve
// ser um número
const sumNumbers = (...nums: number[]) =&amp;gt; {
  return nums.reduce((s, v) =&amp;gt; s + v);
};

const result = sumNumbers(1, 2, 3); // number
console.log(result); // 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Conforme você também pôde notar nos códigos anteriores, a maioria dos tipos
básicos são adicionados com letra minúscula: boolean, number, bigint, symbol,
string, array, null, undefined e object.&lt;/p&gt;
&lt;h3&gt;Tipos básicos que existem apenas no TypeScript&lt;/h3&gt;
&lt;p&gt;Alguns dos tipos que indiquei anteriormente, existem apenas no TypeScript, como
any, tuple, void, never e Enum. Então vamos ver quando usá-los:&lt;/p&gt;
&lt;h4&gt;any&lt;/h4&gt;
&lt;p&gt;Esse é um tipo que você não gostaria de ter no seu código (a não ser que não
tenha outra opção). Significa a mesma coisa que “qualquer coisa” (assim como no
JS).&lt;/p&gt;
&lt;p&gt;O fato aqui é que tudo precisa ter um tipo em tempo de compilação no TypeScript,
quando você não fornecer um tipo e o TSC também não conseguir inferir um tipo
correto, o padrão será “any” (qualquer coisa). Assim, você não terá funções de
intelliSense (auto completar, erros, etc). O any aceitará literalmente qualquer
coisa que você pedir pra ele fazer.&lt;/p&gt;
&lt;p&gt;Por exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Agora a função recebe qualquer coisa
const sumNumbers = (...anything: any[]) =&amp;gt; {
  return anything.reduce((s, v) =&amp;gt; s + v);
};

const result = sumNumbers(1, 2, &amp;#39;a&amp;#39;); // O tipo dessa variável é any
console.log(result); // 3a &amp;lt;- Resultado inesperado
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Evite usar &lt;code&gt;any&lt;/code&gt; a todo o custo. Quase sempre tem uma opção melhor.&lt;/p&gt;
&lt;h4&gt;tuple&lt;/h4&gt;
&lt;p&gt;Uma tupla é um array de tamanho fixo. Por exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// O tipo é [string, number]
const personFirstNameAndAge: [string, number] = [&amp;#39;Luiz&amp;#39;, 30];
console.log(personFirstNameAndAge);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que a tupla em TypeScript é representada como um array, porém ela é
muito mais específica. Os tipos precisam bater exatamente com os valores. No
exemplo acima, minha tupla precisa ter uma string na posição 0 e um número na
posição 1. Eu consigo alterar os valores apenas se forem do mesmo tipo.&lt;/p&gt;
&lt;p&gt;Tupas podem ter quantos valores você quiser, não são apenas dois como no exemplo
acima.&lt;/p&gt;
&lt;h4&gt;void&lt;/h4&gt;
&lt;p&gt;Embora represente um “não valor”, é interessante em alguns casos representar um
método ou função que não tenha retorno, por exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function showLog(): void {
  console.log(&amp;#39;Hey, sou o log.&amp;#39;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Essa função não tem retorno, por isso representamos seu retorno com “void”.&lt;/p&gt;
&lt;h4&gt;never&lt;/h4&gt;
&lt;p&gt;Você já imaginou um método ou função que NUNCA retorna? Sim, eu posso ter
funções que nunca retornam mas lançam um erro. Por exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function error(): never {
  throw new Error(&amp;#39;NUNCA VOU RETORNAR&amp;#39;);
}

error(); // Error: NUNCA VOU RETORNAR
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O retorno dessa função nunca vai ocorrer, porque o erro ocorre antes.&lt;/p&gt;
&lt;h4&gt;Enum&lt;/h4&gt;
&lt;p&gt;Enums são uma maneira de enumerar valores. Uma estrutura de dados não ordenada
que mapeiam chaves para valores. São muito usados quando pessoas querem dar um
determinado número de opções de escolha (choice).&lt;/p&gt;
&lt;p&gt;Por exemplo, na função abaixo, só posso receber parâmetros do tipo
“&lt;code&gt;ProgrammingLanguages&lt;/code&gt;“, nada mais que isso.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;enum ProgrammingLanguages {
  Python,
  JavaScript,
  TypeScript,
}

function yourPreferedProgrammingLanguage(choice: ProgrammingLanguages) {
  console.log(`Você gosta de ${ProgrammingLanguages[choice]}`);
}

// Você gosta de TypeScript
yourPreferedProgrammingLanguage(ProgrammingLanguages.TypeScript);
// Você gosta de JavaScript
yourPreferedProgrammingLanguage(ProgrammingLanguages.JavaScript);
// Você gosta de Python
yourPreferedProgrammingLanguage(ProgrammingLanguages.Python);

// Error
// Argument of type &amp;#39;&amp;quot;Java&amp;quot;&amp;#39; is not assignable to
// parameter of type &amp;#39;ProgrammingLanguages&amp;#39;.ts(2345)
yourPreferedProgrammingLanguage(&amp;#39;Java&amp;#39;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Te confesso que não uso muito essa estrutura (tem outras maneira de fazer
“choices” em TS).&lt;/p&gt;
&lt;h3&gt;Salvando tipos em variáveis&lt;/h3&gt;
&lt;p&gt;É claro que você também pode reusar os tipos que você cria, para isso você pode
usar Type ou Interface. Não vou me aprofundar tanto nisso aqui, porque esse post
está mais imenso do que eu poderia imaginar. Porém, você pode ver um tutorial
sobre isso
&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases&quot;&gt;aqui para Type&lt;/a&gt;
e
&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/interfaces.html&quot;&gt;aqui para Interfaces&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Por exemplo, imagine que eu queira criar uma calculadora com funções. Todas as
operações serão iguais, add (somar), div (dividir), sub (subtrair), mul
(multiplicar). Todas essas funções receberiam x (number) e y (number). Eu posso
facilmente criar um Type Alias ou uma Interface para mapear o tipo de todas
essas funções a fim de reutilizar a tipagem.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Usando type para declarar o tipo de uma função
type CalculatorFn = (x: number, y: number) =&amp;gt; number;

const add: CalculatorFn = (x, y) =&amp;gt; x + y;
const div: CalculatorFn = (x, y) =&amp;gt; x / y;
const sub: CalculatorFn = (x, y) =&amp;gt; x - y;
const mul: CalculatorFn = (x, y) =&amp;gt; x * y;

const onePlusTwo = add(1, 2); // number
console.log(onePlusTwo); // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba que o tipo CalculatorFn agora pode ser utilizado em qualquer função que
implemente essa assinatura. Além disso, eu já fiz a tipagem dos parâmetros
também, então não preciso adicionar tipos de x e y em cada uma das funções.&lt;/p&gt;
&lt;p&gt;Similarmente, posso fazer o mesmo com Interfaces (em várias ocasiões, Type Alias
e Interfaces podem ser usados para fazer a mesma coisa).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Usando type para declarar o tipo de uma função
interface CalculatorFn {
  (x: number, y: number): number;
}

const add: CalculatorFn = (x, y) =&amp;gt; x + y;
const div: CalculatorFn = (x, y) =&amp;gt; x / y;
const sub: CalculatorFn = (x, y) =&amp;gt; x - y;
const mul: CalculatorFn = (x, y) =&amp;gt; x * y;

const onePlusTwo = add(1, 2); // number
console.log(onePlusTwo); // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para casos mais simples assim, eu prefiro usar Type Alias, para casos mais
complexos, talvez use Interfaces.&lt;/p&gt;
&lt;p&gt;Eu posso fazer isso com qualquer tipo, por exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;type NumberOrString = number | string; // | significa OU (union type)

function sayIt(it: NumberOrString): void {
  console.log(`Você disse: ${it}`);
}

sayIt(10); // Você disse: 10
sayIt(&amp;#39;Olá&amp;#39;); // Você disse: Olá

// Error:
// Argument of type &amp;#39;{}&amp;#39; is not
// assignable to parameter
// of type &amp;#39;NumberOrString&amp;#39;.
sayIt({});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perceba também que além de usar Type Alias, também usei Union Type. Da uma lida
sobre isso
&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types&quot;&gt;aqui&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Concluindo&lt;/h2&gt;
&lt;p&gt;Como eu te disse no comecinho, essa seria uma longa introdução ao TypeScript.
Falamos sobre muita coisa, porém ainda tem muito, mas muito mais para você
aprender. O local que sempre indico para quem me pergunta onde e como aprender
TypeScript é na documentação, mais especificamente na parte do
&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/basic-types.html&quot;&gt;Handbook&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tenho a intenção de trazer mais conteúdo como este aqui para o blog, então fique
de olho.&lt;/p&gt;
</content:encoded></item><item><title>Domínio e hospedagem: guia para leigos</title><link>https://otaviomiranda.com.br/2018/dominio-e-hospedagem-guia-para-leigos/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2018/dominio-e-hospedagem-guia-para-leigos/</guid><description>Domínio e hospedagem são duas coisas que caminham de mãos dadas, mas não são a mesma coisa. Nesse artigo, vou deixar claro o que são e como configurar ambos em algumas hospedagens diferentes.</description><pubDate>Wed, 04 Apr 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Domínio e hospedagem são duas coisas que caminham de mãos dadas, mas não são a
mesma coisa. Nesse tópico, vou deixar claro o que são e como configurar ambos em
algumas hospedagens diferentes. Ao final, você poderá configurar seu próprio
domínio e hospedagem sem precisar pagar um profissional para isso.&lt;/p&gt;
&lt;p&gt;Se você precisa de um site, seja para vender, ter um blog ou qualquer outro fim,
vai precisar de duas coisas em seu nome ou da sua empresa: domínio e hospedagem.
O domínio é o nome (endereço) do site; a hospedagem é o local onde os arquivos e
configurações do site estão.&lt;/p&gt;
&lt;p&gt;Ambos estão ligados porque o domínio aponta para o IP do servidor de hospedagem,
onde os arquivos do site estão. Desse modo, pessoas que digitarem o domínio do
seu site no navegador, serão redirecionadas automaticamente para o servidor de
hospedagem.&lt;/p&gt;
&lt;p&gt;Geralmente, empresas especializadas em hospedagem de sites têm planos que trazem
ambos os serviços em um mesmo pacote. Vamos falar sobre essas empresas
posteriormente nesse artigo.&lt;/p&gt;
&lt;h2&gt;Domínio: o endereço do site&lt;/h2&gt;
&lt;p&gt;O domínio é o endereço do site em si. Aquele que, geralmente, inicia-se com
“www” e termina com “.com” ou “.com.br”. Como &lt;code&gt;www.exemplo.com.br&lt;/code&gt;, por
exemplo.&lt;/p&gt;
&lt;p&gt;Contudo, aqui cabem alguns adendos: não é necessário que um domínio inicie com
“www”, mas é recomendado. As partes “.com” e “.br” são
&lt;a href=&quot;https://pt.wikipedia.org/wiki/Dom%C3%ADnio_de_topo&quot;&gt;domínios de topo&lt;/a&gt; e ambos
podem ser totalmente diferentes para cada domínio.&lt;/p&gt;
&lt;p&gt;Vamos ver, na prática, o que acontece quando o seu domínio é usado no navegador
de Internet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;O internauta digita o endereço (domínio) do seu site no navegador;&lt;/li&gt;
&lt;li&gt;O navegador busca as configurações do seu domínio no servidor de DNS;&lt;/li&gt;
&lt;li&gt;O servidor de DNS diz para o navegador qual o IP da sua hospedagem;&lt;/li&gt;
&lt;li&gt;Sua hospedagem diz pro navegador em qual pasta do servidor seu site está;&lt;/li&gt;
&lt;li&gt;O navegador faz o download do conteúdo da pasta do seu site na hospedagem para
o internauta e mostra na tela.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Assim, para que um domínio funcione corretamente ele precisa de servidores de
DNS. Geralmente, esses servidores serão configurados pelo próprio provedor no
iqual você o contratou e não precisam ser modificados, porém, é bom que você
entenda como eles funcionam.&lt;/p&gt;
&lt;p&gt;Um servidor de DNS é quem vai receber a requisição do navegador de internet e
direcionar o Internauta para o local onde seu site está hospedado (hospedagem)
de acordo com as configurações da Zona de DNS.&lt;/p&gt;
&lt;p&gt;A não ser em casos muito específicos, como a configuração de uma rede de
distribuição de conteúdo (&lt;a href=&quot;https://www.gocache.com.br/cdn/&quot;&gt;CDN&lt;/a&gt;), &lt;strong&gt;você não
precisa alterar os servidores de DNS&lt;/strong&gt; do seu domínio. Mas, se precisar, é só ir
no painel do provedor onde você contratou o domínio, acessar a parte de
&lt;strong&gt;domínios&lt;/strong&gt; (ou meus domínios) e acessar as configurações do domínio desejado.
Certamente, a opção vai se chamar algo similar a “Configurar servidores DNS”,
“Alterar Nameservers” ou algo parecido com isso. Ainda assim, vou mostrar como
fazer isso em duas hospedagens diferentes mais abaixo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dica importante:&lt;/strong&gt; A “Zona de DNS” tem configurações dentro do &lt;strong&gt;servidor de
DNS&lt;/strong&gt;. Portanto, você não precisa alterar o servidor de DNS para configurar um
registro dentro da zona de DNS.&lt;/p&gt;
&lt;h2&gt;Como modificar os Servidores de DNS&lt;/h2&gt;
&lt;p&gt;Conforme mencionei anteriormente nesse artigo, são poucos os casos em que você
precisa alterar os servidores de DNS do seu provedor, mas caso precise, veja
como fazê-lo na Uolhost e Hostgator.&lt;/p&gt;
&lt;h3&gt;Na uolhost&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Acesse o &lt;a href=&quot;https://painelhost.uol.com.br/myProducts.html&quot;&gt;painel da uolhost&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Entre com seus dados de usuário e senha;&lt;/li&gt;
&lt;li&gt;Acesse o menu “Domínios”;&lt;/li&gt;
&lt;li&gt;Role a página até encontrar “Seus domínios”;&lt;/li&gt;
&lt;li&gt;Em “Gerenciar”, clique no menu suspenso e acesse a opção “Configurar
Servidores DNS”;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nessa página, você verá 3 opções de servidores “Master”, “Slave 1” e “Slave 2”.
Os servidores padrão da Uolhost são:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ns1.dominios.uol.com.br&lt;/li&gt;
&lt;li&gt;ns2.dominios.uol.com.br&lt;/li&gt;
&lt;li&gt;ns3.dominios.uol.com.br&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; A princípio, você não precisa preencher todos os campos, o servidor
“Master” é o principal, “Slave 1” e “Slave 2” são servidores secundários. Caso o
“Master” não funcione por algum motivo, os servidores secundários serão
acionados, entretanto, como o “Master” é o mais importante, geralmente todas as
requisições vão pra ele.&lt;/p&gt;
&lt;p&gt;Finalmente, clique em “Salvar” após a configuração.&lt;/p&gt;
&lt;h3&gt;Na hostgator&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Acesse a &lt;a href=&quot;https://financeiro.hostgator.com.br/&quot;&gt;área do cliente&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Entre com seu usuário e senha;&lt;/li&gt;
&lt;li&gt;No menu superior vá em “Domínios” &amp;gt; “Meus domínios”;&lt;/li&gt;
&lt;li&gt;No menu suspenso ao lado do domínio desejado, clique em “Alterar Nameserver –
DNS”;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A hostgator te dá opção para adicionar 5 servidores de DNS, mas você não precisa
usar todos caso não tenha milhares de servidores de DNS. O primeiro é o “Master”
e o restante secundários.&lt;/p&gt;
&lt;p&gt;Configure como quiser e clique em “Alterar Nameserver – DNS”.&lt;/p&gt;
&lt;h2&gt;Zona de DNS&lt;/h2&gt;
&lt;p&gt;A zona de DNS armazena registros que especificam os endereços dos serviços do
seu site, como ip da hospedagem, servidores de e-mail, subdomínios, entre outros
dados específicos do seu domínio.&lt;/p&gt;
&lt;p&gt;Existem vários tipos de registros que podem ser configurados na Zona de DNS do
seu domínio, dentre eles:&lt;/p&gt;
&lt;p&gt;Zona de DNS A zona de DNS armazena registros que especificam os endereços dos
serviços do seu site, como ip da hospedagem, servidores de e-mail, subdomínios,
entre outros dados específicos do seu domínio.&lt;/p&gt;
&lt;p&gt;Existem vários tipos de registros que podem ser configurados na Zona de DNS do
seu domínio, dentre eles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tipo A&lt;/strong&gt; – Registro que associa um domínio ao endereço IP de um servidor. O
valor sempre será um endereço de IP. Geralmente, o IP da sua hospedagem, mas
pode ser outro IP em alguns casos;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CNAME&lt;/strong&gt; – É um tipo de registro que mapeia um nome de alias para um nome de
domínio. Certamente, são muito usados para criar subdomínios ao seu domínio.
Por exemplo: suponhamos que você venda um curso online e que esse curso seja
uma página separada do restante do seu site. Você pode criar o subdomínio
“curso.seudominio.com.br” e mapear isso com um registro CNAME. Contudo, vale
lembrar que dentro da hospedagem precisa existir uma pasta específica para
esse subdomínio. Sozinho, o registro CNAME não fará nada. Provavelmente, no
painel da sua hospedagem existe uma opção para configuração de subdomínios e
ela irá criar o registro CNAME, portanto, use-a ao invés de alterar os
registros CNAME.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TXT&lt;/strong&gt; – Registros que podem conter um texto. Normalmente são usados para
verificação de domínio. Por exemplo: recentemente adicionei o “Search console”
da Google em um site e eles me pediram para verificar a autoridade do domínio
com um registro de DNS do tipo TXT.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MX&lt;/strong&gt; – Registros que tem como destino o servidor responsável por receber os
e-mails do domínio. O campo “Prioridade” é usado para definir a prioridade dos
servidores (quanto menor o número, maior a prioridade).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Antes que você me pergunte, esses não são todos os tipos de registros de DNS.
Caso tenha interesse, veja mais alguns tipos
&lt;a href=&quot;https://wiki.dialhost.com.br/o-que-sao-tipos-de-registro/&quot;&gt;aqui&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Se você contratou domínio e hospedagem no mesmo pacote de uma empresa de
hospedagem, certamente não vai precisar se preocupar com essas configurações.
Sem dúvida, o pessoal das empresas de hospedagens já deixam o domínio contratado
apontando para a hospedagem, de modo que é só enviar os arquivos do site pelo
servidor FTP (vamos falar sobre isso também mais adiante) e acessá-los pelo
domínio contratado. Afinal, isso é interessante pra eles!&lt;/p&gt;
&lt;p&gt;Caso tenha interesse em alterar a zona de DNS, faça o seguinte:&lt;/p&gt;
&lt;h3&gt;Na uolhost&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://painelhost.uol.com.br/myProducts.html&quot;&gt;Acesse o painel&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Entre com seus dados de usuário e senha;&lt;/li&gt;
&lt;li&gt;Acesse a opção “Domínios”;&lt;/li&gt;
&lt;li&gt;No menu suspenso do domínio desejado, clique em “Alterar Zona de DNS”;&lt;/li&gt;
&lt;li&gt;Em “Zona de DNS”, clique em “Gerenciar”; Crie os registros dos tipos
desejados e salve.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Na hostgator&lt;/h3&gt;
&lt;p&gt;Dependendo do seu tipo de hospedagem, pode ser que seja necessário usar o
cPanel. Portanto, vou mostrar como encontrar as opções para gerenciar a zona de
DNS do seu domínio por ele.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://financeiro.hostgator.com.br/clientarea.php&quot;&gt;Entre no seu painel&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Na sua hospedagem, clique em cPanel;&lt;/li&gt;
&lt;li&gt;Role a página até a sessão “Domínios”;&lt;/li&gt;
&lt;li&gt;Você pode usar tanto o “Simple Zone Editor” ou o “Advanced Zone Editor”. No
“Simple Zone Editor” você tem algo mais simples e direto, basta escolher o
tipo de registro, nome e o valor. Porém, para o “Advanced Zone Editor” é
necessário que você entenda o que está fazendo, já que tem todas as opções
disponíveis pra você configurar um registro de DNS;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vale lembrar que o cPanel possui uma área específica para a criação de
subdomínios, portanto, não é necessário criar um registro CNAME para um
subdomínio, ele fará isso automaticamente. Aliás, se você criar uma entrada
CNAME e tentar criar um subdomínio, será apresentado um erro na tela.&lt;/p&gt;
&lt;p&gt;Mas, ainda assim, é bom que você saiba como realizar tais configurações.&lt;/p&gt;
&lt;h3&gt;Whois – Informações de contato&lt;/h3&gt;
&lt;p&gt;O **whois **serve para identificar o proprietário de um site. Assim, ele é
alimentado pela própria empresa de hospedagem e reúne todas as informações
pertencentes a uma página. incluindo CNPJ ou CPF de quem o registrou. Além
disso, essas informações são públicas, ou seja, se você registrou um domínio em
seu nome, eventualmente, alguns dos seus dados serão disponibilizados
publicamente online para quem olhar o whois do seu domínio.&lt;/p&gt;
&lt;p&gt;São três setores de contato disponíveis no whois: o contato administrativo, o
contato técnico e o contato de cobrança. Muitos serviços de hospedagem vão usar
o contato do proprietário para todos os setores de contato, todavia, você pode
mudar isso no painel da sua hospedagem.&lt;/p&gt;
&lt;p&gt;Se você quiser ver o whois de um site, acesse &lt;a href=&quot;https://who.is/&quot;&gt;who.is&lt;/a&gt; e digite
o domínio desejado. Então veja a mágica!&lt;/p&gt;
&lt;h2&gt;Hospedagem: o local onde seu site está&lt;/h2&gt;
&lt;p&gt;Agora que você já está fera em domínios, vejamos o que é uma hospedagem.&lt;/p&gt;
&lt;p&gt;Embora pareça complicado, uma hospedagem nada mais é do que um espaço que você
está alugando para os arquivos e configurações do seu site em um ou vários
servidores.&lt;/p&gt;
&lt;p&gt;Um servidor pode ser um ou vários computadores que as empresas de hospedagem
mantém sempre ligados para que seu site esteja sempre online.&lt;/p&gt;
&lt;p&gt;Existem vários tipos de hospedagem que você pode contratar, cada um com seus
prós e contras. Mas isso vai depender do nível no qual o seu site está. Quando
eu digo nível, estou me referindo a quantidade de acessos e quais serviços serão
utilizados.&lt;/p&gt;
&lt;p&gt;Embora pareça complexo de início, se você está iniciando e sabe que seu site vai
começar a receber visitas neste momento, provavelmente a mais barata irá lhe
servir bem, mas é sempre bom verificar se a sua hospedagem oferece upgrade do
seu plano, ou seja, se seu site crescer, será fácil pagar um pouco mais para que
eles liberem mais recursos do servidor para seu site a medida que precise.&lt;/p&gt;
&lt;p&gt;Em uma analogia, seria como se a sua hospedagem fosse um cano, desses que passam
água, e os visitantes do site a água. Se a quantidade de água que passar for
menor que o diâmetro do cano, tudo funciona muito bem, mas, se a quantidade de
água for maior, ela será limitada pelo diâmetro do cano. Assim, se seu site
receber mais visitar do que a hospedagem pode suportar, alguns clientes poderão
ver páginas de erros do servidor, outros não conseguiram acessar e, certamente,
tudo vai ficar muito lento.&lt;/p&gt;
&lt;h3&gt;Tipos de hospedagem&lt;/h3&gt;
&lt;p&gt;Separei alguns tipos de hospedagem encontrados comumente nos provedores.&lt;/p&gt;
&lt;h4&gt;Hospedagem compartilhada&lt;/h4&gt;
&lt;p&gt;Geralmente, trata-se do pacote mais barato dos provedores. Uma solução excelente
para quem está começando e sabe que não terá um turbilhão de visitas de início.&lt;/p&gt;
&lt;p&gt;Os provedores conseguem um preço tão em conta porque compartilham os recursos de
apenas um servidor com outros sites. Ou melhor, CPU, memória, espaço em disco,
tudo isso é compartilhado.&lt;/p&gt;
&lt;p&gt;Além disso, esse plano vem todo configurado, portanto você não precisará
modificar praticamente nada, nem mesmo as configurações de domínio que mencionei
anteriormente nesse artigo.&lt;/p&gt;
&lt;p&gt;Contudo, nem tudo são flores. Você não terá praticamente nenhum controle sobre
as configurações do seu servidor e, provavelmente, também não terá acesso SSH.
Além disso, se algum dos sites do servidor tiver algum pico de visitas ou
receber ataques, o desempenho do seu, certamente, será afetado.&lt;/p&gt;
&lt;p&gt;Ainda assim, continua sendo um dos melhores e mais populares planos para sites
que estão começando ou que recebem poucas visitas.&lt;/p&gt;
&lt;h4&gt;Hospedagem VPS (Virtual Private Server)&lt;/h4&gt;
&lt;p&gt;Na hospedagem VPS, apesar de você ainda continuar compartilhando um servidor com
outros sites, nesse caso recursos são alocados especificamente para seu site, só
que picos em outros sites podem não afetar o desempenho do seu.&lt;/p&gt;
&lt;p&gt;Seu provedor vai alocar uma partição do servidor e recursos (CPU e memória)
exclusivos para o seu site com possibilidade de expansão caso necessário.&lt;/p&gt;
&lt;p&gt;Apesar de mais cara do que a hospedagem compartilhada, pode valer a pena para
sites que têm grande prospecção para expansão. Além disso, a empresa também vai
te passar dados para que você acesse e altere configurações via SSH, ou seja,
você tem acesso e autonomia para configurar determinados serviços dentro do seu
“servidor virtual”.&lt;/p&gt;
&lt;p&gt;Como nem tudo é um mar de rosas, pode ser necessário conhecimento adicional para
trabalhar com hospedagens VPS.&lt;/p&gt;
&lt;h4&gt;Hospedagem Cloud&lt;/h4&gt;
&lt;p&gt;Atualmente, a maioria dos provedores oferecem hospedagem “Cloud” (na nuvem).
Nesse tipo de hospedagem seu site não estará em um servidor, muito menos
compartilhado com outros sites, ele estará alocado em um cluster de servidores
(vários servidores). Isso quer dizer que, se algum dos servidores parar de
funcionar por algum motivo seu site será realocado para outro servidor e
continuará funcionando normalmente.&lt;/p&gt;
&lt;p&gt;O fornecimento de recursos também pode ser de acordo com a demanda, ou seja,
você pode pagar mais para receber mais recursos. Particularmente, já vi casos em
que você paga um valor fixo mensal, mas também já vi casos em que você paga pelo
que utiliza.&lt;/p&gt;
&lt;p&gt;Aqui você também vai precisar de muito conhecimento adicional. Me lembro quando
contratei o primeiro plano cloud para um site em que estávamos trabalhando, o
provedor me mandou o IP de um servidor com o Ubuntu Server instalado e usuário e
senha, ponto. Não tinha absolutamente nada configurado nesse servidor.&lt;/p&gt;
&lt;h4&gt;Hospedagem WordPress&lt;/h4&gt;
&lt;p&gt;Como o próprio nome já diz, é um tipo de hospedagem específica para sites feitos
com o WordPress. Já vem até com alguns plugins de segurança e cache
configurados. Porém, como é uma hospedagem específica, o site vai performar bem
melhor nela pela otimização já realizada pelo provedor.&lt;/p&gt;
&lt;p&gt;Os benefícios disso é que é uma hospedagem mais em conta e que não demanda
conhecimento. Talvez você nem precise ter conhecimento sobre WordPress em si.&lt;/p&gt;
&lt;p&gt;Vale lembrar que se seu site não usa o WordPress, não vale a pena investir nessa
hospedagem.&lt;/p&gt;
&lt;h4&gt;Hospedagem dedicada&lt;/h4&gt;
&lt;p&gt;Na hospedagem dedicada você tem seu próprio servidor e pode fazer o que quiser
com ele, até mudar o sistema operacional, alterar configurações de acordo com
suas necessidades ou desligar o servidor quando quiser (dependendo do provedor).&lt;/p&gt;
&lt;p&gt;Porém, além de um enorme conhecimento, você vai precisar de grana. É um tipo de
hospedagem bem caro.&lt;/p&gt;
&lt;h4&gt;Minha hospedagem preferida&lt;/h4&gt;
&lt;p&gt;De todos os planos que já utilizei em vários provedores, pra mim o que mais
gostei foi a hospedagem cloud (na nuvem). Além da flexibilidade, nunca vi o
serviço fora do ar por nem um segundo em anos.&lt;/p&gt;
&lt;h3&gt;O que você precisa saber sobre a sua hospedagem?&lt;/h3&gt;
&lt;p&gt;Quando se contrata uma hospedagem você precisará saber algumas coisas sobre ela
para realizar configurações, dentre elas estão o gerenciamento do banco de dados
e contas FTP, já que a maioria dos sites que você vai configurar vão utilizar
pelo menos um banco de dados e uma conta FTP.&lt;/p&gt;
&lt;p&gt;Se a sua hospedagem oferecer o cPanel gratuitamente, tudo fica bem mais simples,
porque você não precisará de tanto conhecimento para realizar tais
configurações.&lt;/p&gt;
&lt;p&gt;Vou mostrar como criar um usuário do banco de dados e um banco de dados para
este usuário no cPanel de maneira rápida.&lt;/p&gt;
&lt;h4&gt;Como criar usuário MySQL e banco de dados no cPanel&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Passo 1:&lt;/strong&gt; Abra o cPanel e acesse “MySQL remoto®”;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passo 2:&lt;/strong&gt; Em “Host (o coringa % é permitido)” adicione o “%” (sem aspas) e
clique em “Adicionar host”;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dica:&lt;/strong&gt; vale lembrar que aqui você está adicionando quais IPs podem acessar
seu servidor MySQL. O coringa significa “todos”.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passo 3:&lt;/strong&gt; no menu da lateral esquerda, clique novamente em “Banco de dados” e
agora acesse a configuração “Bancos de dados MySQL®”;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passo 4:&lt;/strong&gt; em “Criar novo banco de dados” adicione o nome do seu banco de
dados e clique em “Criar banco de dados”;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dica:&lt;/strong&gt; onde adicionei o retângulo vermelho, vai existir um prefixo que a
própria hospedagem lhe fornece, isso não é editável, porém faz parte do nome do
seu banco de dados.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passo 5:&lt;/strong&gt; clique em voltar e role a página até “Adicionar novo usuário”;
Preencha os campos “Nome de usuário” (depois do prefixo), “Senha” e “Senha
(novamente)”. Por fim, clique em “Criar usuário”;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passo 6:&lt;/strong&gt; Clique em “Voltar” e navegue até “Adicionar usuário ao banco de
dados”; Em “Usuário”, selecione o usuário que acabou de criar; Em “Banco de
dados”, selecione o banco de dados que criamos anteriormente; Por fim, clique em
“Adicionar”;&lt;/p&gt;
&lt;p&gt;Na nova janela que abriu, clique em “Todos os privilégios” e para finalizar
“Fazer alterações”;&lt;/p&gt;
&lt;p&gt;Pronto, agora se você precisar dos dados para criar qualquer site, os dados
serão:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Servidor MySQL (host):&lt;/strong&gt; seudominio.com.br;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Usuário do Banco de dados:&lt;/strong&gt; O usuário que você acabou de criar;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Senha:&lt;/strong&gt; A senha que você deu para o usuário anteriormente;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Banco de dados:&lt;/strong&gt; O nome do banco de dados que você acabou de criar.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vamos ver como criar uma conta FTP a seguir.&lt;/p&gt;
&lt;h4&gt;Como criar uma conta FTP no cPanel&lt;/h4&gt;
&lt;p&gt;Contas FTP serão necessárias para que qualquer desenvolvedor envie arquivos pra
dentro do seu site. Além disso, você também pode querer ter uma pasta virtual
dentro do seu servidor, para fazer backup dos seus arquivos ou coisas do tipo.&lt;/p&gt;
&lt;p&gt;Nesse caso, vamos criar uma conta FTP para adicionar arquivos diretamente dentro
da pasta do nosso site. Veja como:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passo 1:&lt;/strong&gt; Na página inicial do cPanel, em “Arquivos”, clique em “Contas FTP”;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passo 2:&lt;/strong&gt; Em “Adicionar conta de FTP” digite o “Fazer login” (nome do
usuário), “Senha” e “Senha (novamente)”; Em “Diretório”, adicione apenas
“public_html” (que é a pasta onde o site deve estar). Clique em “Criar conta de
FTP”.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passo 3:&lt;/strong&gt; Para ver os dados de acesso do usuário, role a página um pouco para
baixo, encontre o nome de usuário que acabou de criar e clique em “Configurar
cliente FTP”;&lt;/p&gt;
&lt;p&gt;Serão exibidos os seguintes dados:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nome de usuário do FTP&lt;/li&gt;
&lt;li&gt;Servidor FTP (Host)&lt;/li&gt;
&lt;li&gt;FTP &amp;amp; porta FTPS explícita (A porta)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A senha, certamente não será exibida, mas é aquela que você configurou
anteriormente ao criar a conta FTP.&lt;/p&gt;
&lt;h2&gt;Domínio e hospedagem: conclusão&lt;/h2&gt;
&lt;p&gt;Domínio e hospedagem caminham de mãos dadas, mas não são a mesma coisa. Conforme
expliquei amplamente anteriormente, domínio trata-se do nome do site e é que
encaminha o usuário para o local correto na hospedagem.&lt;/p&gt;
&lt;p&gt;A hospedagem em si é um espaço no servidor onde os arquivos e configurações do
site estão.&lt;/p&gt;
&lt;p&gt;Apesar de não serem a mesma coisa, um depende muito do outro para o bom
funcionamento de tudo o que é online atualmente.&lt;/p&gt;
&lt;h2&gt;Dicas de nomes de domínio&lt;/h2&gt;
&lt;p&gt;Pra finalizar, algumas dicas para não errar na hora de criar seu domínio:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tente usar uma ou duas palavras apenas (sempre que possível) pra ficar fácil
de lembrar;&lt;/li&gt;
&lt;li&gt;Se o domínio em nome de sua empresa já existir, tente colocar termos antes do
nome da empresa. Por exemplo: para um e-commerce, pode adicionar “loja” +
“nomedaempresa”;&lt;/li&gt;
&lt;li&gt;Se não conseguir mesmo assim, pegue o contato do proprietário do domínio pelo
registro whois (expliquei anteriormente nesse artigo);&lt;/li&gt;
&lt;li&gt;Não utilize nomes que não sejam relacionado com a sua empresa. Os e-mails do
seu domínio terão o nome do seu domínio ao final, exemplo:
&lt;a href=&quot;mailto:meuemail@meudominio.com.br&quot;&gt;meuemail@meudominio.com.br&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Você não precisa utilizar os famosos “.com” ou “.com.br”, existem milhares de
extensões de domínio possíveis. Exemplo: .net, .tv, .website, .biz e assim por
diante.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Caso tenha ficado alguma dúvida, não hesite em comentar nesse artigo.&lt;/p&gt;
&lt;p&gt;Até a próxima 🙏!&lt;/p&gt;
</content:encoded></item><item><title>Instalando o certificado SSL-TLS grátis da Let’s Encrypt</title><link>https://otaviomiranda.com.br/2018/instalando-o-certificado-ssl-tls-gratis-da-lets-encrypt/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2018/instalando-o-certificado-ssl-tls-gratis-da-lets-encrypt/</guid><description>você vai aprender a instalar e configurar o certificado SSL-TLS grátis da Lets Encrypt em quantos domínios preferir. É HTTPS gratuito para todos os seus sites.</description><pubDate>Wed, 04 Apr 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Nesse artigo você vai aprender a instalar e configurar o certificado SSL-TLS
grátis da Let&amp;#39;s Encrypt em quantos domínios preferir. É HTTPS gratuito para
todos os seus sites.&lt;/p&gt;
&lt;p&gt;Quando falamos em comércio eletrônico, estamos falando em duas coisas
extremamente importantes e sensíveis: dados pessoais e dinheiro. Portanto, a
segurança é primordial para que todos os dados passem através de conexões
seguras.&lt;/p&gt;
&lt;p&gt;Certamente, você já deve ter ouvido algum especialista em
&lt;a href=&quot;/categoria/seguranca/&quot;&gt;segurança online&lt;/a&gt; explicando que, uma das primeiras
coisas a se verificar para saber se um e-commerce qualquer é seguro, é verificar
se ele possui “aquele cadeado” falando que a conexão é segura (ou, ao invés de
usar &lt;strong&gt;HTTP&lt;/strong&gt; no início do
&lt;a href=&quot;/2018/dominio-e-hospedagem-guia-para-leigos/&quot;&gt;domínio&lt;/a&gt;, usar &lt;strong&gt;HTTPS&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;Na verdade, todos os sites que utilizam
&lt;a href=&quot;https://pt.wikipedia.org/wiki/Hyper_Text_Transfer_Protocol_Secure&quot;&gt;HTTPS&lt;/a&gt;
(&lt;em&gt;Hyper Text Transfer Protocol Secure&lt;/em&gt;) têm uma camada adicional de segurança
que usa o protocolo
&lt;a href=&quot;https://pt.wikipedia.org/wiki/Transport_Layer_Security&quot;&gt;SSL-TLS&lt;/a&gt;. Essa camada
de segurança permite que dados sejam transmitidos por uma conexão criptografada
que verifica a autenticidade do servidor e do computador cliente usando
certificados SSL-TLS. A porta TCP usada pelo HTTPS é a 443.&lt;/p&gt;
&lt;p&gt;Falando em modo superficial, qualquer dado pessoal que o cliente digitar em um
site com HTTPS, só poderá ser lido no servidor para o qual ele foi enviado. Se,
por algum motivo, algum hacker consiga capturar os dados no meio do caminho,
esses dados estarão fortemente criptografados e seria quase impossível lê-los
(digo “quase”, porque nunca se sabe, né?).&lt;/p&gt;
&lt;p&gt;Além disso, se você se preocupa com
&lt;a href=&quot;https://marketingdeconteudo.com/o-que-e-seo/&quot;&gt;SEO&lt;/a&gt; (aparecer primeiro nas
buscas), HTTPS também deverá ajudar
&lt;a href=&quot;https://imasters.com.br/devsecops/o-impacto-ssl-e-o-https-ranking-em-seo&quot;&gt;seu site a se posicionar melhor&lt;/a&gt;
no google, por exemplo.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let&amp;#39;s Encrypt&lt;/a&gt; é uma autoridade de certificação
lançada em 12 de abril de 2016, que fornece certificados SSL-TLS grátis através
de um processo automatizado, criado para eliminar a complexidade dos processos
atuais de criação, validação, instalação e renovação de certificados para sites
seguros. Sua intenção é fazer com que toda a Internet use HTTPS por uma web mais
segura.&lt;/p&gt;
&lt;p&gt;Com poucos comandos você conseguirá instalar e configurar seu certificado
SSL-TLS, habilitando assim HTTPS para seu site.&lt;/p&gt;
&lt;p&gt;Então vamos às configurações. Bora lá!&lt;/p&gt;
&lt;h2&gt;Antes de começar&lt;/h2&gt;
&lt;p&gt;Este tutorial é voltado para a instalação do certificado SSL-TLS grátis da Let’s
Encrypt diretamente no servidor, sendo, assim, não vou me atentar para
&lt;a href=&quot;/2018/dominio-e-hospedagem-guia-para-leigos/&quot;&gt;hospedagens&lt;/a&gt; que ofereçam este
recurso em seus respectivos painéis. Se a sua hospedagem oferece certificado SSL
gratuito (certamente, é da Let’s Encrypt), por favor, utilize o próprio painel
da hospedagem para habilitar HTTPS no seu site, certamente, eles têm um tutorial
explicando como realizar tal procedimento.&lt;/p&gt;
&lt;p&gt;No momento da criação deste tutorial, estou usando o Debian 9. Este tutorial
deverá funcionar perfeitamente em qualquer versão baseada em Debian (Ubuntu,
Linux Mint e assim por diante).&lt;/p&gt;
&lt;p&gt;Vou explicar como instalar e configurar o certificado. Posteriormente no artigo,
explicar como configurar um server block para &lt;strong&gt;Nginx&lt;/strong&gt; e como configurar um
Virtualhost para &lt;strong&gt;Apache&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Se você ainda não instalou nem configurou nenhum desses servidores Web, siga um
dos tutoriais abaixo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/2018/nginx-php-fpm-no-linux/&quot;&gt;nginx + php-fpm no Linux (Debian ou Ubuntu)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/2018/apache2-php-fpm-ubuntu-debian/&quot;&gt;Como instalar e configurar o Apache2 e o PHP-FPM no Ubuntu ou Debian&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Feito isso, agora é só prosseguir com o tutorial.&lt;/p&gt;
&lt;h2&gt;Instalando o certbot&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;certbot&lt;/strong&gt; é o cliente que vai buscar os certificados da Let’s Encrypt. Muito
fácil de usar e instalar.&lt;/p&gt;
&lt;p&gt;Para instalar basta digitar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo apt-get install certbot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No seu terminal.&lt;/p&gt;
&lt;h2&gt;Instalando o certificado SSL-TLS&lt;/h2&gt;
&lt;p&gt;Após a instalação do certbot, você precisa parar os serviços que rodam na porta
80 (apache ou nginx). Para isso basta digitar:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# Para o nginx
service nginx stop
# Para o apache2
service apache2 stop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E para instalar o certificado SSL-TLS digite:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo certbot certonly --standalone -d meudominio.com.br -d www.meudominio.com.br
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A única coisa que você precisa alterar são os domínios. Geralmente, um domínio
tem duas versões:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;meudominio.com.br&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.meudominio.com.br&quot;&gt;www.meudominio.com.br&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ambas apontando para o mesmo local, se esse for o seu caso, no comando acima
altere apenas a parte “meudominio.com.br” para o seu domínio nos dois locais.&lt;/p&gt;
&lt;p&gt;Se o seu domínio tem apenas uma versão, use o seguinte comando:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo certbot certonly --standalone -d meudominio.com.br
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Você deverá receber uma mensagem dizendo que está tudo OK, por exemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
  /etc/letsencrypt/live/meudominio.com.br/fullchain.pem.
  Your cert will expire on 2018-11-23. To obtain a new or tweaked
  version of this certificate in the future, simply run certbot
  again. To non-interactively renew *all* of your certificates, run
  &amp;quot;certbot renew&amp;quot;
- If you like Certbot, please consider supporting our work by:

  Donating to ISRG / Let&amp;#39;s Encrypt:   https://letsencrypt.org/donate
  Donating to EFF:                    https://eff.org/donate-le
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Isso quer dizer que seu certificado SSL-TLS foi salvo em
/etc/letsencrypt/live/meudominio.com.br/.&lt;/p&gt;
&lt;p&gt;Para cada domínio você terá uma pasta com o nome do domínio em questão.
Portanto, se você tem mais de um domínio, siga os mesmos passos para gerar um
certificado SSL-TLS para cada um deles.&lt;/p&gt;
&lt;h2&gt;Configurando um Server Block no nginx&lt;/h2&gt;
&lt;p&gt;Depois de criar seu certificado SSL-TLS, agora você precisa falar para o nginx
as configurações SSL. Para isso acesse o server block do seu site em
/etc/nginx/sites-enabled/meudominio.com.br (ou o nome que você deu para o server
block do seu site).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo nano /etc/nginx/sites-enabled/meudominio.com.br
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E adicione as linhas a seguir em vermelho:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;server {
	listen 80;
	listen [::]:80;

	listen 443 ssl http2;
	listen [::]:443 ssl http2;

	ssl_certificate /etc/letsencrypt/live/meudominio.com.br/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/meudominio.com.br/privkey.pem;
	ssl_trusted_certificate /etc/letsencrypt/live/meudominio.com.br/chain.pem;

	ssl_session_cache shared:SSL:10m;
	ssl_session_timeout 5m;

	ssl_prefer_server_ciphers on;
	ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

	# Desativa SSLv3
	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

	# Diffie-Hellman
	# $ sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
	ssl_dhparam /etc/ssl/certs/dhparam.pem;

	# Ativa HSTS
	add_header Strict-Transport-Security &amp;quot;max-age=63072000; includeSubdomains&amp;quot;;

	# Ativa OCSP stapling
	ssl_stapling on;
	ssl_stapling_verify on;
	resolver 8.8.8.8 8.8.4.4 valid=300s;
	resolver_timeout 5s;

	root /var/www/meudominio.com.br/public_html/;

	# Add index.php to the list if you are using PHP
	index index.html index.htm index.nginx-debian.html index.php;

	server_name meudominio.com.br www.meudominio.com.br;

	location / {
		try_files $uri $uri/ =404;

		# Para WordPress
		# try_files $uri $uri/ /index.php?q=$uri&amp;amp;$args;
	}

	# Passa scripts PHP para o servidor FastCGI (PHP-FPM)
	#
	location ~ \.php$ {
		include snippets/fastcgi-php.conf;
		fastcgi_intercept_errors on;

		#fastcgi_index index.php;
		if (!-f $realpath_root$fastcgi_script_name) {
			return 404;
		}

		fastcgi_buffers 16 16k;
		fastcgi_buffer_size 32k;

		include /etc/nginx/fastcgi_params;
		fastcgi_pass 127.0.0.1:9010;
	}

	location ~ /\. {
		access_log off;
		log_not_found off;
		deny all;
	}

	access_log off;
	error_log   /var/log/nginx/meudominio.com.br-error.log;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As linhas em vermelho no trecho do server block acima configuram SSL no seu
nginx.&lt;/p&gt;
&lt;p&gt;Mais uma coisinha: você precisa executar mais um comando para prevenir
&lt;a href=&quot;https://www.kryptus.com/diffie-hellman-no-openssl/&quot;&gt;ataque contra chaves Diffie-Hellman&lt;/a&gt;
no OpenSSL.&lt;/p&gt;
&lt;p&gt;Para isso digite:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Atenção:&lt;/strong&gt; isso vai demorar mmmmmmmmmmmuuuuuuuuuuuiiiiiiiiitooo.&lt;/p&gt;
&lt;p&gt;Por fim, não se esqueça de iniciar o nginx novamente.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo service nginx start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Em caso de erros, provavelmente são os caminhos dos arquivos, verifique se estão
todos em seus devidos locais.&lt;/p&gt;
&lt;h2&gt;Configurando um Virtualhost no Apache&lt;/h2&gt;
&lt;p&gt;Se você usa apache, altere seu virtualhost digitando:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo nano /etc/apache2/sites-enabled/meudominio.com.br.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Claro, você precisa alterar o comando acima para o nome do seu virtualhost.conf.&lt;/p&gt;
&lt;p&gt;Eu criei um virtualhost simples aqui no apache na porta 80 que ficou assim:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apache&quot;&gt;&amp;lt;VirtualHost *:80&amp;gt;
    ServerAdmin webmaster@meudominio.com.br
    DocumentRoot /var/www/meudominio.com.br/public_html
    ServerName meudominio.com.br
    ServerAlias www.meudominio.com.br

    CustomLog /var/log/apache2/access-meudominio.com.br.br.log combined
    ErrorLog /var/log/apache2/error-meudominio.com.br.br.log

    &amp;lt;FilesMatch &amp;quot;\.php$&amp;quot;&amp;gt;
        &amp;lt;If &amp;quot;-f %{SCRIPT_FILENAME}&amp;quot;&amp;gt;
            SetHandler &amp;quot;proxy:fcgi://127.0.0.1:9010&amp;quot;
        &amp;lt;/If&amp;gt;
    &amp;lt;/FilesMatch&amp;gt;

    &amp;lt;Directory &amp;quot;/var/www/meudominio.com.br/public_html&amp;quot;&amp;gt;
        Options FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        allow from all
    &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Um virtualhost bem básico com &lt;a href=&quot;/2018/apache2-php-fpm-ubuntu-debian/&quot;&gt;PHP-FPM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Para certificado SSL-TLS, vou clonar esse virtualhost, porém adicionando algumas
coisinhas, veja:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apache&quot;&gt;&amp;lt;VirtualHost *:443&amp;gt;
    ServerAdmin webmaster@meudominio.com.br
    DocumentRoot /var/www/meudominio.com.br/public_html
    ServerName meudominio.com.br
    ServerAlias www.meudominio.com.br

    &amp;lt;FilesMatch &amp;quot;\.php$&amp;quot;&amp;gt;
        &amp;lt;If &amp;quot;-f %{SCRIPT_FILENAME}&amp;quot;&amp;gt;
            SetHandler &amp;quot;proxy:fcgi://127.0.0.1:9010&amp;quot;
            #SetHandler &amp;quot;proxy:unix:/var/run/php/php7.2-fpm-om.sock|fcgi://127.0.0.1:9010/&amp;quot;
        &amp;lt;/If&amp;gt;
    &amp;lt;/FilesMatch&amp;gt;

    &amp;lt;Directory &amp;quot;/var/www/meudominio.com.br/public_html&amp;quot;&amp;gt;
        Options FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        allow from all
    &amp;lt;/Directory&amp;gt;

    CustomLog /var/log/apache2/access-meudominio.com.br.br.log combined
    ErrorLog /var/log/apache2/error-meudominio.com.br.br.log

    SSLEngine on

    SSLProtocol             all -SSLv2 -SSLv3
    SSLCipherSuite          ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
    SSLHonorCipherOrder     on
    SSLCompression          off

    SSLOptions +StrictRequire

    SSLCertificateFile /etc/letsencrypt/live/meudominio.com.br/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/meudominio.com.br/privkey.pem
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As partes em vermelho do código são as partes que adicionei para o certificado
SSL-TLS.&lt;/p&gt;
&lt;p&gt;Você também precisa ativar o módulo SSL do apache2, para isso digite:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo a2enmod ssl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lembre-se também que, no apache, você precisa ter os dois virtualhosts, o da
porta 80 e o da porta 443.&lt;/p&gt;
&lt;p&gt;Para finalizar, inicie o apache:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;service apache2 start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Prontinho, só testar.&lt;/p&gt;
&lt;p&gt;Até a próxima!&lt;/p&gt;
</content:encoded></item><item><title>SSH keys: chaves de autenticação SSH</title><link>https://otaviomiranda.com.br/2018/ssh-keys-chaves-de-autenticacao-ssh/</link><guid isPermaLink="true">https://otaviomiranda.com.br/2018/ssh-keys-chaves-de-autenticacao-ssh/</guid><description>SSH keys são chaves de autenticação SSH para aprimorar a segurança do servidor remoto. Nesse artigo você vai aprender a configurar o par de chaves pública/privada no seu computador local (com Linux ou Windows) e no servidor remoto.</description><pubDate>Wed, 04 Apr 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SSH keys são chaves de autenticação SSH para aprimorar a segurança do servidor
remoto. Nesse artigo você vai aprender a configurar o par de chaves
pública/privada no seu computador local (com Linux ou Windows) e no servidor
remoto.&lt;/p&gt;
&lt;p&gt;Se você administra um servidor e este funciona constantemente online,
provavelmente faz acesso a ele via SSH (Secure Shell). E, se já faz algum tempo
que esse seu servidor está online, é certo que já deve ter visto algumas coisas
estranhas nos logs, como tentativas de login no seu arquivo
&lt;strong&gt;/var/log/auth.log&lt;/strong&gt; vindas de vários IPs diferentes. Isso, provavelmente, pode
ser alguém tentando fazer brute force no seu servidor.&lt;/p&gt;
&lt;p&gt;Existem várias maneiras de prevenir
&lt;a href=&quot;https://www.profissionaisti.com.br/2011/11/o-que-e-brute-force-nada-alem-de-forca-bruta/&quot;&gt;brute force&lt;/a&gt;,
e vários outros ataques, apenas manipulando regras de firewall. Um bom exemplo
disso é a &lt;a href=&quot;https://cloud.google.com/&quot;&gt;Google Cloud&lt;/a&gt;, que permite liberar portas
específicas nas VMs apenas para sua rede, porém, não são todos os provedores que
permitem tal configuração.&lt;/p&gt;
&lt;p&gt;Outra forma muito eficaz para aumentar a segurança do seu servidor e ainda
prevenir brute force é usar as SSH Keys (chaves de autenticação SSH) e eliminar
a possibilidade de login por senha.&lt;/p&gt;
&lt;p&gt;As SSH Keys funcionam no modo chave pública e chave privada (sempre em pares),
onde a conexão SFTP/SSH só é autorizada se a &lt;strong&gt;chave privada&lt;/strong&gt; do usuário do
computador cliente bater com a &lt;strong&gt;chave pública&lt;/strong&gt; do usuário do servidor.&lt;/p&gt;
&lt;p&gt;Soa um tanto complexo, né? Mas você vai entender direitinho ao terminar esse
artigo.&lt;/p&gt;
&lt;h2&gt;Antes de continuar&lt;/h2&gt;
&lt;p&gt;Será necessário um servidor com OpenSSH instalado e um usuário com acesso
&lt;a href=&quot;https://www.todoespacoonline.com/w/2015/10/su-sudo-e-sudoers-no-linux/&quot;&gt;sudo&lt;/a&gt;
nesse servidor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Atenção:&lt;/strong&gt; se você está fazendo isso em um servidor de produção, não feche sua
conexão SSH antes de criar uma nova conexão e conseguir se conectar com sucesso.
Do contrário, pode ser que você perca acesso SSH e SFTP ao servidor. Além disso,
preste bastante atenção no que está fazendo para conseguir voltar as
configurações anteriores caso necessário.&lt;/p&gt;
&lt;h2&gt;Divergência de usuários&lt;/h2&gt;
&lt;p&gt;É importante que você saiba que não é necessário que os usuários do computador
local e do servidor remoto tenham o mesmo nome, ou seja, você pode criar uma
chave para um usuário chamado “Joãozinho” no computador local, porém ele vai se
conectar no usuário “Zézinho” no servidor remoto.&lt;/p&gt;
&lt;p&gt;Porém, a chave privada do usuário Joãozinho precisa bater com a chave pública do
usuário Zézinho no servidor.&lt;/p&gt;
&lt;h2&gt;Criando SSH Keys no Linux&lt;/h2&gt;
&lt;p&gt;Se o seu computador local tem o Linux com OpenSSH instalados, basta digitar o
seguinte:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;ssh-keygen -t rsa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ao pressionar “Enter” você verá algo parecido com:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;Generating public/private rsa key pair.
Enter file in which to save the key (/home/joaozinho/.ssh/id_rsa):
Created directory &amp;#39;/home/joaozinho/.ssh&amp;#39;.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/joaozinho/.ssh/id_rsa.
Your public key has been saved in /home/joaozinho/.ssh/id_rsa.pub.
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Na primeira linha é requisitado que você especifique onde deseja gerar a chave.
Não é necessário alterar este local, ela será gerada na sua &lt;strong&gt;pasta local&lt;/strong&gt;
dentro de uma pasta oculta chamada &lt;strong&gt;.ssh&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Atenção:&lt;/strong&gt; em “Enter passphrase”, digite uma senha forte e que você se lembre
posteriormente. Essa senha sempre será utilizada para realizar a primeira
conexão SSH com o servidor. Assim, se você perder ou alguém roubar sua chave,
não conseguirá acesso ao servidor sem saber sua “senha forte”. Será necessário
repetir essa senha na linha “Enter same passphrase again”.&lt;/p&gt;
&lt;h2&gt;Criando uma SSH Key para outro usuário no Linux&lt;/h2&gt;
&lt;p&gt;Apesar de ser algo privado de um usuário e ele mesmo deveria criar e guardar sua
própria chave, você pode gerar uma chave para outro usuário apenas alterando o
caminho onde ela é salva no comando explicado anteriormente.&lt;/p&gt;
&lt;p&gt;Mas, se você tem acesso sudo no computador, pode digitar o seguinte:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo -u joaozinho ssh-keygen -t rsa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apenas altere “joaozinho” para o nome de usuário que deseja criar a chave. Se
você não alterar nada, as chaves pública e privada serão geradas na pasta .ssh
dentro da home do usuário “joaozinho”.&lt;/p&gt;
&lt;h2&gt;Como ver a chave pública&lt;/h2&gt;
&lt;p&gt;Como é a chave pública que será copiada para o servidor, você só vai precisar
copiar ela. Para isso, digite:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;cat /home/joaozinho/.ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apenas altere “joaozinho” para o seu usuário ou nome de usuário que deseja
copiar a chave.&lt;/p&gt;
&lt;p&gt;Lembre-se que as permissões desse arquivo são voltadas para o usuário do
arquivo, se você estiver criando para outro usuário, precisará usar sudo para
executar esse comando.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo cat /home/joaozinho/.ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Você vai ver muitos caracteres, começando com “ssh-rsa”. Selecione e copie todos
os dados dessa linha. Essa é a chave pública do seu usuário que será copiada
para o servidor posteriormente nesse artigo.&lt;/p&gt;
&lt;h2&gt;Criando SSH Keys no Windows&lt;/h2&gt;
&lt;p&gt;No Windows você vai precisar do Putty para gerar seu par de chaves pública e
privada. Baixe o no link a seguir:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html&quot;&gt;Baixar Putty&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Baixe o instalador com extensão &lt;strong&gt;.msi&lt;/strong&gt; e instale no seu computador.&lt;/p&gt;
&lt;p&gt;Depois de instalado, pressione simultaneamente as teclas “Windows” + “R” e
digite:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;%programfiles%\Putty
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E pressione “Enter”.&lt;/p&gt;
&lt;p&gt;Dentro da pasta do PuTTY, abra o arquivo &lt;strong&gt;puttygen.exe&lt;/strong&gt;. Clique em “Generate”
e mova o mouse próximo a barrinha de carregamento até terminar.&lt;/p&gt;
&lt;p&gt;Na imagem abaixo detalho o que você precisa fazer dentro do puttygen:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/1.jpg&quot; alt=&quot;Exemplo 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;Copie sua chave pública para o servidor, como explico na próxima etapa.&lt;/p&gt;
&lt;p&gt;Não se esqueça que, para se conectar ao servidor remoto, você primeiro precisa
carregar sua chave privada.&lt;/p&gt;
&lt;h2&gt;Adicione chave pública no servidor remoto&lt;/h2&gt;
&lt;p&gt;Depois de criar suas chaves, você vai precisar copiar sua chave pública para o
servidor. Para isso, basta criar um arquivo chamado “authorized_keys” dentro da
pasta &lt;strong&gt;.ssh&lt;/strong&gt; do usuário desejado dentro do servidor.&lt;/p&gt;
&lt;p&gt;Por exemplo: suponhamos que eu queira dar autorização para o usuário “Joãozinho”
(usado no linux anteriormente) para acessar o servidor remoto como o usuário
“Zézinho”. Então eu digitaria o seguinte no servidor remoto:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo mkdir --mode=600 /home/zezinho/.ssh/
sudo nano /home/zezinho/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E colocaria a chave pública do “Joãozinho” dentro do arquivo “authorized_keys”
do “Zézinho”.&lt;/p&gt;
&lt;p&gt;Agora cole os dados da sua chave pública dentro desse arquivo e pressione
“CTRL” + “O” para salvar, “CTRL” + “X” para sair.&lt;/p&gt;
&lt;p&gt;Se vários usuários poderão se conectar usando o usuário do servidor remoto,
adicione uma chave pública por linha no &lt;strong&gt;authorized_keys&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Pronto, configuramos os usuários, agora vamos configurar o OpenSSH.&lt;/p&gt;
&lt;h2&gt;Configurando o OpenSSH&lt;/h2&gt;
&lt;p&gt;Dentro do servidor remoto, digite o seguinte:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E altere as linhas:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ssh-config&quot;&gt;# ...
PermitRootLogin no
# ...
PubkeyAuthentication yes
# ...
PasswordAuthentication no
# ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;PermitRootLogin&lt;/strong&gt; remove completamente o acesso do root ao servidor SSH,
&lt;strong&gt;PubkeyAuthentication&lt;/strong&gt; permite o acesso via chave pública e
&lt;strong&gt;PasswordAuthentication&lt;/strong&gt; remove o acesso via senhas de texto.&lt;/p&gt;
&lt;p&gt;Pressione “CTRL”+”O” para salvar e reinicie o servidor SSH:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;sudo service ssh restart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Atenção:&lt;/strong&gt; não feche sua conexão SSH atual. Para testar, abra uma nova conexão
SSH, se der errado você não perderá sua conexão atual.&lt;/p&gt;
&lt;p&gt;É isso, se tiver dúvidas comenta aí.&lt;/p&gt;
</content:encoded></item></channel></rss>