SQLite persistence engine — Skill quality tracking and evolution ledger. Architecture: Write path: async method → asyncio.to_thread → _xxx_sync → self._mu lock → self._conn Read path: sync method → self._reader() → independent short connection (WAL parallel read) Lifecycle:
| 167 | |
| 168 | |
| 169 | class SkillStore: |
| 170 | """SQLite persistence engine — Skill quality tracking and evolution ledger. |
| 171 | |
| 172 | Architecture: |
| 173 | Write path: async method → asyncio.to_thread → _xxx_sync → self._mu lock → self._conn |
| 174 | Read path: sync method → self._reader() → independent short connection (WAL parallel read) |
| 175 | |
| 176 | Lifecycle: ``__init__()`` → use → ``close()`` |
| 177 | Also supports async context manager: |
| 178 | async with SkillStore() as store: |
| 179 | await store.save_record(record) |
| 180 | rec = store.load_record(skill_id) |
| 181 | """ |
| 182 | |
| 183 | def __init__(self, db_path: Optional[Path] = None) -> None: |
| 184 | if db_path is None: |
| 185 | db_dir = PROJECT_ROOT / ".openspace" |
| 186 | db_dir.mkdir(parents=True, exist_ok=True) |
| 187 | db_path = db_dir / "openspace.db" |
| 188 | |
| 189 | self._db_path = Path(db_path) |
| 190 | self._mu = threading.Lock() |
| 191 | self._closed = False |
| 192 | |
| 193 | # Crash recovery: clean up stale WAL/SHM from unclean shutdown |
| 194 | self._cleanup_wal_on_startup() |
| 195 | |
| 196 | # Persistent write connection |
| 197 | self._conn = self._make_connection(read_only=False) |
| 198 | self._init_db() |
| 199 | logger.debug(f"SkillStore ready at {self._db_path}") |
| 200 | |
| 201 | def _make_connection(self, *, read_only: bool) -> sqlite3.Connection: |
| 202 | """Create a tuned SQLite connection. |
| 203 | |
| 204 | Write connection: ``check_same_thread=False`` for cross-thread |
| 205 | usage via ``asyncio.to_thread()``. |
| 206 | |
| 207 | Read connection: ``query_only=ON`` pragma for safety. |
| 208 | """ |
| 209 | conn = sqlite3.connect( |
| 210 | str(self._db_path), |
| 211 | timeout=30.0, |
| 212 | check_same_thread=False, |
| 213 | ) |
| 214 | conn.execute("PRAGMA journal_mode=WAL") |
| 215 | conn.execute("PRAGMA busy_timeout=30000") |
| 216 | conn.execute("PRAGMA synchronous=NORMAL") |
| 217 | conn.execute("PRAGMA cache_size=-16000") # 16 MB |
| 218 | conn.execute("PRAGMA temp_store=MEMORY") |
| 219 | conn.execute("PRAGMA foreign_keys=ON") |
| 220 | if read_only: |
| 221 | conn.execute("PRAGMA query_only=ON") |
| 222 | conn.row_factory = sqlite3.Row |
| 223 | return conn |
| 224 | |
| 225 | @contextmanager |
| 226 | def _reader(self) -> Generator[sqlite3.Connection, None, None]: |
no outgoing calls
no test coverage detected