Aller au contenu
Développement medium

Rich : affichage riche dans le terminal Python

39 min de lecture

Rich transforme ton terminal en interface graphique. Couleurs, tableaux, barres de progression, arbres, logs enrichis — le tout dans un simple terminal. Ce guide te fait construire pas à pas un vrai outil : un script de health check d’infrastructure qui scanne des services, affiche les résultats dans un tableau coloré, avec une barre de progression et des logs structurés. À chaque étape, tu découvres une fonctionnalité de Rich parce que le script en a besoin.

Prérequis : savoir écrire des fonctions Python et utiliser print() (voir le guide sur les fonctions).

Rich est aussi le moteur de rendu de Textual, le framework TUI. Tout ce que tu apprends ici sur le markup et les styles s’applique directement dans Textual (voir le guide Textual).

Le projet fil rouge : un health check d’infrastructure

Section intitulée « Le projet fil rouge : un health check d’infrastructure »

Imagine que tu gères plusieurs services (un serveur web, une API, une base de données). Tu veux un script qui :

  1. Scanne chaque service pour vérifier s’il répond
  2. Affiche les résultats de façon lisible dans le terminal
  3. Montre la progression du scan en temps réel
  4. Logue les événements importants avec horodatage

On va commencer avec un print() basique, puis améliorer le script section par section avec Rich. À la fin, tu auras un outil complet et réutilisable.

Voici le squelette de départ — un simple script avec print() :

"""health_check.py — Version de départ (sans Rich)."""
import random
import time
# Nos services à surveiller
services = [
{"nom": "web-frontend", "ip": "10.0.1.10", "port": 80},
{"nom": "api-backend", "ip": "10.0.2.10", "port": 8080},
{"nom": "db-primary", "ip": "10.0.3.10", "port": 5432},
{"nom": "cache-redis", "ip": "10.0.4.10", "port": 6379},
{"nom": "monitoring", "ip": "10.0.5.10", "port": 9090},
]
def scanner_service(service):
"""Simule un ping vers un service et renvoie la latence."""
time.sleep(0.3) # Simule le temps réseau
latence = random.randint(1, 50)
ok = latence < 100
return {"latence": latence, "ok": ok}
# Scanner et afficher
print("=== Health Check ===")
for svc in services:
resultat = scanner_service(svc)
statut = "OK" if resultat["ok"] else "DOWN"
print(f" {svc['nom']:20s} {svc['ip']:15s} {resultat['latence']:>4}ms {statut}")
print("=== Terminé ===")

Ça fonctionne, mais la sortie est monotone : tout est en blanc sur fond noir, pas de structure visuelle, impossible de distinguer les succès des échecs d’un coup d’œil. On va corriger ça.

Fenêtre de terminal
pip install rich

Vérifie que l’installation fonctionne en lançant la démo intégrée :

Fenêtre de terminal
python -m rich

Si tu vois des couleurs, des tableaux et des emojis dans le terminal, c’est bon.

Le premier pas avec Rich, c’est de remplacer print() par Console.print(). L’objet Console est le point d’entrée de toute utilisation de Rich :

from rich.console import Console
console = Console()
# Au lieu de print(), utilise console.print()
console.print("=== Health Check ===")

Ça donne le même résultat, mais Console sait faire beaucoup plus. Par exemple, il détecte et colore automatiquement les adresses IP, les nombres, les URLs et les booléens :

console.print("Serveur : 10.0.1.10, port : 8080, latence : 12ms")
# → les nombres et l'IP apparaissent en couleur automatiquement

Console dispose aussi de méthodes utiles qu’on va exploiter dans notre script :

# rule() : crée un séparateur visuel — idéal pour structurer la sortie
console.rule("Health Check")
# log() : affiche un message avec l'heure et le fichier source
console.log("Scan démarré")
# → [14:32:01] Scan démarré health_check.py:8
# print_json() : affiche du JSON formaté et coloré
console.print_json('{"nom": "web-1", "cpu": 4, "ram_gb": 16}')

Mettons à jour notre script pour utiliser Console :

