python-configuration

Par wshobson · agents

Gestion de la configuration Python via les variables d'environnement et les paramètres typés. À utiliser pour externaliser la configuration, configurer pydantic-settings, gérer les secrets ou implémenter un comportement spécifique à l'environnement.

npx skills add https://github.com/wshobson/agents --skill python-configuration

Gestion de la Configuration en Python

Externalisez la configuration du code en utilisant des variables d'environnement et des paramètres typés. Une configuration bien gérée permet à un même code de s'exécuter dans n'importe quel environnement sans modification.

Quand utiliser cette compétence

  • Mettre en place le système de configuration d'un nouveau projet
  • Migrer des valeurs codées en dur vers des variables d'environnement
  • Implémenter pydantic-settings pour une configuration typée
  • Gérer les secrets et valeurs sensibles
  • Créer des paramètres spécifiques à chaque environnement (dev/staging/prod)
  • Valider la configuration au démarrage de l'application

Concepts fondamentaux

1. Configuration externalisée

Toutes les valeurs spécifiques à un environnement (URLs, secrets, feature flags) proviennent de variables d'environnement, pas du code.

2. Paramètres typés

Analysez et validez la configuration en objets typés au démarrage, et non dispersés dans le code.

3. Échouer rapidement

Validez toute la configuration requise au démarrage de l'application. Une configuration manquante doit s'arrêter immédiatement avec un message clair.

4. Valeurs par défaut raisonnables

Fournissez des valeurs par défaut raisonnables pour le développement local tout en exigeant des valeurs explicites pour les paramètres sensibles.

Démarrage rapide

from pydantic_settings import BaseSettings
from pydantic import Field

class Settings(BaseSettings):
    database_url: str = Field(alias="DATABASE_URL")
    api_key: str = Field(alias="API_KEY")
    debug: bool = Field(default=False, alias="DEBUG")

settings = Settings()  # Charge depuis l'environnement

Modèles fondamentaux

Modèle 1 : Paramètres typés avec Pydantic

Créez une classe de paramètres centralisée qui charge et valide toute la configuration.

from pydantic_settings import BaseSettings
from pydantic import Field, PostgresDsn, ValidationError
import sys

class Settings(BaseSettings):
    """Configuration de l'application chargée depuis les variables d'environnement."""

    # Base de données
    db_host: str = Field(alias="DB_HOST")
    db_port: int = Field(default=5432, alias="DB_PORT")
    db_name: str = Field(alias="DB_NAME")
    db_user: str = Field(alias="DB_USER")
    db_password: str = Field(alias="DB_PASSWORD")

    # Redis
    redis_url: str = Field(default="redis://localhost:6379", alias="REDIS_URL")

    # Clés API
    api_secret_key: str = Field(alias="API_SECRET_KEY")

    # Feature flags
    enable_new_feature: bool = Field(default=False, alias="ENABLE_NEW_FEATURE")

    model_config = {
        "env_file": ".env",
        "env_file_encoding": "utf-8",
    }

# Créez une instance singleton au chargement du module
try:
    settings = Settings()
except ValidationError as e:
    print(f"Erreur de configuration :\n{e}")
    sys.exit(1)

Importez settings dans toute votre application :

from myapp.config import settings

def get_database_connection():
    return connect(
        host=settings.db_host,
        port=settings.db_port,
        database=settings.db_name,
    )

Modèle 2 : Échouer rapidement sur configuration manquante

Les paramètres requis doivent arrêter l'application immédiatement avec une erreur claire.

from pydantic_settings import BaseSettings
from pydantic import Field, ValidationError
import sys

class Settings(BaseSettings):
    # Requis - pas de défaut signifie que ce paramètre doit être défini
    api_key: str = Field(alias="API_KEY")
    database_url: str = Field(alias="DATABASE_URL")

    # Optionnel avec défauts
    log_level: str = Field(default="INFO", alias="LOG_LEVEL")

try:
    settings = Settings()
except ValidationError as e:
    print("=" * 60)
    print("ERREUR DE CONFIGURATION")
    print("=" * 60)
    for error in e.errors():
        field = error["loc"][0]
        print(f"  - {field} : {error['msg']}")
    print("\nVeuillez définir les variables d'environnement requises.")
    sys.exit(1)

Une erreur claire au démarrage est préférable à un échec cryptique None au milieu d'une requête.

Modèle 3 : Valeurs par défaut pour le développement local

Fournissez des valeurs par défaut raisonnables pour le développement local tout en exigeant des valeurs explicites pour les secrets.

class Settings(BaseSettings):
    # A une valeur par défaut locale, mais prod l'écrasera
    db_host: str = Field(default="localhost", alias="DB_HOST")
    db_port: int = Field(default=5432, alias="DB_PORT")

    # Toujours requis - pas de défaut pour les secrets
    db_password: str = Field(alias="DB_PASSWORD")
    api_secret_key: str = Field(alias="API_SECRET_KEY")

    # Commodité de développement
    debug: bool = Field(default=False, alias="DEBUG")

    model_config = {"env_file": ".env"}

Créez un fichier .env pour le développement local (ne le committez jamais) :

# .env (ajoutez à .gitignore)
DB_PASSWORD=local_dev_password
API_SECRET_KEY=dev-secret-key
DEBUG=true

Modèle 4 : Variables d'environnement avec espaces de noms

Préfixez les variables associées pour plus de clarté et un débogage facile.

# Configuration de la base de données
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DB_USER=admin
DB_PASSWORD=secret

