Rich renderiza tabelas, painéis, progresso, tracebacks e texto estilizado no terminal. O ganho real é hierarquia visual, não quantidade de cores. Uma CLI profissional continua compreensível em terminal estreito, sem cor e com saída redirecionada.
Mostrar dados em uma tabela
from rich.console import Console
from rich.table import Table
console = Console()
tabela = Table(title="Tarefas")
tabela.add_column("Nome")
tabela.add_column("Estado")
tabela.add_row("Importar dados", "concluida")
tabela.add_row("Gerar relatorio", "pendente")
console.print(tabela)
Não misture a criação dos dados com sua renderização. Assim, a mesma função pode produzir JSON para automação e uma tabela para pessoas. Evite usar apenas cor para comunicar erro ou sucesso; inclua um rótulo textual.
Para progresso, atualize uma tarefa existente em vez de imprimir uma linha por item. Em logs, configure um handler conscientemente e preserve campos necessários para observabilidade. Nunca imprima tokens, senhas ou respostas completas apenas porque o traceback formatado parece conveniente.
Rich detecta muitos terminais automaticamente, mas testes devem cobrir NO_COLOR, largura reduzida e saída não interativa. Ao criar comandos completos, o artigo sobre Typer para CLIs em Python mostra como separar argumentos, validação e apresentação.
A documentação oficial do Rich, consultada em 22 de julho de 2026, detalha Console, Table, Progress, Logging e opções para captura ou exportação de saída.
Instalar o Rich e definir um console
Instale o pacote no ambiente da aplicação:
python -m pip install rich
Crie o Console perto da camada de apresentação e passe a instância às funções que renderizam. Um console configurado em um único lugar mantém largura, política de cores, destino de erros e gravação consistentes. Código de domínio deve, em geral, retornar valores. A camada do comando decide se eles viram tabela, texto simples ou JSON.
from rich.console import Console
def mostrar_resultado(resultado: dict[str, str], console: Console) -> None:
console.print(f"[bold]Tarefa:[/bold] {resultado['nome']}")
console.print(f"Estado: {resultado['estado']}")
console = Console(stderr=False)
mostrar_resultado({"nome": "importacao-diaria", "estado": "concluida"}, console)
O markup é conveniente para strings confiáveis, mas um texto recebido do usuário pode conter colchetes interpretados como formatação. Use markup=False, construa um Text ou escape o valor com rich.markup.escape. Essa proteção importa para nomes de arquivo, mensagens de exceção e conteúdo vindo de APIs.
Projetar tabelas para terminais estreitos
Uma tabela ajuda quando a pessoa compara vários registros pelos mesmos campos. Para um único registro com muitas propriedades, uma lista costuma ser mais clara. Escolha apenas colunas úteis para a decisão, coloque as mais importantes primeiro e permita quebra de linha no texto secundário.
from rich.table import Table
def tabela_tarefas(linhas: list[dict[str, str]]) -> Table:
tabela = Table(title="Tarefas", show_lines=False)
tabela.add_column("ID", no_wrap=True)
tabela.add_column("Tarefa", overflow="fold")
tabela.add_column("Estado", no_wrap=True)
for linha in linhas:
tabela.add_row(linha["id"], linha["nome"], linha["estado"])
return tabela
Teste com uma largura fixa, sem depender apenas do terminal de desenvolvimento:
from io import StringIO
from rich.console import Console
saida = StringIO()
console_teste = Console(file=saida, width=40, color_system=None)
console_teste.print(tabela_tarefas([
{"id": "42", "nome": "Importar registros de clientes", "estado": "pendente"}
]))
assert "Importar registros" in saida.getvalue()
Esse teste verifica conteúdo relevante, não cada caractere da borda. Snapshots completos podem ajudar, mas tendem a mudar quando a biblioteca ajusta a renderização. Mantenha asserções separadas para rótulos críticos.
Mostrar progresso sem atrapalhar automação
Progress serve para trabalho cuja conclusão pode ser medida. Avance a tarefa somente depois que um item terminar e envie detalhes de erro para um log ou resumo final.
from rich.progress import Progress
arquivos = ["clientes.csv", "pedidos.csv", "produtos.csv"]
with Progress() as progresso:
tarefa = progresso.add_task("Importando", total=len(arquivos))
for caminho in arquivos:
importar_arquivo(caminho)
progresso.advance(tarefa)
Quando o total é desconhecido, um indicador indeterminado é mais honesto. Em integração contínua, saída redirecionada ou modo legível por máquinas, animações criam ruído. Ofereça uma opção explícita, como --format json, e desative o progresso nesse caminho. Scripts não devem interpretar a interface colorida feita para pessoas.
Usar status, painéis e sintaxe com critério
console.status() funciona para uma operação curta sem percentual útil. Panel pode destacar um resumo importante e Syntax exibe um trecho de código. Cada componente precisa esclarecer o resultado ou a próxima ação. Colocar toda mensagem dentro de uma caixa gasta espaço e enfraquece a hierarquia.
Adote um vocabulário pequeno para mensagens, como sucesso, aviso e erro, sempre com prefixo textual. A cor pode reforçar o significado, mas o texto copiado, um terminal monocromático e um leitor de tela ainda precisam comunicá-lo.
from rich.text import Text
mensagem = Text()
mensagem.append("ERRO: ", style="bold red")
mensagem.append("arquivo de configuracao nao encontrado")
console.print(mensagem)
Antes de exibir código ou dados aninhados, remova credenciais. A impressão formatada facilita a inspeção e também aumenta o risco de expor segredos. Prefira uma lista explícita de campos permitidos em vez de tentar adivinhar todos os nomes possíveis de tokens.
Integrar Rich e logging
RichHandler melhora a leitura local de logs e tracebacks, mas não substitui uma estratégia de logging. Configure níveis no ponto de entrada, escreva mensagens que façam sentido sem estilo e inclua contexto operacional em campos próprios.
import logging
from rich.logging import RichHandler
logging.basicConfig(
level="INFO",
format="%(message)s",
handlers=[RichHandler(rich_tracebacks=True, show_path=False)],
)
logger = logging.getLogger("importador")
logger.info("Importacao iniciada", extra={"job_id": "importacao-diaria"})
Serviços em produção frequentemente precisam de logs JSON para coleta e consulta. Nesse cenário, reserve Rich para o handler interativo e encaminhe registros estruturados para o coletor. Evite incluir variáveis locais em tracebacks quando o processo manipula credenciais ou dados pessoais.
Capturar saída e testar alternativas
Console(record=True) exporta texto ou HTML renderizado, enquanto Console.capture() coleta um trecho. Use esses recursos em relatórios somente depois de definir o consumidor. Saída de terminal contém escolhas visuais e raramente é um formato estável de integração.
Cubra pelo menos três modos: terminal interativo com cor, saída simples com color_system=None e largura reduzida. Se a aplicação respeita NO_COLOR, valide isso na fronteira do comando. Confira também códigos de saída e stderr, pois uma mudança visual não deve deslocar erros silenciosamente para stdout.
Checklist antes de publicar
- Todo estado possui rótulo textual, não apenas cor.
- As tabelas continuam compreensíveis em largura reduzida.
- A saída redirecionada não contém quadros de animação.
- Existe modo estruturado quando automações precisam dos dados.
- Colchetes não confiáveis são escapados ou o markup está desativado.
- Segredos são removidos antes de impressão detalhada ou tracebacks.
- Destinos e níveis de log continuam separados do estilo visual.
Rich funciona melhor como uma camada de apresentação com limites claros. Quando as funções de domínio retornam valores comuns e o comando controla a renderização, um terminal bem cuidado não torna a aplicação mais difícil de testar, automatizar ou manter.
Saber quando texto simples é melhor
Rich é dispensável quando um comando imprime um único valor, participa principalmente de pipelines ou roda somente em automação. Um caminho, identificador ou número sem decoração costuma ser o contrato mais útil. Adicione formatação quando ela realmente ajudar uma pessoa a comparar, localizar ou decidir.
Defina interfaces separadas para pessoas e máquinas, sem depender apenas da detecção do terminal. A detecção oferece um padrão razoável, enquanto --format text, --format json e --no-color dão controle a quem chama. Documente qual saída é estável. Espaçamento e rótulos humanos podem evoluir; um schema estruturado exige compatibilidade.
Considere redirecionamento separadamente de cor. Uma pessoa pode salvar um relatório textual e ainda querer todas as linhas, enquanto um serviço de integração contínua pode simular um terminal. A detecção de capacidade não conhece a intenção.
Meça também o tempo de inicialização de comandos executados com muita frequência. O custo de importação pouco importa em uma tarefa longa, mas aparece em completions e utilitários pequenos. Imports tardios podem fazer sentido na fronteira do comando depois de medir, sem complicar toda a aplicação com base em suposição.