from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore") tg_api_id: int = Field(..., alias="TG_API_ID") tg_api_hash: str = Field(..., alias="TG_API_HASH") tg_phone: str = Field(..., alias="TG_PHONE") tg_session_path: str = Field("/data/session/parser.session", alias="TG_SESSION_PATH") # Preferred for prod / k8s: opaque base64-ish string from `python -m parser_bot.auth`. # If set, takes priority over file-based session. tg_session_string: str | None = Field(None, alias="TG_SESSION_STRING") postgres_user: str = Field("parser", alias="POSTGRES_USER") postgres_password: str = Field("parser", alias="POSTGRES_PASSWORD") postgres_db: str = Field("parser", alias="POSTGRES_DB") postgres_host: str = Field("db", alias="POSTGRES_HOST") postgres_port: int = Field(5432, alias="POSTGRES_PORT") poll_interval_seconds: int = Field(60, alias="POLL_INTERVAL_SECONDS") poll_history_limit: int = Field(50, alias="POLL_HISTORY_LIMIT") api_host: str = Field("0.0.0.0", alias="API_HOST") api_port: int = Field(8000, alias="API_PORT") public_base_path: str = Field("", alias="PUBLIC_BASE_PATH") media_dir: str = Field("/data/media", alias="MEDIA_DIR") media_max_bytes: int = Field(20 * 1024 * 1024, alias="MEDIA_MAX_BYTES") # Local LLM via Ollama for lead classification & extraction llm_enabled: bool = Field(True, alias="LLM_ENABLED") llm_base_url: str = Field("http://ollama:11434", alias="LLM_BASE_URL") llm_model: str = Field("qwen2.5:7b-instruct-q4_K_M", alias="LLM_MODEL") llm_timeout_seconds: int = Field(120, alias="LLM_TIMEOUT_SECONDS") llm_min_text_length: int = Field(20, alias="LLM_MIN_TEXT_LENGTH") llm_classify_interval_seconds: int = Field(20, alias="LLM_CLASSIFY_INTERVAL_SECONDS") llm_classify_batch_size: int = Field(5, alias="LLM_CLASSIFY_BATCH_SIZE") # Admin allowlist for /auth.html, /docs, /openapi.json, /redoc and the # /auth/* API endpoints. Comma-separated IPv4/IPv6. Empty (default) means # no restriction — convenient for local dev. Set explicitly in prod. admin_allowed_ips: str = Field("", alias="ADMIN_ALLOWED_IPS") # Optional second factor for admin-only UI/API operations. Empty keeps the # previous IP-only behavior for local/dev deployments. admin_password: str = Field("", alias="ADMIN_PASSWORD") # When true, honor X-Forwarded-For / X-Real-IP set by a reverse proxy # in front of uvicorn (Docker port-forward, nginx, traefik, etc). trust_proxy_headers: bool = Field(True, alias="TRUST_PROXY_HEADERS") @property def admin_ip_set(self) -> set[str]: return {s.strip() for s in self.admin_allowed_ips.split(",") if s.strip()} @property def database_url(self) -> str: return ( f"postgresql+asyncpg://{self.postgres_user}:{self.postgres_password}" f"@{self.postgres_host}:{self.postgres_port}/{self.postgres_db}" ) settings = Settings()