"""health_check.py — Étape 1 : Console."""
import random
import time
from rich.console import Console
console = Console()
services = [
{"nom": "web-frontend", "ip": "10.0.1.10", "port": 80},
{"nom": "api-backend", "ip": "10.0.2.10", "port": 8080},
{"nom": "db-primary", "ip": "10.0.3.10", "port": 5432},
{"nom": "cache-redis", "ip": "10.0.4.10", "port": 6379},
{"nom": "monitoring", "ip": "10.0.5.10", "port": 9090},
]
def scanner_service(service):
time.sleep(0.3)
latence = random.randint(1, 50)
return {"latence": latence, "ok": latence < 100}
console.rule("Health Check")
for svc in services:
resultat = scanner_service(svc)
statut = "OK" if resultat["ok"] else "DOWN"
console.print(f" {svc['nom']:20s} {svc['ip']:15s} {resultat['latence']:>4}ms {statut}")
console.rule("Terminé")

C’est déjà plus lisible grâce aux séparateurs rule(), et les IP et nombres sont colorés. Mais les statuts OK/DOWN sont toujours en blanc. On veut du vert pour OK et du rouge pour DOWN.

Rich utilise une syntaxe de markup inspirée du BBCode — des balises entre crochets [] — pour appliquer des couleurs et des styles au texte. La syntaxe est simple : [style]texte[/style].

Voici les styles de base :

from rich.console import Console
console = Console()
# Styles de texte
console.print("[bold]Gras[/bold]")
console.print("[italic]Italique[/italic]")
console.print("[underline]Souligné[/underline]")
console.print("[dim]Atténué[/dim]")
# Couleurs
console.print("[red]Rouge[/red]")
console.print("[green]Vert[/green]")
console.print("[yellow]Jaune[/yellow]")
console.print("[blue on white]Bleu sur fond blanc[/blue on white]")
# Combinaisons — les styles se cumulent
console.print("[bold red]Gras et rouge[/bold red]")
console.print("[bold green on black]Gras vert sur fond noir[/bold green on black]")

Le raccourci [/] ferme tous les styles ouverts d’un coup :

console.print("[bold red underline]Style complexe[/]") # tout fermé

Rich accepte aussi les couleurs hexadécimales et RGB :

console.print("[#ff8800]Orange hexadécimal[/#ff8800]")
console.print("[rgb(100,200,50)]Vert RGB[/rgb(100,200,50)]")

Et les emojis via leur code :

console.print(":white_check_mark: Service opérationnel")
console.print(":warning: Attention : espace disque faible")
console.print(":cross_mark: Service inaccessible")
StyleSyntaxeRendu
Gras[bold]...[/bold]Texte en gras
Italique[italic]...[/italic]Texte en italique
Souligné[underline]...[/underline]Texte souligné
Barré[strike]...[/strike]Texte barré
Atténué[dim]...[/dim]Texte grisé
Inversé[reverse]...[/reverse]Couleurs inversées
Couleur texte[red]...[/red]Texte en rouge
Couleur fond[on blue]...[/on blue]Fond bleu
Lien[link=URL]...[/link]Lien cliquable

Quand tu utilises les mêmes couleurs à plusieurs endroits, plutôt que de répéter le markup, définis des objets Style réutilisables :

from rich.style import Style
STYLE_OK = Style(color="green", bold=True)
STYLE_DOWN = Style(color="red", bold=True)
STYLE_WARN = Style(color="yellow", bold=True)
console.print("Service opérationnel", style=STYLE_OK)
console.print("Service inaccessible", style=STYLE_DOWN)

Appliquons le markup à notre script de health check :

