Initial commit: WealthWise financial analytics platform
This commit is contained in:
129
backend/app/core/config.py
Normal file
129
backend/app/core/config.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user