from functools import lru_cache from typing import Literal from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from pydantic import SecretStr, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8', extra='ignore', case_sensitive=False) host: str = '0.0.0.0' port: int = 8080 codex_enabled: bool = True codex_command: str = 'codex' codex_timeout: float = 8 devin_enabled: bool = True devin_api_key: SecretStr = SecretStr('') devin_api_url: str = 'https://api.devin.ai' devin_mode: Literal['enterprise', 'organization_sessions'] = 'enterprise' devin_org_id: str = '' devin_user_id: str = '' devin_session_window_days: int = 30 devin_timeout: float = 8 provider_cache_ttl: float = 8 refresh_interval: int = 10 timezone: str = 'Europe/Vienna' demo_mode: bool = False @field_validator('codex_timeout', 'devin_timeout', 'provider_cache_ttl') @classmethod def positive(cls, value: float) -> float: if value <= 0: raise ValueError('must be positive') return value @field_validator('refresh_interval') @classmethod def positive_interval(cls, value: int) -> int: if value < 1: raise ValueError('must be at least 1') return value @field_validator('devin_session_window_days') @classmethod def valid_session_window(cls, value: int) -> int: if not 1 <= value <= 365: raise ValueError('must be between 1 and 365') return value @field_validator('timezone') @classmethod def valid_timezone(cls, value: str) -> str: try: ZoneInfo(value) except ZoneInfoNotFoundError as exc: raise ValueError('invalid timezone') from exc return value @lru_cache def get_settings() -> Settings: return Settings()