All 13 environment variables from the spec defined with proper defaults. SQLite fallback when DATABASE_URL is unset, in-process queue flag when REDIS_URL is unset, JWT_SECRET auto-generation, empty API_KEY normalization. 13 unit tests covering all configuration paths.
76 lines
2 KiB
Python
76 lines
2 KiB
Python
"""PromptLooper configuration — Pydantic Settings loaded from environment."""
|
|
|
|
import secrets
|
|
from pathlib import Path
|
|
|
|
from pydantic import field_validator
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
extra="ignore",
|
|
)
|
|
|
|
# --- Database ---
|
|
database_url: str | None = None
|
|
|
|
# --- Redis ---
|
|
redis_url: str | None = None
|
|
|
|
# --- Server ---
|
|
host: str = "0.0.0.0"
|
|
port: int = 8400
|
|
|
|
# --- Auth ---
|
|
jwt_secret: str = ""
|
|
api_key: str | None = None
|
|
|
|
# --- Default LLM Endpoint ---
|
|
default_endpoint_url: str | None = None
|
|
default_endpoint_key: str | None = None
|
|
|
|
# --- Limits ---
|
|
max_concurrent_runs: int = 4
|
|
max_tokens_per_sweep: int = 0 # 0 = unlimited
|
|
|
|
# --- Storage ---
|
|
data_dir: str = "/data"
|
|
|
|
# --- MCP ---
|
|
mcp_enabled: bool = True
|
|
mcp_port: int = 8401
|
|
|
|
def model_post_init(self, __context: object) -> None:
|
|
# Auto-generate JWT secret if not provided
|
|
if not self.jwt_secret:
|
|
self.jwt_secret = secrets.token_urlsafe(32)
|
|
|
|
@property
|
|
def effective_database_url(self) -> str:
|
|
"""Return DATABASE_URL or construct a SQLite URL from DATA_DIR."""
|
|
if self.database_url:
|
|
return self.database_url
|
|
db_path = Path(self.data_dir) / "promptlooper.db"
|
|
return f"sqlite:///{db_path}"
|
|
|
|
@property
|
|
def is_sqlite(self) -> bool:
|
|
return self.effective_database_url.startswith("sqlite")
|
|
|
|
@property
|
|
def use_in_process_queue(self) -> bool:
|
|
"""When Redis is unavailable, use in-process task execution."""
|
|
return self.redis_url is None
|
|
|
|
@field_validator("api_key", mode="before")
|
|
@classmethod
|
|
def empty_string_to_none(cls, v: str | None) -> str | None:
|
|
if v is not None and v.strip() == "":
|
|
return None
|
|
return v
|
|
|
|
|
|
settings = Settings()
|