""" Application configuration using Pydantic Settings. Following best practices: modular settings, environment-based config. """ from functools import lru_cache from pathlib import Path from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict # Read version from pyproject.toml try: import tomllib _pyproject_path = Path(__file__).parent.parent / "pyproject.toml" with open(_pyproject_path, "rb") as f: _pyproject = tomllib.load(f) __version__ = _pyproject["project"]["version"] except Exception: __version__ = "0.0.0" # Fallback if pyproject.toml not found class Settings(BaseSettings): """Application settings loaded from environment variables.""" model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore", ) # API Configuration library_api_key: str = Field(..., description="API key for authentication") # Neo4j Configuration neo4j_uri: str = Field(default="bolt://neo4j:7687", description="Neo4j Bolt URI") neo4j_user: str = Field(default="neo4j", description="Neo4j username") neo4j_password: str = Field(..., description="Neo4j password") # Qdrant Configuration qdrant_host: str = Field(default="qdrant", description="Qdrant host") qdrant_port: int = Field(default=6333, description="Qdrant port") # Wiki.js Configuration wikijs_url: str = Field(default="http://wiki:3000", description="Wiki.js URL") wiki_graphql_api: str = Field(default="", description="Wiki.js GraphQL API token (optional - API may be open)") # Legacy auth fields - kept for backwards compatibility but deprecated wikijs_username: str = Field(default="", description="Wiki.js username (deprecated, use wiki_graphql_api)") wikijs_password: str = Field(default="", description="Wiki.js password (deprecated, use wiki_graphql_api)") # Wiki.js Database Configuration (for change listener) wikijs_db_host: str = Field(default="postgres-shared", description="Wiki.js PostgreSQL host") wikijs_db_port: int = Field(default=5432, description="Wiki.js PostgreSQL port") wikijs_db_name: str = Field(default="library", description="Wiki.js database name") wikijs_db_user: str = Field(default="library_desk_listener", description="Wiki.js database user (read-only)") wikijs_db_password: str = Field(..., description="Wiki.js database password") wikijs_change_listener_debounce_seconds: int = Field( default=5, ge=1, le=60, description="Debounce duration to prevent processing duplicate notifications" ) # SearXNG Configuration searxng_url: str = Field(default="http://searxng:8080", description="SearXNG URL") # Ollama Configuration ollama_url: str = Field(default="http://ollama:11434", description="Ollama URL") ollama_model: str = Field(default="mistral-nemo-large:latest", description="Ollama LLM model") ollama_embedding_model: str = Field(default="nomic-embed-text", description="Ollama embedding model") # HybridRAG Configuration reranker_enabled: bool = Field(default=True, description="Enable LLM re-ranking") hybrid_rag_vector_limit: int = Field(default=10, ge=1, le=50, description="Vector search limit") hybrid_rag_graph_limit: int = Field(default=10, ge=1, le=50, description="Graph search limit") hybrid_rag_web_limit: int = Field(default=5, ge=1, le=20, description="Web search limit") vector_similarity_threshold: float = Field(default=0.7, ge=0.0, le=1.0, description="Minimum similarity score for vector results") # Entity Linking Fuzzy Matching Configuration entity_linking_min_confidence: float = Field(default=0.70, ge=0.0, le=1.0, description="Minimum confidence for entity-document matching") entity_linking_min_entity_length: int = Field(default=5, ge=1, le=50, description="Minimum entity name length for matching") entity_linking_min_containment_ratio: float = Field(default=0.30, ge=0.0, le=1.0, description="Minimum containment ratio for substring matching") entity_linking_min_token_overlap: float = Field(default=0.60, ge=0.0, le=1.0, description="Minimum token overlap ratio for matching") # Redis Configuration (for job tracking - separate DB from wiki) redis_host: str = Field(default="redis-shared", description="Redis host") redis_port: int = Field(default=6379, description="Redis port") redis_db: int = Field(default=4, description="Redis database number (4 for library-desk jobs)") # Application app_name: str = Field(default="Library Desk", description="Application name") app_version: str = Field(default=__version__, description="Application version") debug: bool = Field(default=False, description="Debug mode") # RAG Search Configuration search_cache_ttl: int = Field(default=300, ge=0, le=3600, description="Search cache TTL in seconds") search_timeout: int = Field(default=10, ge=1, le=60, description="SearXNG timeout in seconds") search_default_limit: int = Field(default=10, ge=1, le=20, description="Default number of search results") # Content Extraction Configuration content_extraction_timeout: int = Field(default=5, ge=1, le=30, description="Trafilatura per-URL timeout in seconds") content_max_length: int = Field(default=2000, ge=500, le=10000, description="Max extracted content length per result") # Paperless-ngx Configuration paperless_url: str = Field(default="http://paperless:8000", description="Paperless-ngx URL") paperless_token: str = Field(default="", description="Paperless-ngx API token") paperless_timeout: int = Field(default=30, ge=5, le=120, description="Paperless API timeout in seconds") # Document Store Configuration document_store_enabled: bool = Field(default=True, description="Enable document store feature") document_catalog_path_prefix: str = Field(default="docs", description="Wiki path prefix for catalog pages") # Volatile Cache Configuration volatile_cache_enabled: bool = Field(default=True, description="Enable volatile cache feature") volatile_default_ttl: int = Field(default=3600, ge=60, le=86400, description="Default TTL in seconds") volatile_weather_ttl: int = Field(default=1800, ge=60, le=7200, description="Weather data TTL in seconds") volatile_news_ttl: int = Field(default=7200, ge=300, le=86400, description="News data TTL in seconds") volatile_financial_ttl: int = Field(default=300, ge=60, le=3600, description="Financial data TTL in seconds") # Maintenance Configuration maintenance_orphan_cleanup_enabled: bool = Field(default=True, description="Enable automatic orphan cleanup") maintenance_cleanup_batch_size: int = Field(default=100, ge=10, le=1000, description="Cleanup batch size") # Central Settings Database (Tatlock-wide) system_settings_host: str = Field(default="postgres-shared", description="System settings PostgreSQL host") system_settings_port: int = Field(default=5432, description="System settings PostgreSQL port") system_settings_db: str = Field(default="system_settings", description="System settings database name") system_settings_user: str = Field(default="settings", description="System settings database user") system_settings_password: str = Field(default="", description="System settings database password") # Scheduler Service scheduler_url: str = Field(default="http://scheduler:8090", description="Scheduler service URL") @property def qdrant_url(self) -> str: """Computed Qdrant URL.""" return f"http://{self.qdrant_host}:{self.qdrant_port}" @property def redis_url(self) -> str: """Computed Redis URL.""" return f"redis://{self.redis_host}:{self.redis_port}/{self.redis_db}" @property def system_settings_dsn(self) -> str: """Computed System Settings PostgreSQL DSN.""" if not self.system_settings_password: return "" return ( f"postgresql://{self.system_settings_user}:{self.system_settings_password}" f"@{self.system_settings_host}:{self.system_settings_port}/{self.system_settings_db}" ) @lru_cache def get_settings() -> Settings: """ Get cached settings instance. Uses lru_cache to ensure single instance across app. """ return Settings()