"""health_check.py — Étape 2 : markup couleur."""
import random
import time
from rich.console import Console
console = Console()
services = [
{"nom": "web-frontend", "ip": "10.0.1.10", "port": 80},
{"nom": "api-backend", "ip": "10.0.2.10", "port": 8080},
{"nom": "db-primary", "ip": "10.0.3.10", "port": 5432},
{"nom": "cache-redis", "ip": "10.0.4.10", "port": 6379},
{"nom": "monitoring", "ip": "10.0.5.10", "port": 9090},
]
def scanner_service(service):
time.sleep(0.3)
latence = random.randint(1, 50)
return {"latence": latence, "ok": latence < 100}
console.rule("[bold blue]Health Check")
for svc in services:
resultat = scanner_service(svc)
if resultat["ok"]:
statut = "[bold green]OK[/bold green]"
style_latence = "green"
else:
statut = "[bold red]DOWN[/bold red]"
style_latence = "red"
latence = resultat["latence"]
console.print(
f" [cyan]{svc['nom']:20s}[/cyan] "
f"[dim]{svc['ip']:15s}[/dim] "
f"[{style_latence}]{latence:>4}ms[/{style_latence}] "
f"{statut}"
)
console.rule("[bold blue]Terminé")

Maintenant, les noms de services sont en cyan, les IP en gris, la latence est colorée, et le statut est en vert (OK) ou rouge (DOWN). En un coup d’œil, tu identifies les problèmes. Mais la sortie ressemble encore à une liste de lignes. On peut faire mieux avec une table.

Rich peut afficher des tables formatées directement dans le terminal — avec des colonnes alignées, des bordures et des styles par colonne. On crée une table en trois étapes : instancier, ajouter les colonnes, ajouter les lignes.

from rich.table import Table
# 1. Créer la table
table = Table(title="Mon titre")
# 2. Ajouter les colonnes
table.add_column("Colonne 1", style="cyan")
table.add_column("Colonne 2", justify="right")
# 3. Ajouter les lignes
table.add_row("valeur 1", "valeur 2")
table.add_row("valeur 3", "valeur 4")
# 4. Afficher
console.print(table)
OptionDescriptionDéfaut
styleCouleur/style du contenuAucun
justifyAlignement (left, center, right)left
no_wrapEmpêcher le retour à la ligneFalse
widthLargeur fixe en caractèresAuto
min_width / max_widthLimites de largeurAucun
header_styleStyle de l’en-têteAucun

Rich propose plusieurs styles de bordure via le module box :

from rich import box
table = Table(box=box.ROUNDED) # Coins arrondis (le plus courant)
table = Table(box=box.SIMPLE) # Lignes simples
table = Table(box=box.HEAVY) # Traits épais
table = Table(box=box.DOUBLE) # Double ligne
table = Table(box=None) # Sans bordure

Intégrons la table à notre script :

"""health_check.py — Étape 3 : résultats en table."""
import random
import time
from rich.console import Console
from rich.table import Table
from rich import box
console = Console()
services = [
{"nom": "web-frontend", "ip": "10.0.1.10", "port": 80},
{"nom": "api-backend", "ip": "10.0.2.10", "port": 8080},
{"nom": "db-primary", "ip": "10.0.3.10", "port": 5432},
{"nom": "cache-redis", "ip": "10.0.4.10", "port": 6379},
{"nom": "monitoring", "ip": "10.0.5.10", "port": 9090},
]
def scanner_service(service):
time.sleep(0.3)
latence = random.randint(1, 50)
return {"latence": latence, "ok": latence < 100}
# Scanner tous les services
resultats = []
for svc in services:
resultat = scanner_service(svc)
resultats.append({**svc, **resultat})
# Construire la table
table = Table(title="Health Check", box=box.ROUNDED, show_lines=True)
table.add_column("Service", style="cyan bold", no_wrap=True)
table.add_column("IP", style="dim")
table.add_column("Port", justify="right")
table.add_column("Latence", justify="right")
table.add_column("Statut", justify="center")
for r in resultats:
style_latence = "green" if r["latence"] < 20 else "yellow" if r["latence"] < 50 else "red"
statut = "[bold green]OK[/]" if r["ok"] else "[bold red]DOWN[/]"
table.add_row(
r["nom"],
r["ip"],
str(r["port"]),
f"[{style_latence}]{r['latence']}ms[/]",
statut,
)
console.print(table)

Résultat dans le terminal :

