diff --git a/adapters/baseline_diagnosis_store.py b/adapters/baseline_diagnosis_store.py index 75bc44a..a4cb9ca 100644 --- a/adapters/baseline_diagnosis_store.py +++ b/adapters/baseline_diagnosis_store.py @@ -10,9 +10,13 @@ from __future__ import annotations import sqlite3 from pathlib import Path +from typing import TYPE_CHECKING from core.evolution.types import DiagnosisSignalRow +if TYPE_CHECKING: + from types import TracebackType + # 表列顺序即 DiagnosisSignalRow 字段顺序(question_id..session_id), # upsert 写入与 load 还原共用,避免手写列名两处漂移。 _COLUMNS: tuple[str, ...] = ( @@ -152,3 +156,18 @@ class SqliteDiagnosisSignalStore: ) for r in cursor.fetchall() ] + + def close(self) -> None: + """关闭底层 SQLite 连接,释放文件描述符与锁。""" + self._conn.close() + + def __enter__(self) -> SqliteDiagnosisSignalStore: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() diff --git a/core/evolution/protocols.py b/core/evolution/protocols.py index ecadb06..df4039c 100644 --- a/core/evolution/protocols.py +++ b/core/evolution/protocols.py @@ -1,8 +1,10 @@ -"""core/evolution/ 子包的只读 Protocol 定义。 +"""core/evolution/ 子包的持久化 Protocol 定义。 -三个 Protocol 均为只读——core/ 返回结果 dataclass,写入由 app/ 持久化。 -SkillStore / PromptStore 为同步(文件读取量小且快),RunLog 为异步 -(隔离 SQLite 查询,core/ 不写 SQL)。 +SkillStore / PromptStore / RunLog 为只读——core/ 返回结果 dataclass, +读取由 app/ 落盘的资源。SkillStore / PromptStore 同步(文件读取量小且快), +RunLog 异步(隔离 SQLite 查询,core/ 不写 SQL)。 +DiagnosisSignalStore 兼具读写:逐题 upsert 诊断信号并支持断点续跑查询, +同样隔离 SQLite 实现,app/core 不写裸 SQL。 """ from __future__ import annotations diff --git a/tests/unit/test_baseline_diagnosis_store.py b/tests/unit/test_baseline_diagnosis_store.py index 0702704..00a654d 100644 --- a/tests/unit/test_baseline_diagnosis_store.py +++ b/tests/unit/test_baseline_diagnosis_store.py @@ -4,7 +4,12 @@ load 往返还原,以及 INFRA 行的可空字段处理。 """ -from adapters.baseline_diagnosis_store import DiagnosisSignalRow, SqliteDiagnosisSignalStore +import sqlite3 + +import pytest + +from adapters.baseline_diagnosis_store import SqliteDiagnosisSignalStore +from core.evolution.types import DiagnosisSignalRow def _store(tmp_path): @@ -78,3 +83,16 @@ def test_null_fields_for_infra_row(tmp_path): ) rows = s.load("r", "fp") assert rows[0].error_type is None and rows[0].evolution_target is None and rows[0].infra is True + + +def test_context_manager_closes_connection(tmp_path): + # with 块退出后连接关闭,再操作应报 ProgrammingError + with SqliteDiagnosisSignalStore(str(tmp_path / "h.db")) as s: + s.upsert( + DiagnosisSignalRow( + "q4", "v4", "r", "fp", "Counting", None, None, "T0", None, False, True, None + ) + ) + assert s.done_question_ids("r", "fp") == {"q4"} + with pytest.raises(sqlite3.ProgrammingError): + s.done_question_ids("r", "fp")