"""Application configuration via environment variables / .env file. All settings are read from the environment (or a .env file in the project root). Import :func:`get_settings` wherever you need configuration; the returned object is cached after the first call. """ from __future__ import annotations from functools import lru_cache from pydantic import AliasChoices, Field from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """Centralised, env-driven configuration for the codex application.""" model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore", ) # ------------------------------------------------------------------ # Database # ------------------------------------------------------------------ database_url: str = Field( default="postgresql://researcher:change_me@localhost:5432/papers", description=( "libpq-compatible connection string consumed by psycopg. " "Example: postgresql://user:pass@host:5432/dbname" ), ) # ------------------------------------------------------------------ # External services # ------------------------------------------------------------------ grobid_url: str = Field( default="http://localhost:8070", description="Base URL of the GROBID HTTP API (containerised).", ) nougat_url: str = Field( default="http://localhost:8080", validation_alias=AliasChoices("NOUGAT_URL", "nougat_url"), description="Base URL of the Nougat OCR HTTP API (containerised).", ) ollama_base_url: str = Field( default="http://localhost:11434", description="Base URL of the local Ollama endpoint (optional Q&A layer).", ) # ------------------------------------------------------------------ # Embeddings # ------------------------------------------------------------------ embedding_model: str = Field( default="BAAI/bge-m3", description=( "sentence-transformers model identifier. " "Must match EMBEDDING_DIM. Default: BAAI/bge-m3 (1024 dims)." ), ) embedding_dim: int = Field( default=1024, gt=0, description=( "Dimension of the dense embedding vectors. " "Must match the output dimension of EMBEDDING_MODEL." ), ) # ------------------------------------------------------------------ # API etiquette # ------------------------------------------------------------------ openalex_mailto: str = Field( default="", description=( "E-mail address for the OpenAlex Polite Pool (faster rate limits). " "Required by OpenAlex ToS for automated access." ), ) @lru_cache(maxsize=1) def get_settings() -> Settings: """Return the cached application settings singleton.""" return Settings()