# config.py — Centralized Configuration using pydantic-settings from pydantic_settings import BaseSettings from functools import lru_cache from typing import Optional import os class Settings(BaseSettings): # Database (support both MYSQL_ prefix and DB_ prefix) MYSQL_HOST: Optional[str] = None MYSQL_PORT: Optional[int] = None MYSQL_DATABASE: Optional[str] = None MYSQL_USER: Optional[str] = None MYSQL_PASSWORD: Optional[str] = None DB_HOST: Optional[str] = None DB_PORT: Optional[int] = None DB_NAME: Optional[str] = None DB_USER: Optional[str] = None DB_PASSWORD: Optional[str] = None # API Auth & Configuration API_PORT: int = 8000 API_HOST: str = "0.0.0.0" API_SECRET_KEY: str = "your-random-secret" JWT_SECRET_KEY: str = "sikomo-daily-scraper-jwt-secret-key-xyz-789" JWT_ALGORITHM: str = "HS256" # Default Schedules DEFAULT_SCHEDULE: str = "0 8 * * *" DEFAULT_SCHEDULES: str = "0 8 * * *" # CORS FRONTEND_URL: str = "http://localhost:3000" @property def DATABASE_URL(self) -> str: # Fallback mappings host = self.MYSQL_HOST or self.DB_HOST or os.getenv("MYSQL_HOST") or os.getenv("DB_HOST") or "localhost" port = self.MYSQL_PORT or self.DB_PORT or os.getenv("MYSQL_PORT") or os.getenv("DB_PORT") or 3306 db = self.MYSQL_DATABASE or self.DB_NAME or os.getenv("MYSQL_DATABASE") or os.getenv("DB_NAME") or "sikomo_db" user = self.MYSQL_USER or self.DB_USER or os.getenv("MYSQL_USER") or os.getenv("DB_USER") or "root" password = self.MYSQL_PASSWORD or self.DB_PASSWORD or os.getenv("MYSQL_PASSWORD") or os.getenv("DB_PASSWORD") or "" return f"mysql+pymysql://{user}:{password}@{host}:{port}/{db}" class Config: env_file = ".env" extra = "ignore" @lru_cache() def get_settings() -> Settings: return Settings() class RuntimeConfig: """Runtime ML Configuration editable via dashboard without server restart.""" def __init__(self): self.optuna_trials: int = int(os.getenv("OPTUNA_TRIALS", "30")) self.model_dir: str = os.getenv("MODEL_DIR", "models") self.forecast_days: int = int(os.getenv("FORECAST_DAYS", "7")) self.default_schedules: str = os.getenv("DEFAULT_FORECAST_SCHEDULES", "0 1 * * *") def to_dict(self) -> dict: return { "optuna_trials": self.optuna_trials, "model_dir": self.model_dir, "forecast_days": self.forecast_days, "default_schedules": self.default_schedules } def update(self, key: str, value: str): allowed = { "optuna_trials": lambda v: setattr(self, "optuna_trials", int(v)), "model_dir": lambda v: setattr(self, "model_dir", v), "forecast_days": lambda v: setattr(self, "forecast_days", int(v)), "default_schedules": lambda v: setattr(self, "default_schedules", v), } if key not in allowed: raise ValueError(f"Key '{key}' tidak diizinkan untuk diubah.") allowed[key](value) runtime_config = RuntimeConfig()