first commit

This commit is contained in:
strepsiades
2026-03-09 16:31:42 +08:00
commit 4a9c444b10
486 changed files with 52479 additions and 0 deletions
+128
View File
@@ -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