将persist和后端分离,方便替换为其他数据库

This commit is contained in:
strepsiades
2026-03-24 10:15:16 +08:00
parent 7d92b3f1fe
commit 03a22b0c1c
19 changed files with 665 additions and 471 deletions
@@ -11,6 +11,7 @@ import pytest_asyncio
from sync_state_machine.common.binding import BindingManager
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.persistence import PersistenceBackend
from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend
from sync_state_machine.common.types import BindingStatus, SyncAction, SyncStatus
from sync_state_machine.pipeline.full_sync_pipeline import FullSyncPipeline
from sync_state_machine.sync_system.strategy_ops.compare_ops import (
@@ -39,7 +40,7 @@ async def pipeline_bundle():
register_test_domain()
db = Path(tempfile.mkdtemp(prefix="sync_state_machine_pipeline_") ) / "state.db"
persistence = PersistenceBackend(str(db))
persistence = SQLitePersistenceBackend(str(db))
await persistence.initialize()
local_collection = DataCollection("local", persistence=persistence, auto_persist=False)
@@ -304,7 +305,7 @@ async def test_load_overrides_bind_data_id_from_bindings(pipeline_bundle):
existing_persistence = local_collection.persistence
assert existing_persistence is not None
persistence = PersistenceBackend(existing_persistence.db_path)
persistence = SQLitePersistenceBackend(existing_persistence.db_path)
await persistence.initialize()
await persistence.conn.execute(
@@ -443,7 +444,7 @@ async def test_pipeline_create_then_update_completes_in_single_run_with_reload()
register_test_domain()
db = Path(tempfile.mkdtemp(prefix="sync_state_machine_pipeline_reload_") ) / "state.db"
persistence = PersistenceBackend(str(db))
persistence = SQLitePersistenceBackend(str(db))
await persistence.initialize()
local_collection = DataCollection("local", persistence=persistence, auto_persist=False)
@@ -8,6 +8,7 @@ import pytest
from sync_state_machine.common.binding import BindingManager
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.persistence import PersistenceBackend
from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend
from sync_state_machine.pipeline.full_sync_pipeline import FullSyncPipeline
from sync_state_machine.sync_system.config import OrphanAction, UpdateDirection
from tests.fixtures.mock_domain import (
@@ -36,7 +37,7 @@ async def test_full_pipeline_multinode_mock_persisted_db_snapshot() -> None:
if db_path.exists():
db_path.unlink()
persistence = PersistenceBackend(str(db_path))
persistence = SQLitePersistenceBackend(str(db_path))
await persistence.initialize()
local_collection = DataCollection("local", persistence=persistence, auto_persist=False)
@@ -7,6 +7,7 @@ import pytest
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.persistence import PersistenceBackend
from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend
from sync_state_machine.common.types import BindingStatus, SyncAction, SyncStatus
from sync_state_machine.engine.dispatcher import StateMachineRuntime
from sync_state_machine.engine.model import StateMachineConfig
@@ -21,7 +22,7 @@ from tests.mocks.mock_datasource import (
@pytest.fixture
def datasource_bundle():
db = Path(tempfile.mkdtemp(prefix="sync_state_machine_mock_ds_")) / "state.db"
persistence = PersistenceBackend(str(db))
persistence = SQLitePersistenceBackend(str(db))
cfg_path = Path(__file__).resolve().parents[2] / "sync_state_machine" / "config" / "node_state_machine.yaml"
runtime = StateMachineRuntime(StateMachineConfig.load(cfg_path))
collection = DataCollection("mock", persistence=None, auto_persist=False)
@@ -49,7 +50,7 @@ async def test_create_success_to_s11(datasource_bundle):
node = _new_node("n1", SyncAction.CREATE)
await collection.add(node)
datasource.register_handler(ScriptedMockHandler(HandlerScript(create="success")))
datasource.add_handler(ScriptedMockHandler(HandlerScript(create="success")))
datasource.set_collection(collection)
await datasource.sync_all(data_type="mock")
@@ -62,7 +63,7 @@ async def test_create_skipped_is_rejected(datasource_bundle):
node = _new_node("n2", SyncAction.CREATE)
await collection.add(node)
datasource.register_handler(ScriptedMockHandler(HandlerScript(create="skipped")))
datasource.add_handler(ScriptedMockHandler(HandlerScript(create="skipped")))
datasource.set_collection(collection)
await datasource.sync_all(data_type="mock")
@@ -76,7 +77,7 @@ async def test_update_skip_downgrade_to_s14(datasource_bundle):
node = _new_node("n3", SyncAction.UPDATE)
await collection.add(node)
datasource.register_handler(
datasource.add_handler(
ScriptedMockHandler(HandlerScript(update="skipped"))
)
datasource.set_collection(collection)
@@ -94,7 +95,7 @@ async def test_update_success_writes_back_origin_data(datasource_bundle):
node.set_data({"id": "n3b", "name": "after"})
await collection.add(node)
datasource.register_handler(ScriptedMockHandler(HandlerScript(update="success")))
datasource.add_handler(ScriptedMockHandler(HandlerScript(update="success")))
datasource.set_collection(collection)
await datasource.sync_all(data_type="mock")
@@ -109,7 +110,7 @@ async def test_in_progress_poll_success_to_s11(datasource_bundle):
node = _new_node("n4", SyncAction.CREATE)
await collection.add(node)
datasource.register_handler(
datasource.add_handler(
ScriptedMockHandler(HandlerScript(create="in_progress", poll_sequence=["success"]))
)
datasource.set_collection(collection)
@@ -124,7 +125,7 @@ async def test_in_progress_poll_timeout_to_s12(datasource_bundle):
node = _new_node("n5", SyncAction.CREATE)
await collection.add(node)
datasource.register_handler(
datasource.add_handler(
ScriptedMockHandler(HandlerScript(create="in_progress", poll_sequence=["in_progress", "in_progress"]))
)
datasource.set_collection(collection)
@@ -10,6 +10,7 @@ import pytest
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.binding import BindingManager
from sync_state_machine.common.persistence import PersistenceBackend
from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend
from sync_state_machine.common.types import BindingStatus, SyncAction, SyncStatus
from sync_state_machine.engine import StateMachineConfig, StateMachineRuntime
from sync_state_machine.engine.events import (
@@ -464,7 +465,7 @@ async def test_sync_log_cleared_on_initial_load(tmp_path: Path) -> None:
REPORT_DIR.mkdir(parents=True, exist_ok=True)
db = tmp_path / "sync_log_clear_state.db"
persistence = PersistenceBackend(str(db))
persistence = SQLitePersistenceBackend(str(db))
await persistence.initialize()
collection = DataCollection("project-log-clear", persistence=persistence, auto_persist=False)
@@ -531,7 +532,7 @@ async def test_load_preserves_state_then_e01_normalizes_to_s00(tmp_path: Path) -
REPORT_DIR.mkdir(parents=True, exist_ok=True)
db = tmp_path / "load_preserve_then_e01_normalize.db"
persistence = PersistenceBackend(str(db))
persistence = SQLitePersistenceBackend(str(db))
await persistence.initialize()
collection = DataCollection("project-e01-normalize", persistence=persistence, auto_persist=False)
@@ -583,7 +584,7 @@ async def test_invalid_persisted_enum_is_bootstrap_blocked_and_skipped_in_reset(
REPORT_DIR.mkdir(parents=True, exist_ok=True)
db = tmp_path / "invalid_enum_bootstrap_blocked.db"
persistence = PersistenceBackend(str(db))
persistence = SQLitePersistenceBackend(str(db))
await persistence.initialize()
collection_id = "project-invalid-enum"
@@ -9,6 +9,7 @@ import pytest_asyncio
from sync_state_machine.common.binding import BindingManager
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.persistence import PersistenceBackend
from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend
from sync_state_machine.common.types import BindingStatus, SyncAction, SyncStatus
from sync_state_machine.sync_system.resolve_ids import IDResolver
from sync_state_machine.sync_system.config import OrphanAction, UpdateDirection
@@ -32,7 +33,7 @@ from tests.fixtures.mock_domain import (
async def setup_env():
register_test_domain()
db = Path(tempfile.mkdtemp(prefix="sync_state_machine_biz_") ) / "state.db"
persistence = PersistenceBackend(str(db))
persistence = SQLitePersistenceBackend(str(db))
await persistence.initialize()
local = DataCollection("local", persistence=persistence, auto_persist=False)
@@ -9,7 +9,7 @@ from pydantic import BaseModel
from sync_state_machine.config.multi_project_config import build_multi_project_config_from_file
from sync_state_machine.common.binding import BindingManager
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.persistence import PersistenceBackend
from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend
from sync_state_machine.common.sync_node import SyncNode
from sync_state_machine.pipeline.multi_project_pipeline import (
_project_scope_overrides,
@@ -88,7 +88,7 @@ class _ProjectNode(SyncNode[dict]):
@pytest.mark.asyncio
async def test_binding_manager_prunes_stale_bindings_after_project_scope_cleanup() -> None:
persistence = PersistenceBackend(":memory:")
persistence = SQLitePersistenceBackend(":memory:")
await persistence.initialize()
try:
local_collection = DataCollection("local", persistence=persistence, auto_persist=False)
+2 -1
View File
@@ -7,6 +7,7 @@ import pytest
from sync_state_machine.common.binding import BindingManager
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.persistence import PersistenceBackend
from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend
from sync_state_machine.common.types import BindingStatus, SyncAction, SyncStatus
from sync_state_machine.datasource.datasource import BaseDataSource
from sync_state_machine.engine.state_apply import apply_state_to_node
@@ -38,7 +39,7 @@ def _set_state(pipeline: FullSyncPipeline, node: MockSyncNode, state_id: str, *,
async def _new_pipeline(tmp_path: Path) -> tuple[FullSyncPipeline, DataCollection, DataCollection, BindingManager, PersistenceBackend]:
db_path = tmp_path / "state.db"
persistence = PersistenceBackend(str(db_path))
persistence = SQLitePersistenceBackend(str(db_path))
await persistence.initialize()
local_collection = DataCollection("local", persistence=persistence, auto_persist=False)
@@ -7,6 +7,7 @@ import pytest
from sync_state_machine.common.binding import BindingManager
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.persistence import PersistenceBackend
from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend
from sync_state_machine.datasource.datasource import BaseDataSource
from sync_state_machine.pipeline.full_sync_pipeline import FullSyncPipeline
from sync_state_machine.sync_system.strategy import BaseSyncStrategy
@@ -53,7 +54,7 @@ class _OkStrategy(BaseSyncStrategy):
@pytest.mark.asyncio
async def test_phase_sync_by_node_type_continues_after_strategy_error(tmp_path: Path) -> None:
db_path = tmp_path / "state.db"
persistence = PersistenceBackend(str(db_path))
persistence = SQLitePersistenceBackend(str(db_path))
await persistence.initialize()
local_collection = DataCollection("local", persistence=persistence, auto_persist=False)
@@ -101,7 +102,7 @@ async def test_phase_sync_by_node_type_continues_after_strategy_error(tmp_path:
@pytest.mark.asyncio
async def test_phase_sync_by_node_type_skip_sync_keeps_bind_but_skips_writes(tmp_path: Path) -> None:
db_path = tmp_path / "state.db"
persistence = PersistenceBackend(str(db_path))
persistence = SQLitePersistenceBackend(str(db_path))
await persistence.initialize()
local_collection = DataCollection("local", persistence=persistence, auto_persist=False)
@@ -8,6 +8,7 @@ import pytest
from sync_state_machine.common.binding import BindingManager
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.persistence import PersistenceBackend
from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend
from sync_state_machine.common.types import BindingStatus, SyncAction, SyncStatus
from sync_state_machine.domain.construction.sync_node import ConstructionTaskSyncNode
from sync_state_machine.domain.construction.sync_strategy import ConstructionTaskSyncStrategy
@@ -19,7 +20,7 @@ from sync_state_machine.sync_system.strategy_ops.post_check_ops import evaluate_
@pytest.mark.asyncio
async def test_post_check_uses_same_id_resolver_as_create_update_for_depend_ids(tmp_path: Path) -> None:
db_path = tmp_path / "state.db"
persistence = PersistenceBackend(str(db_path))
persistence = SQLitePersistenceBackend(str(db_path))
await persistence.initialize()
local_collection = DataCollection("local", persistence=persistence, auto_persist=False)
@@ -167,7 +168,7 @@ async def test_post_check_uses_same_id_resolver_as_create_update_for_depend_ids(
@pytest.mark.asyncio
async def test_post_check_ignores_only_explicit_configured_fields(tmp_path: Path) -> None:
db_path = tmp_path / "state.db"
persistence = PersistenceBackend(str(db_path))
persistence = SQLitePersistenceBackend(str(db_path))
await persistence.initialize()
local_collection = DataCollection("local", persistence=persistence, auto_persist=False)
@@ -7,6 +7,7 @@ import pytest
from sync_state_machine.common.binding import BindingManager
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.common.persistence import PersistenceBackend
from sync_state_machine.common.sqlite_backend import SQLitePersistenceBackend
from tests.fixtures.mock_domain import build_project_node, register_test_domain
@@ -15,7 +16,7 @@ async def test_scope_partitioning_and_copy_between_scopes(tmp_path: Path) -> Non
register_test_domain()
db_path = tmp_path / "scope_partitioning.db"
persistence = PersistenceBackend(str(db_path))
persistence = SQLitePersistenceBackend(str(db_path))
await persistence.initialize()
global_collection = DataCollection("local", scope="global", persistence=persistence, auto_persist=False)