"""Engine, session plumbing, and shared column helpers. DATA_DIR is resolved relative to this file; the db package lives one level deeper than the old single-file module, hence the extra ``parent``. """ import json import uuid from datetime import datetime, timezone from pathlib import Path from typing import Any from sqlalchemy.pool import StaticPool from sqlmodel import Session, SQLModel, create_engine DATA_DIR = Path(__file__).resolve().parent.parent.parent.parent.parent / "data" DATA_DIR.mkdir(parents=True, exist_ok=True) DATABASE_URL = f"sqlite:///{DATA_DIR / 'agenteval.db'}" FILES_DIR = DATA_DIR / "files" FILES_DIR.mkdir(parents=True, exist_ok=True) engine = create_engine( DATABASE_URL, echo=False, connect_args={"check_same_thread": False}, poolclass=StaticPool, ) def utc_now() -> datetime: return datetime.now(timezone.utc) def as_utc(dt: datetime) -> datetime: """Attach UTC tzinfo to a naive datetime. SQLite round-trips drop tzinfo; stored times are always UTC, so a naive value read back is restored as UTC before any comparison with utc_now(). """ return dt if dt.tzinfo is not None else dt.replace(tzinfo=timezone.utc) def iso_utc(dt: datetime | None) -> str | None: """Serialize a datetime to ISO 8601 with UTC timezone suffix. Guarantees the output always ends with 'Z' or '+00:00' so JavaScript's Date.parse() interprets it correctly as UTC (no 8-hour local-time offset). """ if dt is None: return None if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) return dt.isoformat(timespec="seconds").replace("+00:00", "Z") def new_uuid() -> str: return str(uuid.uuid4()) def _json_dumps(value: Any) -> str: """Serialize a JSON column value. ensure_ascii=False keeps CJK readable in the stored text — the single serialization口径 for all JSON columns.""" return json.dumps(value, ensure_ascii=False) def _json_loads(raw: str) -> Any: return json.loads(raw) def init_db() -> None: SQLModel.metadata.create_all(engine) def get_session() -> Session: return Session(engine) def get_session_context(): """Context manager that creates and properly closes a database session.""" session = Session(engine) try: yield session finally: session.close()