Rich renderiza tablas, paneles, progreso, tracebacks y texto con estilo en el terminal. Su valor real es la jerarquía visual, no la cantidad de colores. Una CLI profesional sigue siendo comprensible en un terminal estrecho, sin color y con salida redirigida.
Presentar datos en una tabla
from rich.console import Console
from rich.table import Table
console = Console()
tabla = Table(title="Tareas")
tabla.add_column("Nombre")
tabla.add_column("Estado")
tabla.add_row("Importar datos", "completa")
tabla.add_row("Crear informe", "pendiente")
console.print(tabla)
Separa la creación de datos de su presentación. Así, la misma función puede producir JSON para automatización y una tabla para personas. No uses solo el color para indicar error o éxito; añade una etiqueta textual.
Para progreso, actualiza una tarea existente en lugar de imprimir una línea por elemento. Si Rich formatea logs, configura el handler con cuidado y conserva los campos necesarios. Nunca imprimas tokens o contraseñas porque el traceback resulte cómodo.
Rich detecta muchas capacidades del terminal, pero las pruebas deben cubrir NO_COLOR, ancho reducido y salida no interactiva. Para comandos completos, la guía de Typer para CLIs en Python ayuda a separar argumentos, validación y presentación.
La documentación oficial de Rich, consultada el 22 de julio de 2026, explica Console, Table, Progress, Logging y captura de salida.
Instalar Rich y definir una consola
Instala el paquete en el entorno de la aplicación:
python -m pip install rich
Crea Console cerca de la capa de presentación y pasa la instancia a las funciones que renderizan. Una sola configuración mantiene coherentes el ancho, la política de color, el destino de errores y la grabación. El código de dominio debería devolver valores. La capa del comando decide si esos valores se muestran como tabla, texto simple o JSON.
from rich.console import Console
def mostrar_resultado(resultado: dict[str, str], console: Console) -> None:
console.print(f"[bold]Tarea:[/bold] {resultado['nombre']}")
console.print(f"Estado: {resultado['estado']}")
console = Console(stderr=False)
mostrar_resultado({"nombre": "importacion-diaria", "estado": "completa"}, console)
El markup es cómodo para cadenas confiables, pero un texto aportado por el usuario puede incluir corchetes que Rich interprete como formato. Usa markup=False, crea un objeto Text o escapa el valor con rich.markup.escape. Esta precaución importa para nombres de archivo, mensajes de excepción y contenido recibido de una API.
Diseñar tablas para terminales estrechos
Una tabla resulta útil al comparar varios registros mediante los mismos campos. Para un único registro con muchas propiedades, una lista suele ser más clara. Selecciona columnas accionables, ordena primero las importantes y permite que el texto secundario se ajuste.
from rich.table import Table
def tabla_tareas(filas: list[dict[str, str]]) -> Table:
tabla = Table(title="Tareas", show_lines=False)
tabla.add_column("ID", no_wrap=True)
tabla.add_column("Tarea", overflow="fold")
tabla.add_column("Estado", no_wrap=True)
for fila in filas:
tabla.add_row(fila["id"], fila["nombre"], fila["estado"])
return tabla
Prueba el resultado con un ancho fijo:
from io import StringIO
from rich.console import Console
salida = StringIO()
consola_prueba = Console(file=salida, width=40, color_system=None)
consola_prueba.print(tabla_tareas([
{"id": "42", "nombre": "Importar registros de clientes", "estado": "pendiente"}
]))
assert "Importar registros" in salida.getvalue()
La prueba comprueba contenido significativo, no cada carácter del borde. Los snapshots completos pueden ayudar, pero cambian cuando Rich ajusta su renderizado. Conserva aserciones independientes para las etiquetas críticas.
Mostrar progreso sin perjudicar la automatización
Progress está pensado para trabajos cuya finalización puede medirse. Avanza una tarea después de que el elemento termine y envía los detalles de error a un log o resumen final.
from rich.progress import Progress
archivos = ["clientes.csv", "pedidos.csv", "productos.csv"]
with Progress() as progreso:
tarea = progreso.add_task("Importando", total=len(archivos))
for ruta in archivos:
importar_archivo(ruta)
progreso.advance(tarea)
Si no conoces el total, un indicador indeterminado es más honesto. En integración continua, salida redirigida o un modo para máquinas, la animación añade ruido. Ofrece una opción como --format json y desactiva el progreso en esa ruta. Un script no debería analizar una interfaz colorida para personas.
Usar estados, paneles y sintaxis con moderación
console.status() sirve para una operación breve sin porcentaje útil. Panel puede destacar un resumen y Syntax mostrar código. Cada componente debe aclarar el resultado o el próximo paso. Encerrar cada línea en una caja consume espacio y elimina la jerarquía.
Adopta un vocabulario pequeño, como éxito, aviso y error, siempre con prefijo textual. El color puede reforzar el significado, pero un lector de pantalla, un terminal monocromo o un log copiado deben conservarlo.
from rich.text import Text
mensaje = Text()
mensaje.append("ERROR: ", style="bold red")
mensaje.append("no se encontro el archivo de configuracion")
console.print(mensaje)
Antes de presentar código o datos anidados, elimina credenciales. La impresión bonita facilita la inspección y también la exposición accidental. Una lista de campos permitidos es más segura que intentar reconocer todos los nombres posibles de secretos.
Integrar Rich con logging
RichHandler mejora los logs y tracebacks locales, pero no sustituye una estrategia de logging. Configura niveles en el punto de entrada, escribe mensajes útiles sin estilos y conserva el contexto operativo en campos.
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("Importacion iniciada", extra={"job_id": "importacion-diaria"})
Los servicios de producción suelen necesitar logs JSON para recopilación y consultas. Reserva Rich para el handler interactivo y envía registros estructurados al colector. No incluyas variables locales en tracebacks cuando el proceso maneje credenciales o datos personales.
Capturar salida y probar alternativas
Console(record=True) puede exportar texto o HTML renderizado y Console.capture() recopila un fragmento. Úsalos para informes después de definir quién consumirá el resultado. La salida de terminal contiene decisiones visuales y no suele ser un formato de intercambio estable.
Cubre tres modos como mínimo: terminal interactivo con color, salida simple con color_system=None y ancho reducido. Si la aplicación respeta NO_COLOR, compruébalo en la frontera del comando. Revisa también códigos de salida y stderr, porque un cambio visual no debe enviar errores a stdout.
Lista de comprobación
- Cada estado tiene una etiqueta textual y no depende del color.
- Las tablas siguen siendo comprensibles con poco ancho.
- La salida redirigida no contiene fotogramas de animación.
- Existe un modo estructurado cuando la automatización necesita datos.
- Los corchetes no confiables se escapan o el markup está desactivado.
- Los secretos se eliminan antes de imprimir datos o tracebacks.
- Los destinos y niveles de log no dependen del estilo del terminal.
Rich funciona mejor como una capa de presentación con límites claros. Si las funciones de dominio devuelven valores normales y el comando controla el renderizado, un terminal cuidado no vuelve la aplicación más difícil de probar, automatizar o mantener.
Saber cuándo conviene el texto simple
Rich es prescindible cuando un comando imprime un único valor, participa sobre todo en pipelines o solo se ejecuta mediante automatización. Una ruta, un identificador o un número sin decoración suele ser el contrato más útil. Añade formato cuando ayude de verdad a comparar, localizar o decidir.
Define interfaces distintas para personas y máquinas sin depender exclusivamente de detectar el terminal. La detección ofrece un valor predeterminado razonable, mientras --format text, --format json y --no-color dan control al consumidor. Documenta qué salida es estable. Los espacios y rótulos humanos pueden evolucionar; un esquema estructurado requiere compatibilidad.
Considera la redirección aparte del color. Una persona puede guardar un informe textual y necesitar filas completas, mientras un sistema de integración continua puede simular un terminal. Detectar capacidades no revela la intención.
Mide también el tiempo de arranque de comandos muy frecuentes. El coste de importación apenas importa en una tarea larga, pero se nota en el autocompletado y utilidades pequeñas. Las importaciones tardías pueden justificarse en la frontera del comando después de medir, sin complicar toda la aplicación por una suposición.