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 :
- Scanne chaque service pour vérifier s’il répond
- Affiche les résultats de façon lisible dans le terminal
- Montre la progression du scan en temps réel
- 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 randomimport time
# Nos services à surveillerservices = [ {"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 afficherprint("=== 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.
Installation de Rich
Section intitulée « Installation de Rich »pip install richVérifie que l’installation fonctionne en lançant la démo intégrée :
python -m richSi tu vois des couleurs, des tableaux et des emojis dans le terminal, c’est bon.
Étape 1 : remplacer print() par Console
Section intitulée « Étape 1 : remplacer print() par Console »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 automatiquementConsole 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 sortieconsole.rule("Health Check")
# log() : affiche un message avec l'heure et le fichier sourceconsole.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 randomimport timefrom 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.
Étape 2 : colorer avec le markup
Section intitulée « Étape 2 : colorer avec le markup »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 Consoleconsole = Console()
# Styles de texteconsole.print("[bold]Gras[/bold]")console.print("[italic]Italique[/italic]")console.print("[underline]Souligné[/underline]")console.print("[dim]Atténué[/dim]")
# Couleursconsole.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 cumulentconsole.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")Tableau des styles disponibles
Section intitulée « Tableau des styles disponibles »| Style | Syntaxe | Rendu |
|---|---|---|
| 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 |
L’objet Style (pour le code)
Section intitulée « L’objet Style (pour le code) »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 randomimport timefrom 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.
Étape 3 : structurer les résultats en table
Section intitulée « Étape 3 : structurer les résultats en 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 tabletable = Table(title="Mon titre")
# 2. Ajouter les colonnestable.add_column("Colonne 1", style="cyan")table.add_column("Colonne 2", justify="right")
# 3. Ajouter les lignestable.add_row("valeur 1", "valeur 2")table.add_row("valeur 3", "valeur 4")
# 4. Afficherconsole.print(table)Options des colonnes
Section intitulée « Options des colonnes »| Option | Description | Défaut |
|---|---|---|
style | Couleur/style du contenu | Aucun |
justify | Alignement (left, center, right) | left |
no_wrap | Empêcher le retour à la ligne | False |
width | Largeur fixe en caractères | Auto |
min_width / max_width | Limites de largeur | Aucun |
header_style | Style de l’en-tête | Aucun |
Styles de bordure
Section intitulée « Styles de bordure »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 simplestable = Table(box=box.HEAVY) # Traits épaistable = Table(box=box.DOUBLE) # Double lignetable = Table(box=None) # Sans bordureIntégrons la table à notre script :
"""health_check.py — Étape 3 : résultats en table."""
import randomimport timefrom rich.console import Consolefrom rich.table import Tablefrom 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 servicesresultats = []for svc in services: resultat = scanner_service(svc) resultats.append({**svc, **resultat})
# Construire la tabletable = 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.
Étape 4 : ajouter une barre de progression
Section intitulée « Étape 4 : ajouter une barre de progression »Rich propose deux façons d’afficher une barre de progression :
La version simple : track()
Section intitulée « La version simple : track() »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 iciTraitement... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 67% 0:00:01La version avancée : Progress()
Section intitulée « La version avancée : Progress() »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:00Installation... ━━━━━━━━━━━━━━━━ 45% 0:00:02Intégrons track() à notre script pour montrer la progression du scan :
"""health_check.py — Étape 4 : barre de progression."""
import randomimport timefrom rich.console import Consolefrom rich.table import Tablefrom rich.progress import trackfrom 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 progressionresultats = []for svc in track(services, description="[cyan]Scan des services..."): resultat = scanner_service(svc) resultats.append({**svc, **resultat})
console.print()
# Table des résultatstable = 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.
Étape 5 : afficher une arborescence avec Tree
Section intitulée « Étape 5 : afficher une arborescence avec Tree »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 racinearbre = Tree(":cloud: Mon infrastructure")
# Ajouter des branchesfrontend = 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)Encadrer avec Panel
Section intitulée « Encadrer avec Panel »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 contenuconsole.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 randomimport timefrom rich.console import Consolefrom rich.table import Tablefrom rich.panel import Panelfrom rich.tree import Treefrom rich.progress import trackfrom 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'architecturearbre = 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 progressionresultats = []for svc in track(services, description="[cyan]Scan des services..."): resultat = scanner_service(svc) resultats.append({**svc, **resultat})
console.print()
# 3. Table des résultatstable = 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.
Étape 6 : ajouter des logs enrichis
Section intitulée « Étape 6 : ajouter des logs enrichis »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 loggingfrom rich.logging import RichHandler
# Configuration en une lignelogging.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:16Chaque 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.
Options de RichHandler
Section intitulée « Options de RichHandler »| Option | Description | Défaut |
|---|---|---|
rich_tracebacks | Tracebacks enrichis et colorés | False |
show_time | Afficher l’heure | True |
show_level | Afficher le niveau (INFO, ERROR…) | True |
show_path | Afficher fichier:ligne | True |
markup | Interpréter le markup Rich dans les messages | False |
tracebacks_show_locals | Afficher les variables locales dans les tracebacks | False |
Étape 7 : des tracebacks lisibles
Section intitulée « Étape 7 : des tracebacks lisibles »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 scriptinstall(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'╭──────── Traceback (most recent call last) ────────╮│ health_check.py:12 in <module> ││ ││ 10 │ data = {"app": "web", "port": 8080} ││ 11 │ ││ ❱ 12 │ result = process_config(data) ││ 13 │ ││ ╭─ locals ──╮ ││ │ data = {'app': 'web', 'port': 8080} ││ ╰───────────╯ ││ ││ health_check.py:8 in process_config ││ ❱ 8 │ return config["database"]["host"] ││ ╭─ locals ──╮ ││ │ config = {'app': 'web', 'port': 8080} ││ ╰───────────╯ │╰────────────────────────────────────────────────────╯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.
Étape 8 : Status et Live (affichage dynamique)
Section intitulée « Étape 8 : Status et Live (affichage dynamique) »Spinner avec console.status()
Section intitulée « Spinner avec console.status() »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.
Mise à jour en temps réel avec Live
Section intitulée « Mise à jour en temps réel avec Live »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.
Étape bonus : explorer un objet avec inspect()
Section intitulée « Étape bonus : explorer un objet avec inspect() »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 dictionnaireinspect({"nom": "web-1", "ip": "10.0.1.10"})
# Inspecter avec les méthodes visiblesinspect(["a", "b", "c"], methods=True)
# Inspecter un module entierimport osinspect(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(...) ││ ... │╰─────────────────────────────────────────────────╯Script final complet
Section intitulée « Script final complet »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 loggingimport randomimport time
from rich.console import Consolefrom rich.logging import RichHandlerfrom rich.panel import Panelfrom rich.progress import trackfrom rich.table import Tablefrom rich.traceback import installfrom rich.tree import Treefrom 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 :
python health_check.pyLe 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.
Exporter la sortie
Section intitulée « Exporter la sortie »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'enregistrementconsole = 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")Bonnes pratiques
Section intitulée « Bonnes pratiques »Quand utiliser chaque fonctionnalité
Section intitulée « Quand utiliser chaque fonctionnalité »| Besoin | Fonctionnalité Rich | Quand l’utiliser |
|---|---|---|
| Afficher un résultat | console.print() + markup | Toujours — remplace print() |
| Structurer des données | Table | Listes de serveurs, résultats, comparaisons |
| Montrer une hiérarchie | Tree + Panel | Architecture, arborescence fichiers, organisations |
| Suivre un traitement long | track() | Boucle dont tu connais le nombre d’itérations |
| Tâches parallèles | Progress() | Plusieurs progressions simultanées |
| Attente indéterminée | console.status() | Connexion, chargement, sans nombre d’étapes |
| Mise à jour en place | Live | Dashboard, monitoring en temps réel |
| Journaliser | RichHandler | Tout script qui utilise logging |
| Débugger un crash | install() de traceback | En début de script, toujours |
| Explorer un objet | inspect() | En debug uniquement |
Performance
Section intitulée « Performance »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.
Compatibilité terminal
Section intitulée « Compatibilité terminal »- 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.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
| Pas de couleurs dans le terminal | Terminal ne supporte pas les couleurs | Vérifie avec python -m rich |
| Couleurs absentes en CI/CD | stdout n’est pas un TTY | Console(force_terminal=True) |
| Texte tronqué dans les tables | Terminal trop étroit | Console(width=120) ou réduis les colonnes |
[red]texte[/red] affiché tel quel | Tu utilises print() au lieu de console.print() | Remplace par Console().print() |
| Markup interprété dans les logs | markup=True sur RichHandler | Mets markup=False si tu logues des données brutes |
| Emoji non affiché | Terminal sans support emoji | Console(emoji=False) |
| Export HTML sans couleurs | Oubli de record=True | Console(record=True) avant les print() |
À retenir
Section intitulée « À retenir »- Rich remplace
print()parConsole.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.TreeetPanelaffichent des hiérarchies et encadrent les résultats — pour un rendu professionnel.RichHandlerremplace le logging standard avec horodatage, couleurs par niveau et fichier source.install()derich.tracebackrend 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.