Travail avec les fichiers de configuration YAML¶
Qu’est-ce que YAML ?¶
YAML (YAML Ain’t Markup Language) est un standard de sérialisation de données humainement compréhensible pour tous les langages de programmation. Il est couramment utilisé pour les fichiers de configuration et l’échange de données entre systèmes.
Caractéristiques clés de YAML¶
- Lisible par l’homme : Syntaxe propre et intuitive facile à comprendre
- Supporte des structures de données complexes : Listes, dictionnaires, objets imbriqués
- Commentaires : Peut inclure des commentaires pour la documentation
- Inférence de types : Détecte automatiquement les types de données
- Sensible aux espaces : Utilise l’indentation pour la structure (comme Python)
YAML vs autres formats¶
| Caractéristique | YAML | JSON | TOML | INI |
|---|---|---|---|---|
| Lisible par l’homme | ✅ Excellent | ❌ Verbose | ✅ Bon | ✅ Bon |
| Commentaires | ✅ Oui | ❌ Non | ✅ Oui | ✅ Oui |
| Types de données | ✅ Riches | ✅ Bon | ✅ Riches | ❌ Limités |
| Hiérarchique | ✅ Excellent | ✅ Oui | ✅ Oui | ❌ Limité |
| Complexité | ❌ Peut être complexe | ✅ Simple | ✅ Simple | ✅ Simple |
Où YAML est utilisé¶
YAML est largement utilisé dans divers domaines :
DevOps et Cloud Computing¶
- Docker Compose - Orchestration de conteneurs
- Kubernetes - Configuration de cluster
- Ansible - Infrastructure as code
- Terraform - Provisionnement d’infrastructure
Projets Python¶
- PyYAML - Parseur YAML pour Python
- Sphinx - Générateur de documentation
- Travis CI - Configuration d’intégration continue
- GitHub Actions - Définitions de workflows
Pourquoi YAML pour la configuration ?¶
- Lisible : Les utilisateurs non techniques peuvent comprendre et modifier les paramètres
- Flexible : Supporte des structures imbriquées complexes
- Commentaires : Documenter pourquoi certains paramètres existent
- Inférence de types : Réduit les erreurs de configuration
- Compatible avec le contrôle de version : Diffs clairs lorsque les paramètres changent
Installation de la bibliothèque YAML¶
Python dispose de plusieurs bibliothèques YAML disponibles :
# PyYAML - Bibliothèque YAML la plus populaire pour Python
pip install pyyaml
# Alternative : ruamel.yaml (supporte la préservation du parcours aller-retour)
pip install ruamel.yaml
Opérations YAML de base¶
Syntaxe YAML de base¶
# Ceci est un commentaire en YAML
# Valeurs scalaires
nom: "Mon Application"
version: 1.0.0
debug: true
max_connexions: 100
timeout: 30.5
# Listes (tableaux)
hôtes_autorisés:
- localhost
- 127.0.0.1
- example.com
ports: [ 8000, 8001, 8002 ]
# Dictionnaires (mappings)
base_de_données:
hôte: localhost
port: 5432
nom: myapp
utilisateur: admin
# Structures imbriquées
serveur:
hôte: 0.0.0.0
port: 8000
workers: 4
base_de_données:
taille_pool: 10
timeout: 30
Lecture des fichiers YAML¶
import yaml
def lire_config(fichier):
"""Lire le fichier de configuration YAML"""
try:
with open(fichier, 'r', encoding='utf-8') as file:
config = yaml.safe_load(file)
return config if config else {}
except FileNotFoundError:
print(f"Fichier de configuration {fichier} introuvable")
return {}
except yaml.YAMLError as e:
print(f"Erreur lors de l'analyse du fichier YAML: {e}")
return {}
# Utilisation
config = lire_config('config.yaml')
print(f"Nom de l'application: {config.get('nom', 'Inconnu')}")
print(f"Hôte de la base de données: {config.get('base_de_données', {}).get('hôte', 'localhost')}")
Écriture des fichiers YAML¶
import yaml
def écrire_config(fichier, données_config):
"""Écrire la configuration dans un fichier YAML"""
try:
with open(fichier, 'w', encoding='utf-8') as file:
yaml.dump(données_config, file, sort_keys=False)
print(f"Configuration sauvegardée dans {fichier}")
return True
except Exception as e:
print(f"Erreur lors de l'écriture de la configuration: {e}")
return False
# Données de configuration d'exemple
config = {
'nom': 'Mon Application',
'version': 1.0,
'debug': True,
'base_de_données': {
'hôte': 'localhost',
'port': 5432,
'nom': 'myapp'
},
'hôtes_autorisés': ['localhost', '127.0.0.1']
}
# Écrire la configuration
écrire_config('output.yaml', config)
Exemple complet de configuration d’application¶
Fichier de configuration d’exemple (app_config.yaml)¶
# Fichier de Configuration de l'Application
# Généré automatiquement - modifier avec précaution
application:
nom: "Serveur WebAPI"
version: "2.1.0"
description: "Un serveur web API haute performance"
debug: false
environnement: "production"
serveur:
hôte: "0.0.0.0"
port: 8000
workers: 4
timeout: 30
taille_max_request: "10MB"
méthodes_autorisées:
- GET
- POST
- PUT
- DELETE
base_de_données:
moteur: "postgresql"
hôte: "db.example.com"
port: 5432
nom: "webapp_prod"
utilisateur: "webapp_user"
# Note: Le mot de passe doit être dans les variables d'environnement, pas dans les fichiers de configuration
max_connexions: 20
timeout: 30.0
ssl_requis: true
pool:
taille_min: 5
taille_max: 20
timeout_acquisition: 10.0
temps_recyclage: 3600
cache:
activé: true
backend: "redis"
hôte: "cache.example.com"
port: 6379
base_de_données: 0
préfixe_clé: "webapp:"
timeout_par_defaut: 300
logs:
niveau: "INFO"
format: "{temps} | {niveau} | {nom} | {message}"
fichier_activé: true
chemin_fichier: "/var/log/webapp/app.log"
rotation_fichier: "quotidienne"
rétention_fichier: 30
console:
activée: true
colorée: true
email:
activé: true
hôte_smtp: "smtp.example.com"
port_smtp: 587
utiliser_tls: true
adresse_expéditeur: "noreply@example.com"
emails_admin:
- "admin@example.com"
- "ops@example.com"
sécurité:
clé_secrète_env: "APP_SECRET_KEY" # Nom de la variable d'environnement
durée_jwt_heures: 24
tours_bcrypt: 12
limite_taux_minute: 60
origines_cors:
- "https://app.example.com"
- "https://admin.example.com"
fonctionnalités:
inscription_utilisateur: true
vérification_email: true
authentification_2_facteurs: false
tableau_de_bord_admin: true
analytics_api: true
intégrations:
- nom: "paiement_gateway"
activé: true
url_base: "https://api.payments.com"
timeout: 15
tentatives_reessai: 3
- nom: "service_email"
activé: true
url_base: "https://api.emailprovider.com"
timeout: 10
tentatives_reessai: 2
surveillance:
activée: true
port_métriques: 9090
chemin_vérification_santé: "/health"
chemin_métriques: "/metrics"
alertes:
seuil_cpu: 80.0
seuil_mémoire: 85.0
seuil_disque: 90.0
seuil_temps_reponse: 1000 # millisecondes
Classe de gestionnaire de configuration¶
import yaml
from pathlib import Path
from typing import Dict, Any, Optional
import os
class GestionnaireConfigurationYAML:
"""Gère la configuration de l'application en utilisant des fichiers YAML"""
def __init__(self, fichier_config: str = "config.yaml"):
self.fichier_config = Path(fichier_config)
self.config = {}
self._charger_config()
def _charger_config(self):
"""Charger la configuration depuis le fichier YAML"""
try:
if self.fichier_config.exists():
with open(self.fichier_config, 'r', encoding='utf-8') as file:
self.config = yaml.safe_load(file)
print(f"Configuration chargée depuis {self.fichier_config}")
else:
print(f"Fichier de configuration {self.fichier_config} introuvable, utilisation des valeurs par défaut")
self._créer_config_par_défaut()
except yaml.YAMLError as e:
print(f"Erreur lors de l'analyse de la configuration YAML: {e}")
print("Utilisation de la configuration par défaut")
self._créer_config_par_défaut()
def _créer_config_par_défaut(self):
"""Créer une configuration par défaut"""
self.config = {
'application': {
'nom': 'Mon Application',
'version': 1.0,
'debug': True,
'environnement': 'développement'
},
'serveur': {
'hôte': 'localhost',
'port': 8000,
'workers': 1
},
'base_de_données': {
'hôte': 'localhost',
'port': 5432,
'nom': 'myapp_dev',
'max_connexions': 10
},
'logs': {
'niveau': 'DEBUG',
'console': {'activée': True}
}
}
def obtenir(self, chemin_clé: str, valeur_par_défaut: Any = None) -> Any:
"""
Obtenir la valeur de configuration en utilisant la notation pointée
Exemple : obtenir('base_de_données.hôte') ou obtenir('serveur.port')
"""
clés = chemin_clé.split('.')
valeur = self.config
try:
for clé in clés:
valeur = valeur[clé]
return valeur
except (KeyError, TypeError):
return valeur_par_défaut
def définir(self, chemin_clé: str, valeur: Any):
"""
Définir la valeur de configuration en utilisant la notation pointée
Exemple : définir('base_de_données.hôte', 'nouvel-hôte')
"""
clés = chemin_clé.split('.')
section_config = self.config
# Naviguer jusqu'à la section parente
for clé in clés[:-1]:
if clé not in section_config:
section_config[clé] = {}
section_config = section_config[clé]
# Définir la valeur finale
section_config[clés[-1]] = valeur
def sauvegarder(self, fichier: Optional[str] = None):
"""Sauvegarder la configuration actuelle dans un fichier YAML"""
fichier_cible = Path(fichier) if fichier else self.fichier_config
try:
with open(fichier_cible, 'w', encoding='utf-8') as file:
yaml.dump(self.config, file, sort_keys=False)
print(f"Configuration sauvegardée dans {fichier_cible}")
return True
except Exception as e:
print(f"Erreur lors de la sauvegarde de la configuration: {e}")
return False
def valider(self) -> bool:
"""Valider la configuration pour les champs requis"""
sections_requises = ['application', 'serveur', 'base_de_données']
erreurs = []
for section in sections_requises:
if section not in self.config:
erreurs.append(f"Section requise manquante: {section}")
# Valider les champs spécifiques requis
champs_requis = {
'application.nom': str,
'application.version': (str, int, float),
'serveur.hôte': str,
'serveur.port': int,
'base_de_données.hôte': str,
'base_de_données.port': int
}
for chemin_champ, types_attendus in champs_requis.items():
valeur = self.obtenir(chemin_champ)
if valeur is None:
erreurs.append(f"Champ requis manquant: {chemin_champ}")
elif not isinstance(valeur, types_attendus):
noms_types = ', '.join(t.__name__ for t in types_attendus)
erreurs.append(f"Le champ {chemin_champ} doit être de type {noms_types}")
if erreurs:
print("Erreurs de validation de la configuration:")
for erreur in erreurs:
print(f" - {erreur}")
return False
print("Validation de la configuration réussie")
return True
def obtenir_url_base_de_données(self) -> str:
"""Générer l'URL de la base de données à partir de la configuration"""
config_bdd = self.config.get('base_de_données', {})
utilisateur = config_bdd.get('utilisateur', '')
mot_de_passe = os.getenv('DB_PASSWORD', '') # Obtenir depuis l'environnement
hôte = config_bdd.get('hôte', 'localhost')
port = config_bdd.get('port', 5432)
nom = config_bdd.get('nom', 'myapp')
if utilisateur and mot_de_passe:
return f"postgresql://{utilisateur}:{mot_de_passe}@{hôte}:{port}/{nom}"
else:
return f"postgresql://{hôte}:{port}/{nom}"
def est_développement(self) -> bool:
"""Vérifier si l'exécution se fait en environnement de développement"""
return self.obtenir('application.environnement', 'développement') == 'développement'
def est_debug_activé(self) -> bool:
"""Vérifier si le mode debug est activé"""
return self.obtenir('application.debug', False)
def obtenir_adresse_serveur(self) -> tuple:
"""Obtenir l'hôte et le port du serveur sous forme de tuple"""
hôte = self.obtenir('serveur.hôte', 'localhost')
port = self.obtenir('serveur.port', 8000)
return (hôte, port)
def afficher_résumé(self):
"""Afficher un résumé de la configuration"""
print(f"\n=== Résumé de la Configuration ===")
print(f"Application: {self.obtenir('application.nom')} v{self.obtenir('application.version')}")
print(f"Environnement: {self.obtenir('application.environnement')}")
print(f"Debug: {self.obtenir('application.debug')}")
print(f"Serveur: {self.obtenir('serveur.hôte')}:{self.obtenir('serveur.port')}")
print(f"Base de données: {self.obtenir('base_de_données.hôte')}:{self.obtenir('base_de_données.port')}")
print(f"Niveau de log: {self.obtenir('logs.niveau')}")
if self.obtenir('fonctionnalités'):
fonctionnalités_activées = [k for k, v in self.obtenir('fonctionnalités', {}).items() if v]
print(f"Fonctionnalités: {', '.join(fonctionnalités_activées)}")
print("=" * 30)
# Exemple d'utilisation et de test
def main():
"""Exemple d'utilisation du GestionnaireConfigurationYAML"""
# Initialiser le gestionnaire de configuration
config = GestionnaireConfigurationYAML('app_config.yaml')
# Valider la configuration
if not config.valider():
print("Validation de la configuration échouée!")
return
# Afficher le résumé de la configuration
config.afficher_résumé()
# Accéder aux valeurs de configuration
print(f"\nAccès à la configuration:")
print(f"Nom de l'application: {config.obtenir('application.nom')}")
print(f"Port du serveur: {config.obtenir('serveur.port')}")
print(f"Hôte de la base de données: {config.obtenir('base_de_données.hôte')}")
print(f"Debug activé: {config.est_debug_activé()}")
print(f"Mode développement: {config.est_développement()}")
# Obtenir l'URL de la base de données
print(f"URL de la base de données: {config.obtenir_url_base_de_données()}")
# Accéder à la configuration imbriquée
niveau_logs = config.obtenir('logs.niveau', 'INFO')
console_activée = config.obtenir('logs.console.activée', True)
print(f"Logs: {niveau_logs}, Console: {console_activée}")
# Accéder aux configurations de liste
méthodes_autorisées = config.obtenir('serveur.méthodes_autorisées', [])
print(f"Méthodes HTTP autorisées: {méthodes_autorisées}")
# Accéder à la liste de dictionnaires
intégrations = config.obtenir('intégrations', [])
intégrations_activées = [i['nom'] for i in intégrations if i.get('activé')]
print(f"Intégrations activées: {intégrations_activées}")
# Démonstration de la modification de configuration
print(f"\nModification de la configuration:")
config.définir('application.debug', False)
config.définir('serveur.port', 9000)
print(f"Debug maintenant: {config.obtenir('application.debug')}")
print(f"Port maintenant: {config.obtenir('serveur.port')}")
# Sauvegarder la configuration modifiée
config.sauvegarder('modified_config.yaml')
if __name__ == "__main__":
main()
Bonnes pratiques pour la configuration YAML¶
1. Structure et organisation¶
- Regrouper les paramètres liés dans des dictionnaires imbriqués
- Utiliser des noms de clés clairs et descriptifs
- Garder l’imbrication à un niveau raisonnable (2-3 niveaux max)
- Utiliser des conventions de nommage cohérentes (snake_case recommandé)
2. Considérations de sécurité¶
# Bon : Référencer les variables d'environnement pour les secrets
base_de_données:
mot_de_passe_env: "DB_PASSWORD" # Nom de la variable d'environnement
# Mauvais : Ne jamais stocker les secrets dans les fichiers de configuration
# mot_de_passe: "super_secret_password" # NE PAS FAIRE ÇA
sécurité:
clé_secrète_env: "APP_SECRET_KEY"
jwt_secrétaire_env: "JWT_SECRET"
3. Documentation et commentaires¶
# Configuration de l'Application
# Dernière mise à jour : 2024-01-15
# Environnement : Production
serveur:
# Nombre maximum de processus workers
# Recommandé : 2 * nombre de cœurs CPU
workers: 4
# Timeout des requêtes en secondes
# Augmenter pour les endpoints lents
timeout: 30
4. Validation et gestion des erreurs¶
Toujours valider votre configuration :
def valider_types_config(config):
"""Valider les types de données de la configuration"""
vérifications_type = [
('serveur.port', int, lambda x: 1 <= x <= 65535),
('serveur.workers', int, lambda x: x > 0),
('base_de_données.max_connexions', int, lambda x: x > 0),
('application.debug', bool, None),
]
for chemin, type_attendu, validateur in vérifications_type:
valeur = config.get(chemin)
if valeur is not None:
if not isinstance(valeur, type_attendu):
raise ValueError(f"{chemin} doit être {type_attendu.__name__}")
if validateur and not validateur(valeur):
raise ValueError(f"{chemin} a échoué la validation")
Points clés à retenir¶
- YAML est pour la configuration : Parfait pour les paramètres d’application, pas pour le stockage de données général
- Lisible par l’homme : Conçu pour être modifié par des humains
- Inférence de types : Détecte automatiquement les types de données
- Les commentaires comptent : Documenter vos choix de configuration
- Séparation des environnements : Utiliser différents fichiers pour différents environnements
- Sécurité d’abord : Ne jamais stocker les secrets dans les fichiers de configuration
- Validation : Toujours valider la configuration après chargement
- Valeurs par défaut : Fournir des valeurs par défaut sensées pour les paramètres optionnels
Erreurs courantes à éviter¶
- Stocker les mots de passe ou clés API dans les fichiers YAML
- Créer des structures imbriquées trop complexes
- Ne pas valider la configuration des données
- Mélanger la configuration avec les données d’exécution
- Ne pas utiliser les configurations spécifiques à l’environnement
- Oublier de gérer les fichiers de configuration manquants
- Ne pas documenter les options de configuration
YAML offre un excellent support pour la configuration complexe des applications, rendant facile pour les développeurs et les opérateurs de comprendre et modifier les paramètres d’application tout en maintenant la lisibilité.