Health Check
╭─────────────────┬────────────┬──────┬─────────┬────────╮
│ Service │ IP │ Port │ Latence │ Statut │
├─────────────────┼────────────┼──────┼─────────┼────────┤
│ web-frontend │ 10.0.1.10 │ 80 │ 12ms │ OK │
├─────────────────┼────────────┼──────┼─────────┼────────┤
│ api-backend │ 10.0.2.10 │ 8080 │ 8ms │ OK │
├─────────────────┼────────────┼──────┼─────────┼────────┤
│ db-primary │ 10.0.3.10 │ 5432 │ 3ms │ OK │
├─────────────────┼────────────┼──────┼─────────┼────────┤
│ cache-redis │ 10.0.4.10 │ 6379 │ 42ms │ OK │
├─────────────────┼────────────┼──────┼─────────┼────────┤
│ monitoring │ 10.0.5.10 │ 9090 │ 28ms │ OK │
╰─────────────────┴────────────┴──────┴─────────┴────────╯

Les données sont structurées, alignées, colorées. Mais quand le scan prend du temps (beaucoup de services), l’utilisateur ne voit rien pendant l’attente. Il faut une barre de progression.

Rich propose deux façons d’afficher une barre de progression :

track() est la solution en une ligne. Tu l’utilises à la place d’un itérable dans une boucle for :

from rich.progress import track
for item in track(range(100), description="Traitement..."):
time.sleep(0.02) # ton travail ici
Traitement... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 67% 0:00:01

Progress() permet de gérer plusieurs tâches et de personnaliser l’affichage :

from rich.progress import Progress
with Progress() as progress:
tache1 = progress.add_task("[cyan]Téléchargement...", total=100)
tache2 = progress.add_task("[green]Installation...", total=100)
while not progress.finished:
progress.update(tache1, advance=3)
progress.update(tache2, advance=1)
time.sleep(0.02)
Téléchargement... ━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
Installation... ━━━━━━━━━━━━━━━━ 45% 0:00:02

Intégrons track() à notre script pour montrer la progression du scan :

"""health_check.py — Étape 4 : barre de progression."""
import random
import time
from rich.console import Console
from rich.table import Table
from rich.progress import track
from rich import box
console = Console()
services = [
{"nom": "web-frontend", "ip": "10.0.1.10", "port": 80},
{"nom": "api-backend", "ip": "10.0.2.10", "port": 8080},
{"nom": "db-primary", "ip": "10.0.3.10", "port": 5432},
{"nom": "cache-redis", "ip": "10.0.4.10", "port": 6379},
{"nom": "monitoring", "ip": "10.0.5.10", "port": 9090},
]
def scanner_service(service):
time.sleep(0.3)
latence = random.randint(1, 50)
return {"latence": latence, "ok": latence < 100}
# Scanner avec barre de progression
resultats = []
for svc in track(services, description="[cyan]Scan des services..."):
resultat = scanner_service(svc)
resultats.append({**svc, **resultat})
console.print()
# Table des résultats
table = Table(title="Health Check", box=box.ROUNDED, show_lines=True)
table.add_column("Service", style="cyan bold", no_wrap=True)
table.add_column("IP", style="dim")
table.add_column("Port", justify="right")
table.add_column("Latence", justify="right")
table.add_column("Statut", justify="center")
for r in resultats:
style_latence = "green" if r["latence"] < 20 else "yellow" if r["latence"] < 50 else "red"
statut = "[bold green]OK[/]" if r["ok"] else "[bold red]DOWN[/]"
table.add_row(
r["nom"], r["ip"], str(r["port"]),
f"[{style_latence}]{r['latence']}ms[/]", statut,
)
console.print(table)

Maintenant, l’utilisateur voit une barre progresser pendant le scan, puis le tableau de résultats. Le script devient réactif. Prochaine amélioration : on veut montrer l’architecture des services avant de les scanner.

Tree affiche des données hiérarchiques — comme l’architecture d’un cluster, l’arborescence de fichiers, ou la structure d’une organisation :

from rich.tree import Tree
# Créer l'arbre racine
arbre = Tree(":cloud: Mon infrastructure")
# Ajouter des branches
frontend = arbre.add("[green]Frontend")
frontend.add("[cyan]web-1 (10.0.1.10)")
frontend.add("[cyan]web-2 (10.0.1.11)")
backend = arbre.add("[yellow]Backend")
backend.add("[cyan]api-1 (10.0.2.10)")
console.print(arbre)
☁ Mon infrastructure
├── Frontend
│ ├── web-1 (10.0.1.10)
│ └── web-2 (10.0.1.11)
└── Backend
└── api-1 (10.0.2.10)

