Files
yellow-bank-soal/backend/app/core/config.py
2026-06-20 01:43:39 +07:00

152 lines
4.8 KiB
Python

"""
Application configuration using Pydantic Settings.
Loads configuration from environment variables with validation.
"""
from typing import Annotated, Literal, List, Union
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
)
# Database
DATABASE_URL: str = Field(
default="postgresql+asyncpg://postgres:postgres@localhost:5432/irt_bank_soal",
description="PostgreSQL database URL with asyncpg driver",
)
# FastAPI
SECRET_KEY: str = Field(
default="dev-secret-key-change-in-production",
description="Secret key for JWT token signing",
)
API_V1_STR: str = Field(default="/api/v1", description="API v1 prefix")
PROJECT_NAME: str = Field(default="IRT Bank Soal", description="Project name")
ENVIRONMENT: Literal["development", "staging", "production"] = Field(
default="development", description="Environment name"
)
ENABLE_ADMIN: bool = Field(
default=False,
description="Enable admin UI and admin-only API routes",
)
ADMIN_USERNAME: str = Field(
default="",
description="Admin panel username",
)
ADMIN_PASSWORD: str = Field(
default="",
description="Admin panel password (plain env value)",
)
ADMIN_SESSION_EXPIRE_SECONDS: int = Field(
default=3600,
description="Admin session lifetime in seconds",
)
# OpenRouter (AI Generation)
OPENROUTER_API_KEY: str = Field(
default="", description="OpenRouter API key for AI generation"
)
OPENROUTER_MODEL_QWEN: str = Field(
default="qwen/qwen2.5-32b-instruct",
description="Balanced Qwen model identifier",
)
OPENROUTER_MODEL_CHEAP: str = Field(
default="mistralai/mistral-small-2603",
description="Low-cost model identifier",
)
OPENROUTER_MODEL_LLAMA: str = Field(
default="meta-llama/llama-3.3-70b-instruct",
description="Premium Llama model identifier",
)
OPENROUTER_TIMEOUT: int = Field(default=30, description="OpenRouter API timeout in seconds")
OPENROUTER_PROVIDER_ORDER: List[str] = Field(
default=["NovitaAI", "AkashML", "Inception"],
description="Preferred OpenRouter providers in priority order",
)
OPENROUTER_ALLOW_PROVIDER_FALLBACKS: bool = Field(
default=True,
description="Allow OpenRouter to fallback outside preferred providers",
)
# WordPress Integration
WORDPRESS_API_URL: str = Field(
default="", description="WordPress REST API base URL"
)
WORDPRESS_AUTH_TOKEN: str = Field(
default="", description="WordPress JWT authentication token"
)
# Redis (Celery)
REDIS_URL: str = Field(
default="redis://localhost:6379/0", description="Redis connection URL"
)
CELERY_BROKER_URL: str = Field(
default="redis://localhost:6379/0", description="Celery broker URL"
)
CELERY_RESULT_BACKEND: str = Field(
default="redis://localhost:6379/0", description="Celery result backend URL"
)
# CORS - stored as list, accepts comma-separated string from env
ALLOWED_ORIGINS: Annotated[List[str], NoDecode] = Field(
default=["http://localhost:3000", "http://localhost:5173", "http://127.0.0.1:5173"],
description="List of allowed CORS origins",
)
@field_validator("ALLOWED_ORIGINS", mode="before")
@classmethod
def parse_allowed_origins(cls, v: Union[str, List[str]]) -> List[str]:
"""Parse comma-separated origins into list."""
if isinstance(v, str):
return [origin.strip() for origin in v.split(",") if origin.strip()]
return v
@field_validator("OPENROUTER_PROVIDER_ORDER", mode="before")
@classmethod
def parse_provider_order(cls, v: Union[str, List[str]]) -> List[str]:
"""Parse comma-separated OpenRouter provider list into array."""
if isinstance(v, str):
return [provider.strip() for provider in v.split(",") if provider.strip()]
return v
# Global settings instance
_settings: Union[Settings, None] = None
def get_settings() -> Settings:
"""
Get application settings instance.
Returns:
Settings: Application settings
Raises:
ValueError: If settings not initialized
"""
global _settings
if _settings is None:
_settings = Settings()
return _settings
def init_settings(settings: Settings) -> None:
"""
Initialize settings with custom instance (useful for testing).
Args:
settings: Settings instance to use
"""
global _settings
_settings = settings