# Configuration Redis
REDIS_URL=redis://localhost:6379
REDIS_MAX_CONNECTIONS=10

# Authentification
AUTH_SECRET_KEY=your-secret-key
AUTH_TOKEN_EXPIRY_SECONDS=3600
AUTH_ALGORITHM=HS256

# Feature flags
FEATURE_NEW_CHECKOUT=true
FEATURE_BETA_UI=false

Rend env | grep DB_ utile pour le débogage.

Modèles avancés

Modèle 5 : Conversion de type

Pydantic gère automatiquement les conversions courantes.

from pydantic_settings import BaseSettings
from pydantic import Field, field_validator

class Settings(BaseSettings):
    # Convertit automatiquement "true", "1", "yes" en True
    debug: bool = False

    # Convertit automatiquement string en int
    max_connections: int = 100

    # Analyse une string séparée par des virgules en liste
    allowed_hosts: list[str] = Field(default_factory=list)

    @field_validator("allowed_hosts", mode="before")
    @classmethod
    def parse_allowed_hosts(cls, v: str | list[str]) -> list[str]:
        if isinstance(v, str):
            return [host.strip() for host in v.split(",") if host.strip()]
        return v

Utilisation :

ALLOWED_HOSTS=example.com,api.example.com,localhost
MAX_CONNECTIONS=50
DEBUG=true

Modèle 6 : Configuration spécifique à l'environnement

Utilisez une énumération d'environnement pour modifier le comportement.

from enum import Enum
from pydantic_settings import BaseSettings
from pydantic import Field, computed_field

class Environment(str, Enum):
    LOCAL = "local"
    STAGING = "staging"
    PRODUCTION = "production"

class Settings(BaseSettings):
    environment: Environment = Field(
        default=Environment.LOCAL,
        alias="ENVIRONMENT",
    )

    # Paramètres qui varient selon l'environnement
    log_level: str = Field(default="DEBUG", alias="LOG_LEVEL")

    @computed_field
    @property
    def is_production(self) -> bool:
        return self.environment == Environment.PRODUCTION

    @computed_field
    @property
    def is_local(self) -> bool:
        return self.environment == Environment.LOCAL

# Utilisation
if settings.is_production:
    configure_production_logging()
else:
    configure_debug_logging()

Modèle 7 : Groupes de configuration imbriqués

Organisez les paramètres associés en modèles imbriqués.

from pydantic import BaseModel
from pydantic_settings import BaseSettings

class DatabaseSettings(BaseModel):
    host: str = "localhost"
    port: int = 5432
    name: str
    user: str
    password: str

class RedisSettings(BaseModel):
    url: str = "redis://localhost:6379"
    max_connections: int = 10

class Settings(BaseSettings):
    database: DatabaseSettings
    redis: RedisSettings
    debug: bool = False

    model_config = {
        "env_nested_delimiter": "__",
        "env_file": ".env",
    }

Les variables d'environnement utilisent un double tiret bas pour l'imbrication :

DATABASE__HOST=db.example.com
DATABASE__PORT=5432
DATABASE__NAME=myapp
DATABASE__USER=admin
DATABASE__PASSWORD=secret
REDIS__URL=redis://redis.example.com:6379

Modèle 8 : Secrets à partir de fichiers

Pour les environnements de conteneurs, lisez les secrets depuis des fichiers montés.

from pydantic_settings import BaseSettings
from pydantic import Field
from pathlib import Path

class Settings(BaseSettings):
    # Lire depuis une variable d'environnement ou un fichier
    db_password: str = Field(alias="DB_PASSWORD")

    model_config = {
        "secrets_dir": "/run/secrets",  # Emplacement des secrets Docker
    }

Pydantic cherchera /run/secrets/db_password si la variable d'environnement n'est pas définie.

Modèle 9 : Validation de la configuration

Ajoutez une validation personnalisée pour les exigences complexes.

from pydantic_settings import BaseSettings
from pydantic import Field, model_validator

class Settings(BaseSettings):
    db_host: str = Field(alias="DB_HOST")
    db_port: int = Field(alias="DB_PORT")
    read_replica_host: str | None = Field(default=None, alias="READ_REPLICA_HOST")
    read_replica_port: int = Field(default=5432, alias="READ_REPLICA_PORT")

    @model_validator(mode="after")
    def validate_replica_settings(self):
        if self.read_replica_host and self.read_replica_port == self.db_port:
            if self.read_replica_host == self.db_host:
                raise ValueError(
                    "Le replica en lecture ne peut pas être la même que la base de données primaire"
                )
        return self

Résumé des bonnes pratiques

  1. Ne codez jamais la configuration en dur - Toutes les valeurs spécifiques à un environnement proviennent de variables d'environnement
  2. Utilisez les paramètres typés - pydantic-settings avec validation
  3. Échouez rapidement - Arrêtez sur configuration requise manquante au démarrage
  4. Fournissez des valeurs par défaut pour le dev - Facilitez le développement local
  5. Ne committez jamais les secrets - Utilisez des fichiers .env (gitignorés) ou des gestionnaires de secrets
  6. Utilisez des espaces de noms pour les variables - DB_HOST, REDIS_URL pour plus de clarté
  7. Importez le singleton de paramètres - N'appelez pas os.getenv() dans tout le code
  8. Documentez toutes les variables - Le README doit lister les variables d'environnement requises
  9. Validez tôt - Vérifiez la corrección de la configuration au démarrage
  10. Utilisez secrets_dir - Supportez les secrets montés dans les conteneurs

Skills similaires