from typing import Any, Dict, Optional from pydantic import BaseModel, ConfigDict, Field, model_validator class PersistConfig(BaseModel): model_config = ConfigDict(extra="forbid", validate_assignment=True) backend: str = "sqlite" db_path: Optional[str] = None url: Optional[str] = None backend_options: Dict[str, Any] = Field(default_factory=dict) enable: bool = True wipe_on_start: bool = False @model_validator(mode="before") @classmethod def _validate_target(cls, data: Any) -> Any: if not isinstance(data, dict): return data normalized = dict(data) normalized["backend"] = (str(normalized.get("backend") or "sqlite").strip().lower() or "sqlite") normalized["db_path"] = cls._normalize_optional_text(normalized.get("db_path")) normalized["url"] = cls._normalize_optional_text(normalized.get("url")) if not normalized["db_path"] and not normalized["url"]: raise ValueError("persist requires either db_path or url") if normalized["backend"] != "sqlite" and not normalized["url"]: raise ValueError(f"persist.url is required for backend={normalized['backend']}") return normalized @staticmethod def _normalize_optional_text(value: Optional[str]) -> Optional[str]: if value is None: return None normalized = str(value).strip() return normalized or None def resolved_url(self) -> str: if self.url: return self.url if self.backend == "sqlite" and self.db_path: return f"sqlite+aiosqlite:///{self.db_path}" raise ValueError(f"persist.url is required for backend={self.backend}") def resolved_db_path(self) -> Optional[str]: if self.db_path: return self.db_path if not self.url: return None from sqlalchemy.engine import make_url parsed = make_url(self.url) if parsed.get_backend_name() != "sqlite": return None database = parsed.database if not database: return None return str(database) def wipe_file_path(self) -> Optional[str]: db_path = self.resolved_db_path() if not db_path or db_path == ":memory:": return None return db_path def display_target(self) -> str: return self.url or self.db_path or ""