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