Panel encadre n’importe quel contenu Rich dans un cadre avec titre :

from rich.panel import Panel
console.print(Panel(
"Contenu encadré",
title="Mon titre",
subtitle="Mon sous-titre",
border_style="blue",
))
# Panel ajusté au contenu
console.print(Panel.fit("Texte court", title="Info"))

On combine Tree et Panel pour afficher l’architecture avant le scan :

"""health_check.py — Étape 5 : architecture avec Tree et Panel."""
import random
import time
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.tree import Tree
from rich.progress import track
from rich import box
console = Console()
services = [
{"nom": "web-frontend", "ip": "10.0.1.10", "port": 80, "groupe": "Frontend"},
{"nom": "api-backend", "ip": "10.0.2.10", "port": 8080, "groupe": "Backend"},
{"nom": "db-primary", "ip": "10.0.3.10", "port": 5432, "groupe": "Base de données"},
{"nom": "cache-redis", "ip": "10.0.4.10", "port": 6379, "groupe": "Cache"},
{"nom": "monitoring", "ip": "10.0.5.10", "port": 9090, "groupe": "Observabilité"},
]
def scanner_service(service):
time.sleep(0.3)
latence = random.randint(1, 50)
return {"latence": latence, "ok": latence < 100}
# 1. Afficher l'architecture
arbre = Tree(":cloud: Infrastructure de production")
groupes = {}
for svc in services:
groupe = svc["groupe"]
if groupe not in groupes:
groupes[groupe] = arbre.add(f"[bold]{groupe}[/]")
groupes[groupe].add(f"[cyan]{svc['nom']}[/] ([dim]{svc['ip']}:{svc['port']}[/])")
console.print(Panel(arbre, title="Architecture", border_style="blue"))
console.print()
# 2. Scanner avec barre de progression
resultats = []
for svc in track(services, description="[cyan]Scan des services..."):
resultat = scanner_service(svc)
resultats.append({**svc, **resultat})
console.print()
# 3. Table des résultats
table = Table(title="Résultats du Health Check", box=box.ROUNDED, show_lines=True)
table.add_column("Service", style="cyan bold", no_wrap=True)
table.add_column("IP", style="dim")
table.add_column("Latence", justify="right")
table.add_column("Statut", justify="center")
for r in resultats:
style_latence = "green" if r["latence"] < 20 else "yellow" if r["latence"] < 50 else "red"
statut = "[bold green]OK[/]" if r["ok"] else "[bold red]DOWN[/]"
table.add_row(r["nom"], r["ip"], f"[{style_latence}]{r['latence']}ms[/]", statut)
console.print(table)
console.print()
# 4. Résumé encadré
total = len(resultats)
ok_count = sum(1 for r in resultats if r["ok"])
couleur = "green" if ok_count == total else "yellow" if ok_count > 0 else "red"
console.print(Panel.fit(
f"[bold]{ok_count}/{total}[/] services opérationnels\n"
f"Latence moyenne : [cyan]{sum(r['latence'] for r in resultats) / total:.0f}ms[/]",
title="Résumé",
border_style=couleur,
))

Le script affiche maintenant quatre sections : l’architecture en arbre, la barre de progression du scan, le tableau de résultats, et un résumé encadré. C’est déjà un outil utilisable. Mais si quelque chose plante, on veut des logs lisibles et des tracebacks utiles.

Python dispose d’un module logging intégré pour écrire des messages de log. Par défaut, les logs sont en texte brut sans couleurs. RichHandler remplace le handler par défaut pour afficher les logs avec horodatage, couleurs par niveau, et le nom du fichier source :

