"""Configuration de l'application HomeStock. Utilise Pydantic Settings pour charger et valider les variables d'environnement. Documentation : https://docs.pydantic.dev/latest/concepts/pydantic_settings/ """ from functools import lru_cache from pathlib import Path from typing import Literal from pydantic import Field, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """Configuration globale de l'application. Les valeurs sont chargées depuis les variables d'environnement ou le fichier .env. """ model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore", ) # === Application === APP_NAME: str = Field(default="HomeStock", description="Nom de l'application") APP_VERSION: str = Field(default="0.1.0", description="Version de l'application") ENVIRONMENT: Literal["development", "production"] = Field( default="development", description="Environnement d'exécution" ) DEBUG: bool = Field(default=True, description="Mode debug") LOG_LEVEL: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = Field( default="DEBUG", description="Niveau de log" ) # === Serveur === BACKEND_HOST: str = Field(default="0.0.0.0", description="Host du serveur backend") BACKEND_PORT: int = Field(default=8000, description="Port du serveur backend") BACKEND_RELOAD: bool = Field( default=True, description="Hot reload en développement" ) # === Base de données === DATABASE_URL: str = Field( default="sqlite+aiosqlite:///./data/homestock.db", description="URL de connexion à la base de données", ) @field_validator("DATABASE_URL") @classmethod def validate_database_url(cls, v: str) -> str: """Valide et normalise l'URL de la base de données.""" # Pour SQLite, s'assurer que le driver async est utilisé if v.startswith("sqlite:///"): return v.replace("sqlite:///", "sqlite+aiosqlite:///") return v # === CORS === CORS_ORIGINS: str = Field( default="http://localhost:5173,http://10.0.0.50:5173", description="Origines autorisées pour CORS (séparées par des virgules)", ) CORS_ALLOW_CREDENTIALS: bool = Field( default=False, description="Autorise les credentials CORS" ) @property def cors_origins_list(self) -> list[str]: """Retourne la liste des origines CORS autorisées.""" return [origin.strip() for origin in self.CORS_ORIGINS.split(",")] # === Stockage fichiers === UPLOAD_DIR: str = Field( default="./uploads", description="Répertoire des uploads", ) MAX_UPLOAD_SIZE_MB: int = Field( default=50, description="Taille max des uploads en Mo" ) ALLOWED_EXTENSIONS: str = Field( default="jpg,jpeg,png,gif,pdf,doc,docx", description="Extensions de fichiers autorisées (séparées par des virgules)", ) @property def allowed_extensions_list(self) -> list[str]: """Retourne la liste des extensions autorisées.""" return [ext.strip().lower() for ext in self.ALLOWED_EXTENSIONS.split(",")] @property def max_upload_size_bytes(self) -> int: """Retourne la taille max en octets.""" return self.MAX_UPLOAD_SIZE_MB * 1024 * 1024 @property def upload_dir_path(self) -> Path: """Retourne le chemin du répertoire d'uploads comme Path.""" return Path(self.UPLOAD_DIR).resolve() # === Recherche === SEARCH_MIN_QUERY_LENGTH: int = Field( default=2, description="Longueur minimale des requêtes de recherche" ) # === Propriétés calculées === @property def is_development(self) -> bool: """Indique si l'environnement est en développement.""" return self.ENVIRONMENT == "development" @property def is_production(self) -> bool: """Indique si l'environnement est en production.""" return self.ENVIRONMENT == "production" @lru_cache def get_settings() -> Settings: """Retourne l'instance singleton de la configuration. Utilise lru_cache pour ne charger la configuration qu'une seule fois. """ return Settings() # Instance globale de configuration settings = get_settings()