Files
ecm_sync_system/sync_state_machine/config/strategy_config.py
T
2026-03-24 08:40:40 +08:00

117 lines
3.8 KiB
Python

from __future__ import annotations
from enum import Enum
from typing import Dict, List, Optional
from pydantic import BaseModel, ConfigDict, Field
class OrphanAction(str, Enum):
CREATE_REMOTE = "create_remote"
CREATE_LOCAL = "create_local"
DELETE_LOCAL = "delete_local"
DELETE_REMOTE = "delete_remote"
NONE = "none"
class UpdateDirection(str, Enum):
PUSH = "push"
PULL = "pull"
BIDIRECTIONAL = "bidirectional"
NONE = "none"
class PreBindDataIdPair(BaseModel):
model_config = ConfigDict(validate_assignment=True, extra="forbid")
local_data_id: str
remote_data_id: str
class StrategyConfig(BaseModel):
model_config = ConfigDict(validate_assignment=True, extra="forbid")
auto_bind: bool = True
auto_bind_fields: List[str] = Field(default_factory=list)
pre_bind_data_id: List[PreBindDataIdPair] = Field(default_factory=list)
depend_fields: Dict[str, str] = Field(default_factory=dict)
local_orphan_action: OrphanAction = OrphanAction.NONE
remote_orphan_action: OrphanAction = OrphanAction.NONE
update_direction: UpdateDirection = UpdateDirection.PUSH
# 字段级同步方向覆盖。
# field_direction_key: 节点 data 中用作查表键的字段名,如 "key"。
# field_direction_overrides: 字段值 → UpdateDirection 的映射。
# 未命中映射的节点沿用 update_direction 作为默认方向。
field_direction_key: Optional[str] = None
field_direction_overrides: Dict[str, UpdateDirection] = Field(default_factory=dict)
# post-check 一致性比较时忽略的顶层字段。
# 只影响最终一致性评估,不影响 create/update 的差异检测。
post_check_ignore_fields: List[str] = Field(default_factory=list)
# schema diff / validate 时忽略的顶层字段。
compare_ignore_fields: List[str] = Field(default_factory=list)
class StrategyRuntimeConfig(BaseModel):
"""Pipeline 层策略运行配置(按 node_type)。"""
model_config = ConfigDict(validate_assignment=True, extra="forbid")
node_type: str
skip_sync: bool = False
config: StrategyConfig = Field(default_factory=StrategyConfig)
class ConfigPresets:
@staticmethod
def first_sync() -> StrategyConfig:
return StrategyConfig(
local_orphan_action=OrphanAction.CREATE_REMOTE,
remote_orphan_action=OrphanAction.CREATE_LOCAL,
update_direction=UpdateDirection.PUSH,
)
@staticmethod
def daily_sync() -> StrategyConfig:
return ConfigPresets.push_only()
@staticmethod
def repair_sync() -> StrategyConfig:
return StrategyConfig(
local_orphan_action=OrphanAction.NONE,
remote_orphan_action=OrphanAction.NONE,
update_direction=UpdateDirection.NONE,
)
@staticmethod
def pull_only() -> StrategyConfig:
return StrategyConfig(
local_orphan_action=OrphanAction.NONE,
remote_orphan_action=OrphanAction.CREATE_LOCAL,
update_direction=UpdateDirection.PULL,
)
@staticmethod
def push_only() -> StrategyConfig:
return StrategyConfig(
local_orphan_action=OrphanAction.CREATE_REMOTE,
remote_orphan_action=OrphanAction.NONE,
update_direction=UpdateDirection.PUSH,
)
@staticmethod
def get_preset(name: str) -> StrategyConfig:
presets = {
"first_sync": ConfigPresets.first_sync,
"daily_sync": ConfigPresets.daily_sync,
"repair_sync": ConfigPresets.repair_sync,
"pull_only": ConfigPresets.pull_only,
"push_only": ConfigPresets.push_only,
}
if name not in presets:
raise ValueError(f"Unknown preset: {name}. Available: {list(presets.keys())}")
return presets[name]()