first commit
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
"""
|
||||
同步系统工具函数模块
|
||||
|
||||
提供业务键提取等通用功能。
|
||||
"""
|
||||
from typing import List, Tuple, Optional, Any, Dict
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def is_id_field(field_name: str) -> bool:
|
||||
"""
|
||||
判断字段是否为ID字段。
|
||||
|
||||
ID字段的判断规则:
|
||||
- 以 _id 结尾(如 project_id, company_id)
|
||||
- 字段名为 id
|
||||
|
||||
Args:
|
||||
field_name: 字段名
|
||||
|
||||
Returns:
|
||||
如果是ID字段返回True
|
||||
"""
|
||||
return field_name.endswith("_id") or field_name == "id"
|
||||
|
||||
|
||||
def extract_biz_key_dict(
|
||||
data: Any,
|
||||
field_names: List[str]
|
||||
) -> Optional[Dict[str, str]]:
|
||||
"""
|
||||
从数据对象提取业务键字段,返回字典格式。
|
||||
|
||||
Args:
|
||||
data: 数据对象(dict或pydantic model)
|
||||
field_names: 业务键字段名列表
|
||||
|
||||
Returns:
|
||||
业务键字段字典,如果任何必需字段缺失则返回None
|
||||
"""
|
||||
if not field_names or not data:
|
||||
return None
|
||||
|
||||
# 转换为字典
|
||||
if isinstance(data, dict):
|
||||
d = data
|
||||
else:
|
||||
d = data.model_dump()
|
||||
|
||||
key_dict = {}
|
||||
for field_name in field_names:
|
||||
# 区分:字段不存在 vs 字段存在但值为None
|
||||
if field_name not in d:
|
||||
return None # 字段不存在,视为缺失
|
||||
value = d.get(field_name)
|
||||
# 值为None时保留为空字符串,允许作为有效业务键值
|
||||
key_dict[field_name] = str(value) if value is not None else ""
|
||||
|
||||
return key_dict if key_dict else None
|
||||
|
||||
|
||||
def dict_to_biz_key_tuple(
|
||||
key_dict: Dict[str, str],
|
||||
field_names: List[str]
|
||||
) -> Tuple:
|
||||
"""
|
||||
将业务键字典转换为tuple(按field_names顺序)。
|
||||
|
||||
Args:
|
||||
key_dict: 业务键字段字典
|
||||
field_names: 字段名顺序
|
||||
|
||||
Returns:
|
||||
业务键tuple
|
||||
"""
|
||||
return tuple(key_dict[fn] for fn in field_names)
|
||||
|
||||
|
||||
async def resolve_id_fields_in_key_dict(
|
||||
key_dict: Dict[str, str],
|
||||
id_resolver,
|
||||
node_type_hints: Dict[str, str]
|
||||
) -> tuple[Optional[Dict[str, str]], Optional[str]]:
|
||||
"""
|
||||
解析业务键字典中的ID字段,将本地ID转换为远程ID。
|
||||
|
||||
只处理ID字段(以_id结尾或为id),其他字段保持不变。
|
||||
|
||||
特殊处理:
|
||||
- 如果 ID 字段值为空字符串 "",直接保留(空值也是有效的业务键值)
|
||||
- 只有非空 ID 才需要解析
|
||||
|
||||
Args:
|
||||
key_dict: 业务键字典
|
||||
id_resolver: ID解析器实例
|
||||
node_type_hints: 字段名到node_type的映射
|
||||
|
||||
Returns:
|
||||
(resolved_dict, error_msg): 成功时返回 (解析后的字典, None),失败时返回 (None, 错误描述)
|
||||
"""
|
||||
resolved_dict = {}
|
||||
|
||||
for field_name, value in key_dict.items():
|
||||
if is_id_field(field_name):
|
||||
# 这是ID字段
|
||||
if value == "" or value is None:
|
||||
# 空值直接保留,不尝试解析
|
||||
# 空值也是有效的业务键值,只要两边一致就能匹配
|
||||
resolved_dict[field_name] = value or ""
|
||||
else:
|
||||
# 非空ID需要解析
|
||||
node_type = node_type_hints.get(field_name)
|
||||
remote_id = await id_resolver.resolve_id(
|
||||
value, node_type_hint=node_type
|
||||
)
|
||||
if remote_id is None:
|
||||
# ID解析失败,返回详细错误信息
|
||||
node_type_info = f"({node_type})" if node_type else "(未知类型)"
|
||||
error_msg = f"字段 {field_name}{node_type_info} 的 ID '{value}' 解析失败(依赖节点未绑定)"
|
||||
return None, error_msg
|
||||
resolved_dict[field_name] = remote_id
|
||||
else:
|
||||
# 非ID字段,直接使用原值
|
||||
resolved_dict[field_name] = value
|
||||
|
||||
return resolved_dict, None
|
||||
Reference in New Issue
Block a user