""" Application configuration via Pydantic Settings. All settings loaded from environment variables or .env file. Project metadata (name, version, description) sourced from pyproject.toml. """ import tomllib from dataclasses import dataclass from functools import lru_cache from pathlib import Path from pydantic_settings import BaseSettings, SettingsConfigDict @dataclass(frozen=True) class ProjectMeta: """Project metadata from pyproject.toml (single source of truth).""" name: str version: str description: str def _load_project_meta() -> ProjectMeta: """Load project metadata from pyproject.toml.""" pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml" try: with open(pyproject_path, "rb") as f: data = tomllib.load(f) project = data.get("project", {}) return ProjectMeta( name=str(project.get("name", "webber")).title(), version=str(project.get("version", "0.0.0")), description=str(project.get("description", "")), ) except FileNotFoundError: return ProjectMeta(name="Webber", version="0.0.0", description="") PROJECT = _load_project_meta() __version__ = PROJECT.version class Settings(BaseSettings): """Application settings loaded from environment.""" # Application (from pyproject.toml) app_name: str = PROJECT.name app_version: str = PROJECT.version app_description: str = PROJECT.description debug: bool = False # Server host: str = "0.0.0.0" port: int = 8086 # Logging log_level: str = "INFO" # CORS cors_origins: list[str] = ["http://localhost:3000", "http://localhost:8080"] cors_credentials: bool = True cors_methods: list[str] = ["*"] cors_headers: list[str] = ["*"] # LLM - Ollama (always hot in VRAM on tower-of-joy) ollama_url: str = "http://192.168.86.149:11434" ollama_agent_model: str = "mistral-nemo-large:latest" ollama_embed_model: str = "nomic-embed-text:latest" # Auth - Tatlock integration tatlock_api_url: str | None = "http://192.168.86.149:8000" internal_api_key: str | None = None # Tool execution tool_timeout_seconds: int = 120 sandbox_enabled: bool = True allowed_paths: list[str] | None = None # Sessions session_ttl_hours: int = 24 max_context_tokens: int = 128000 model_config = SettingsConfigDict( env_file=".env", case_sensitive=False, extra="ignore", env_parse_none_str="", # Treat empty string as None ) @property def effective_allowed_paths(self) -> list[str]: """Return allowed_paths or empty list if None.""" return self.allowed_paths or [] @lru_cache def get_settings() -> Settings: """Cached settings singleton.""" return Settings()