import logging
from rich.logging import RichHandler
# Configuration en une ligne
logging.basicConfig(
level=logging.DEBUG,
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(rich_tracebacks=True)]
)
log = logging.getLogger("health_check")
log.debug("Détail technique pour le debug")
log.info("Scan démarré")
log.warning("Latence élevée sur cache-redis")
log.error("Service db-primary inaccessible")
log.critical("Aucun service ne répond !")
[14:32:01] DEBUG Détail technique pour le debug health_check.py:12
[14:32:01] INFO Scan démarré health_check.py:13
[14:32:01] WARNING Latence élevée sur cache-redis health_check.py:14
[14:32:01] ERROR Service db-primary inaccessible health_check.py:15
[14:32:01] CRITICAL Aucun service ne répond ! health_check.py:16

Chaque niveau a sa couleur (DEBUG gris, INFO bleu, WARNING jaune, ERROR rouge, CRITICAL rouge gras), et Rich affiche automatiquement le fichier et le numéro de ligne.

OptionDescriptionDéfaut
rich_tracebacksTracebacks enrichis et colorésFalse
show_timeAfficher l’heureTrue
show_levelAfficher le niveau (INFO, ERROR…)True
show_pathAfficher fichier:ligneTrue
markupInterpréter le markup Rich dans les messagesFalse
tracebacks_show_localsAfficher les variables locales dans les tracebacksFalse

Quand un script plante, le traceback Python standard est souvent difficile à lire. Rich le remplace par une version colorée avec le code source et les variables locales :

from rich.traceback import install
# Active les tracebacks Rich pour tout le script
install(show_locals=True)
Traceback (most recent call last):
File "health_check.py", line 12, in <module>
result = process_config(data)
File "health_check.py", line 8, in process_config
return config["database"]["host"]
KeyError: 'database'

La version Rich montre le code source autour de l’erreur, les variables locales à chaque niveau, et une coloration syntaxique qui aide à repérer le problème immédiatement. Active-le au début de tes scripts pour ne plus jamais revenir en arrière.

Pour indiquer qu’un travail est en cours sans barre de progression (quand tu ne connais pas le nombre d’étapes), utilise console.status() :

with console.status("[bold green]Connexion au cluster...") as status:
time.sleep(2)
status.update("[bold yellow]Vérification des certificats...")
time.sleep(1)
status.update("[bold cyan]Chargement de la config...")
time.sleep(1)
console.print("[bold green]Connecté !")

Un spinner animé tourne dans le terminal pendant chaque étape.

Live permet de mettre à jour un affichage (table, panel…) sans scintillement ni défilement. L’affichage se rafraîchit sur place :

from rich.live import Live
def generer_table(etape):
"""Génère une table de statut avec l'état actuel du scan."""
table = Table(title="Scan en cours")
table.add_column("Service")
table.add_column("Statut")
noms = ["web-frontend", "api-backend", "db-primary", "cache-redis", "monitoring"]
for i, nom in enumerate(noms):
if i < etape:
table.add_row(nom, "[green]Scanné")
elif i == etape:
table.add_row(nom, "[yellow]En cours...")
else:
table.add_row(nom, "[dim]En attente")
return table
with Live(generer_table(0), console=console, refresh_per_second=4) as live:
for etape in range(5):
time.sleep(0.5)
live.update(generer_table(etape + 1))

La table se met à jour en temps réel : chaque service passe de “En attente” à “En cours” puis “Scanné”, le tout sans que le terminal défile.

Pendant le développement, tu peux utiliser rich.inspect() pour explorer n’importe quel objet Python — ses attributs, ses méthodes, sa documentation :

from rich import inspect
# Inspecter un dictionnaire
inspect({"nom": "web-1", "ip": "10.0.1.10"})
# Inspecter avec les méthodes visibles
inspect(["a", "b", "c"], methods=True)
# Inspecter un module entier
import os
inspect(os, methods=True)
╭──────────────── <class 'dict'> ────────────────╮
│ {'nom': 'web-1', 'ip': '10.0.1.10'} │
│ │
│ clear = def clear(...) │
│ copy = def copy(...) │
│ keys = def keys(...) │
│ values = def values(...) │
│ items = def items(...) │
│ ... │
╰─────────────────────────────────────────────────╯

