"""WealthWise application configuration management. This module provides centralized configuration management using Pydantic Settings v2. Configuration values are loaded from environment variables and .env files. """ from functools import lru_cache from typing import Any, Optional, Union from pydantic import Field, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """Application settings management. All configuration values are loaded from environment variables. The .env file is automatically loaded when present. System environment variables override values in .env. Attributes: PROJECT_NAME: Application name for documentation and logging API_V1_STR: Base path for API v1 endpoints VERSION: Application version string DATABASE_URL: PostgreSQL connection string (Supabase Transaction Pooler format) SUPABASE_JWT_SECRET: JWT secret for Supabase authentication DEBUG: Enable debug mode (default: False) CORS_ORIGINS: Comma-separated list of allowed CORS origins """ model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", extra="ignore", # Allow extra env vars without raising errors case_sensitive=True, ) # Application Info PROJECT_NAME: str = Field(default="WealthWise", description="Application name") API_V1_STR: str = Field(default="/api/v1", description="API v1 base path") VERSION: str = Field(default="1.0.0", description="Application version") # Security SECRET_KEY: str = Field( default="dev-secret-key-change-in-production", description="Secret key for JWT signing", ) ALGORITHM: str = Field( default="HS256", description="JWT signing algorithm", ) ACCESS_TOKEN_EXPIRE_MINUTES: int = Field( default=30, description="JWT token expiration in minutes", ) # Database - Local PostgreSQL (Docker) DATABASE_URL: str = Field( default="postgresql+asyncpg://postgres:postgres@localhost:5432/wealthwise", description="PostgreSQL async connection string", ) # Database Pool Configuration DB_POOL_SIZE: int = Field(default=20, ge=1, le=100, description="Connection pool size") DB_MAX_OVERFLOW: int = Field(default=10, ge=0, le=50, description="Max overflow connections") DB_POOL_PRE_PING: bool = Field( default=True, description="Verify connections before using from pool", ) DB_ECHO: bool = Field(default=False, description="Echo SQL queries to stdout") # API Configuration DEBUG: bool = Field(default=False, description="Debug mode") CORS_ORIGINS: str = Field( default="http://localhost:5173,http://localhost:3000", description="Comma-separated list of allowed CORS origins", ) # Logging LOG_LEVEL: str = Field(default="INFO", pattern="^(DEBUG|INFO|WARNING|ERROR|CRITICAL)$") @property def cors_origins_list(self) -> list[str]: """Parse CORS_ORIGINS string into a list. Returns: List of origin strings """ return [origin.strip() for origin in self.CORS_ORIGINS.split(",") if origin.strip()] @field_validator("DATABASE_URL") @classmethod def validate_database_url(cls, v: Optional[str]) -> Any: """Validate and ensure proper asyncpg driver in DATABASE_URL. Args: v: Database URL string Returns: Validated database URL with asyncpg driver Raises: ValueError: If URL format is invalid """ if not v: raise ValueError("DATABASE_URL cannot be empty") # Ensure asyncpg driver is used if v.startswith("postgresql://"): v = v.replace("postgresql://", "postgresql+asyncpg://", 1) elif not v.startswith("postgresql+asyncpg://"): raise ValueError( "DATABASE_URL must use postgresql+asyncpg:// driver for async support" ) return v @lru_cache() def get_settings() -> Settings: """Get cached settings instance. This function uses LRU caching to avoid re-reading configuration on every call. Settings are loaded once at application startup. Returns: Settings instance with all configuration values """ return Settings()