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

  1. Todo estado possui rótulo textual, não apenas cor.
  2. As tabelas continuam compreensíveis em largura reduzida.
  3. A saída redirecionada não contém quadros de animação.
  4. Existe modo estruturado quando automações precisam dos dados.
  5. Colchetes não confiáveis são escapados ou o markup está desativado.
  6. Segredos são removidos antes de impressão detalhada ou tracebacks.
  7. 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.