Voici le script complet qui rassemble toutes les fonctionnalités vues dans ce guide. Tu peux le copier et l’adapter à ta propre infrastructure :

"""health_check.py — Script complet de health check avec Rich."""
import logging
import random
import time
from rich.console import Console
from rich.logging import RichHandler
from rich.panel import Panel
from rich.progress import track
from rich.table import Table
from rich.traceback import install
from rich.tree import Tree
from rich import box
# --- Configuration ---
install(show_locals=True) # Tracebacks Rich pour tout le script
logging.basicConfig(
level=logging.INFO,
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(rich_tracebacks=True)],
)
log = logging.getLogger("health_check")
console = Console()
# --- Données ---
services = [
{"nom": "web-frontend", "ip": "10.0.1.10", "port": 80, "groupe": "Frontend"},
{"nom": "api-backend", "ip": "10.0.2.10", "port": 8080, "groupe": "Backend"},
{"nom": "db-primary", "ip": "10.0.3.10", "port": 5432, "groupe": "Base de données"},
{"nom": "cache-redis", "ip": "10.0.4.10", "port": 6379, "groupe": "Cache"},
{"nom": "monitoring", "ip": "10.0.5.10", "port": 9090, "groupe": "Observabilité"},
]
def scanner_service(service):
"""Simule un health check réseau."""
time.sleep(0.3)
latence = random.randint(1, 80)
return {"latence": latence, "ok": latence < 100}
def afficher_architecture(services):
"""Affiche l'arborescence des services dans un panel."""
arbre = Tree(":cloud: Infrastructure de production")
groupes = {}
for svc in services:
groupe = svc["groupe"]
if groupe not in groupes:
groupes[groupe] = arbre.add(f"[bold]{groupe}[/]")
groupes[groupe].add(
f"[cyan]{svc['nom']}[/] ([dim]{svc['ip']}:{svc['port']}[/])"
)
console.print(Panel(arbre, title="Architecture", border_style="blue"))
def scanner_tous(services):
"""Scanne chaque service avec une barre de progression."""
resultats = []
for svc in track(services, description="[cyan]Scan des services..."):
resultat = scanner_service(svc)
resultats.append({**svc, **resultat})
if not resultat["ok"]:
log.error(f"{svc['nom']} est DOWN (latence : {resultat['latence']}ms)")
elif resultat["latence"] > 30:
log.warning(f"{svc['nom']} — latence élevée : {resultat['latence']}ms")
else:
log.info(f"{svc['nom']} — OK ({resultat['latence']}ms)")
return resultats
def afficher_resultats(resultats):
"""Affiche les résultats dans une table colorée."""
table = Table(
title="Résultats du Health Check",
box=box.ROUNDED,
show_lines=True,
)
table.add_column("Service", style="cyan bold", no_wrap=True)
table.add_column("Groupe", style="dim")
table.add_column("IP", style="dim")
table.add_column("Latence", justify="right")
table.add_column("Statut", justify="center")
for r in resultats:
if r["latence"] < 20:
style_latence = "green"
elif r["latence"] < 50:
style_latence = "yellow"
else:
style_latence = "red"
statut = "[bold green]OK[/]" if r["ok"] else "[bold red]DOWN[/]"
table.add_row(
r["nom"],
r["groupe"],
f"{r['ip']}:{r['port']}",
f"[{style_latence}]{r['latence']}ms[/]",
statut,
)
console.print(table)
def afficher_resume(resultats):
"""Affiche un résumé encadré."""
total = len(resultats)
ok_count = sum(1 for r in resultats if r["ok"])
latence_moy = sum(r["latence"] for r in resultats) / total
if ok_count == total:
couleur = "green"
icone = ":white_check_mark:"
elif ok_count > 0:
couleur = "yellow"
icone = ":warning:"
else:
couleur = "red"
icone = ":cross_mark:"
console.print(Panel.fit(
f"{icone} [bold]{ok_count}/{total}[/] services opérationnels\n"
f"Latence moyenne : [cyan]{latence_moy:.0f}ms[/]",
title="Résumé",
border_style=couleur,
))
# --- Exécution ---
console.rule("[bold blue]Health Check — Production")
console.print()
afficher_architecture(services)
console.print()
resultats = scanner_tous(services)
console.print()
afficher_resultats(resultats)
console.print()
afficher_resume(resultats)

