规范了update data的构建方式,统一使用build_update_request_data方法来处理更新数据的构建和过滤逻辑。这个方法会根据提供的原始数据、更新数据和对应的schema来生成最终的更新请求数据,同时会自动排除掉未变化的字段和空值字段,从而确保只发送必要的更新信息到后端接口。这种方式不仅简化了代码逻辑,还提高了代码的可维护性和一致性。
This commit is contained in:
@@ -17,7 +17,7 @@ import asyncio
|
||||
from abc import abstractmethod
|
||||
from copy import deepcopy
|
||||
from typing import ClassVar, Dict, List, Any, Optional, TYPE_CHECKING, TypeVar
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from ...common.type_safety import get_pydantic_model_fields
|
||||
from ..handler import BaseNodeHandler
|
||||
@@ -51,9 +51,6 @@ class BaseApiHandler(BaseNodeHandler[T]):
|
||||
# post-check 将只比较这些 schema 覆盖的字段,过滤后端只读的派生字段。
|
||||
self.update_schemas: List[type] = []
|
||||
|
||||
def set_collection(self, collection: 'DataCollection') -> None:
|
||||
self._collection = collection
|
||||
|
||||
def get_update_fields(self, *, exclude: frozenset = frozenset({'id'})) -> List[str]:
|
||||
"""从 update_schemas 中提取所有可写字段名,排除标识键(默认排除 id)。"""
|
||||
fields: set = set()
|
||||
@@ -167,10 +164,6 @@ class BaseApiHandler(BaseNodeHandler[T]):
|
||||
payload["steps"] = self._get_request_steps(node) if node is not None else []
|
||||
return payload
|
||||
|
||||
def _build_full_update_data(self, new_data: Dict[str, Any], schema: Any) -> Dict[str, Any]:
|
||||
validated = schema.model_validate(new_data)
|
||||
return validated.model_dump(exclude_none=True)
|
||||
|
||||
def _mark_approval_snapshot_synced(self, node: 'SyncNode') -> None:
|
||||
snapshot = self._get_active_approval_snapshot(node)
|
||||
if snapshot:
|
||||
@@ -255,8 +248,53 @@ class BaseApiHandler(BaseNodeHandler[T]):
|
||||
return None
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
|
||||
def _get_update_schema_payload(
|
||||
self,
|
||||
schema: Any,
|
||||
data: Dict[str, Any],
|
||||
*,
|
||||
require_valid: bool,
|
||||
) -> Dict[str, Any] | None:
|
||||
"""Update 内部实现:提取指定 schema 下的 payload。
|
||||
|
||||
这里明确只服务“update schema”,不负责 response model 一致性校验。
|
||||
这个方法只给 BaseApiHandler 的 update 入口方法复用,不作为 handler 入口。
|
||||
|
||||
处理顺序:
|
||||
1. 先尝试用 update schema 校验,成功则直接使用校验后的 model_dump。
|
||||
2. 如果校验失败且 require_valid=False,则退化为只抽取 schema 定义的字段。
|
||||
|
||||
之所以允许退化,是因为 update 比较时两边快照常常不完整:
|
||||
某些字段在旧快照缺失,但新数据已经有值。此时应该继续比较 schema
|
||||
相关字段,而不是把它误判成“无差异”。
|
||||
|
||||
空字符串和 None 会被统一过滤掉,因为当前 update handler 并不把它们
|
||||
当成“清空字段”的显式语义,而是当作“不参与本次更新”。
|
||||
"""
|
||||
try:
|
||||
validated = schema.model_validate(data)
|
||||
payload = validated.model_dump(exclude_unset=True)
|
||||
except ValidationError:
|
||||
if require_valid:
|
||||
return None
|
||||
|
||||
schema_fields = get_pydantic_model_fields(schema)
|
||||
if not schema_fields:
|
||||
return {}
|
||||
payload = {
|
||||
field_name: data.get(field_name)
|
||||
for field_name in schema_fields.keys()
|
||||
if field_name in data
|
||||
}
|
||||
|
||||
return {
|
||||
field_name: value
|
||||
for field_name, value in payload.items()
|
||||
if value not in (None, "")
|
||||
}
|
||||
|
||||
def _get_schema_diff(
|
||||
def get_update_schema_diff(
|
||||
self,
|
||||
schema,
|
||||
data: Dict[str, Any],
|
||||
@@ -264,8 +302,7 @@ class BaseApiHandler(BaseNodeHandler[T]):
|
||||
node_id: Optional[str] = None,
|
||||
force_full_schema: bool = False,
|
||||
) -> Dict[str, Any] | None:
|
||||
"""
|
||||
获取数据在指定 schema 下的差异字段
|
||||
"""Update 入口:获取指定 update schema 下的差异字段。
|
||||
|
||||
Args:
|
||||
schema: Pydantic schema 类
|
||||
@@ -276,46 +313,37 @@ class BaseApiHandler(BaseNodeHandler[T]):
|
||||
Returns:
|
||||
Dict[str, Any] | None: 有差异返回差异字段字典,无差异或字段不全返回 None
|
||||
"""
|
||||
from pydantic import ValidationError
|
||||
|
||||
try:
|
||||
new_model = schema.model_validate(data)
|
||||
origin_model = schema.model_validate(origin_data)
|
||||
|
||||
new_dict = new_model.model_dump(exclude_unset=True)
|
||||
origin_dict = origin_model.model_dump(exclude_unset=True)
|
||||
|
||||
if new_dict == origin_dict:
|
||||
if force_full_schema:
|
||||
return new_dict
|
||||
return None
|
||||
|
||||
# 返回有差异的字段
|
||||
diff_fields = {k: v for k, v in new_dict.items() if origin_dict.get(k) != v}
|
||||
|
||||
if diff_fields:
|
||||
# 打印差异详情
|
||||
node_info = f"[{node_id}]" if node_id else ""
|
||||
print(f" [DIFF] {self.node_type}{node_info} schema={schema.__name__}")
|
||||
for k, v in diff_fields.items():
|
||||
orig_v = origin_dict.get(k)
|
||||
print(f" • {k}: {orig_v!r} → {v!r}")
|
||||
|
||||
return diff_fields if diff_fields else None
|
||||
|
||||
except ValidationError:
|
||||
# schema 字段不全,视为无差异
|
||||
# 当前待发送的数据必须能构成合法 update payload;允许旧快照不完整。
|
||||
new_dict = self._get_update_schema_payload(schema, data, require_valid=True)
|
||||
origin_dict = self._get_update_schema_payload(schema, origin_data, require_valid=False)
|
||||
|
||||
if new_dict is None or origin_dict is None:
|
||||
return None
|
||||
|
||||
if new_dict == origin_dict:
|
||||
if force_full_schema:
|
||||
return new_dict
|
||||
return None
|
||||
|
||||
diff_fields = {k: v for k, v in new_dict.items() if origin_dict.get(k) != v}
|
||||
|
||||
if diff_fields:
|
||||
node_info = f"[{node_id}]" if node_id else ""
|
||||
print(f" [DIFF] {self.node_type}{node_info} schema={schema.__name__}")
|
||||
for k, v in diff_fields.items():
|
||||
orig_v = origin_dict.get(k)
|
||||
print(f" • {k}: {orig_v!r} → {v!r}")
|
||||
|
||||
return diff_fields if diff_fields else None
|
||||
|
||||
def _filter_update_data(
|
||||
def build_update_request_data(
|
||||
self,
|
||||
new_data: Dict[str, Any],
|
||||
original_data: Dict[str, Any],
|
||||
schema: Optional[Any] = None,
|
||||
force_full_schema: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
过滤出需要更新的字段
|
||||
"""Update 入口:构造最终要发送给更新接口的请求数据。
|
||||
|
||||
规则:
|
||||
1. 提取 schema 定义的所有字段(包括必填)
|
||||
@@ -342,29 +370,10 @@ class BaseApiHandler(BaseNodeHandler[T]):
|
||||
update_data[key] = new_value
|
||||
return update_data
|
||||
|
||||
# 1. 提取 schema 定义的所有字段
|
||||
schema_fields = set(schema_fields.keys())
|
||||
schema_data = {}
|
||||
has_change = False
|
||||
|
||||
for key in schema_fields:
|
||||
new_value = new_data.get(key)
|
||||
|
||||
# 排除空值
|
||||
if new_value == "" or new_value is None:
|
||||
continue
|
||||
|
||||
schema_data[key] = new_value
|
||||
|
||||
# 检查是否变化
|
||||
original_value = original_data.get(key)
|
||||
if new_value != original_value:
|
||||
# 记录具体差异以便排查
|
||||
# print(f" [DEBUG] Field '{key}' changed: '{original_value}' -> '{new_value}'")
|
||||
has_change = True
|
||||
|
||||
# 2. 只有存在变化时才返回数据
|
||||
if has_change or force_full_schema:
|
||||
schema_data = self._get_update_schema_payload(schema, new_data, require_valid=True) or {}
|
||||
origin_schema_data = self._get_update_schema_payload(schema, original_data, require_valid=False) or {}
|
||||
|
||||
if force_full_schema or schema_data != origin_schema_data:
|
||||
return schema_data
|
||||
return {}
|
||||
|
||||
@@ -374,16 +383,12 @@ class BaseApiHandler(BaseNodeHandler[T]):
|
||||
new_data: Dict[str, Any],
|
||||
origin_data: Dict[str, Any],
|
||||
) -> List[str]:
|
||||
schema_fields = get_pydantic_model_fields(schema)
|
||||
if not schema_fields:
|
||||
return []
|
||||
changed: List[str] = []
|
||||
for field_name in schema_fields.keys():
|
||||
if new_data.get(field_name) != origin_data.get(field_name):
|
||||
changed.append(field_name)
|
||||
return changed
|
||||
new_payload = self._get_update_schema_payload(schema, new_data, require_valid=True) or {}
|
||||
origin_payload = self._get_update_schema_payload(schema, origin_data, require_valid=False) or {}
|
||||
field_names = sorted(set(new_payload) | set(origin_payload))
|
||||
return [field_name for field_name in field_names if new_payload.get(field_name) != origin_payload.get(field_name)]
|
||||
|
||||
def _build_update_schema_status(
|
||||
def build_update_schema_status(
|
||||
self,
|
||||
*,
|
||||
schema_name: str,
|
||||
@@ -393,6 +398,7 @@ class BaseApiHandler(BaseNodeHandler[T]):
|
||||
will_update: bool,
|
||||
update_error: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Update 入口:生成 update schema 的状态说明文本。"""
|
||||
new_error = self._validate_schema(new_data, schema)
|
||||
origin_error = self._validate_schema(origin_data, schema)
|
||||
changed_fields = self._schema_changed_fields(schema, new_data, origin_data)
|
||||
|
||||
Reference in New Issue
Block a user