129 lines
3.8 KiB
Python
129 lines
3.8 KiB
Python
"""
|
||
同步系统工具函数模块
|
||
|
||
提供业务键提取等通用功能。
|
||
"""
|
||
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
|