Lance-le :

Fenêtre de terminal
python health_check.py

Le terminal affiche successivement : l’arbre d’architecture, la barre de progression du scan, les logs horodatés par service, le tableau de résultats coloré, et le résumé encadré avec une icône verte/jaune/rouge.

Tu as peut-être envie d’envoyer ce rapport par email ou de l’archiver. Rich peut exporter la sortie en texte brut ou en HTML :

# Activer l'enregistrement
console = Console(record=True)
# ... tout le script s'exécute ici ...
# Exporter en texte brut (sans codes couleur)
with open("rapport.txt", "w") as f:
f.write(console.export_text())
# Exporter en HTML (avec les couleurs préservées)
with open("rapport.html", "w") as f:
f.write(console.export_html())

Pour écrire directement dans un fichier sans couleurs :

with open("rapport.txt", "w") as f:
fichier_console = Console(file=f, force_terminal=False)
fichier_console.print("Rapport sans couleurs")
BesoinFonctionnalité RichQuand l’utiliser
Afficher un résultatconsole.print() + markupToujours — remplace print()
Structurer des donnéesTableListes de serveurs, résultats, comparaisons
Montrer une hiérarchieTree + PanelArchitecture, arborescence fichiers, organisations
Suivre un traitement longtrack()Boucle dont tu connais le nombre d’itérations
Tâches parallèlesProgress()Plusieurs progressions simultanées
Attente indéterminéeconsole.status()Connexion, chargement, sans nombre d’étapes
Mise à jour en placeLiveDashboard, monitoring en temps réel
JournaliserRichHandlerTout script qui utilise logging
Débugger un crashinstall() de tracebackEn début de script, toujours
Explorer un objetinspect()En debug uniquement
  • Console(record=True) : active l’enregistrement uniquement si tu exportes — ça consomme de la mémoire.
  • track() : préfère-le à Progress() pour les cas simples.
  • Markup dans les boucles serrées : pour des millions d’itérations, print() natif reste plus rapide.
  • Rich détecte automatiquement si le terminal supporte les couleurs.
  • En CI/CD : Rich désactive les couleurs si stdout n’est pas un TTY. Force-les avec Console(force_terminal=True).
  • Redirection vers un fichier : utilise Console(file=f) pour un export propre sans codes ANSI.
SymptômeCause probableSolution
Pas de couleurs dans le terminalTerminal ne supporte pas les couleursVérifie avec python -m rich
Couleurs absentes en CI/CDstdout n’est pas un TTYConsole(force_terminal=True)
Texte tronqué dans les tablesTerminal trop étroitConsole(width=120) ou réduis les colonnes
[red]texte[/red] affiché tel quelTu utilises print() au lieu de console.print()Remplace par Console().print()
Markup interprété dans les logsmarkup=True sur RichHandlerMets markup=False si tu logues des données brutes
Emoji non affichéTerminal sans support emojiConsole(emoji=False)
Export HTML sans couleursOubli de record=TrueConsole(record=True) avant les print()
  • Rich remplace print() par Console.print() — couleurs, markup, détection automatique d’IP/URL, et export.
  • Le markup [bold red]texte[/] applique des styles combinables en une syntaxe simple.
  • Les tables structurent les résultats avec colonnes alignées, bordures et couleurs par colonne.
  • track() ajoute une barre de progression en une seule ligne de changement.
  • Tree et Panel affichent des hiérarchies et encadrent les résultats — pour un rendu professionnel.
  • RichHandler remplace le logging standard avec horodatage, couleurs par niveau et fichier source.
  • install() de rich.traceback rend les erreurs lisibles avec code source et variables locales.
  • Rich est le moteur de Textual : le markup et les styles appris ici s’appliquent dans les TUI.
  • Le fil rouge : chaque fonctionnalité Rich résout un vrai besoin du script — couleur pour distinguer OK/DOWN, table pour structurer, progression pour l’attente, logs pour tracer, tracebacks pour débugger.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.