Files
WealthWise/backend/app/core/config.py

130 lines
4.4 KiB
Python

"""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()