mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 13:01:12 +00:00
add update remove
This commit is contained in:
@@ -107,6 +107,8 @@ class HTTPClient:
|
|||||||
for u, n in old_uuids.items():
|
for u, n in old_uuids.items():
|
||||||
if u in uuid_mapping:
|
if u in uuid_mapping:
|
||||||
n.res_content.uuid = uuid_mapping[u]
|
n.res_content.uuid = uuid_mapping[u]
|
||||||
|
for c in n.children:
|
||||||
|
c.res_content.parent_uuid = n.res_content.uuid
|
||||||
else:
|
else:
|
||||||
logger.warning(f"资源UUID未更新: {u}")
|
logger.warning(f"资源UUID未更新: {u}")
|
||||||
return uuid_mapping
|
return uuid_mapping
|
||||||
|
|||||||
@@ -386,13 +386,33 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
try:
|
try:
|
||||||
if action == "add":
|
if action == "add":
|
||||||
# 添加资源到资源跟踪器
|
# 添加资源到资源跟踪器
|
||||||
plr_resource = tree_set.to_plr_resources() # FIXME: 转成plr的实例
|
plr_resources = tree_set.to_plr_resources()
|
||||||
|
for plr_resource, tree in zip(plr_resources, tree_set.trees):
|
||||||
|
self.resource_tracker.add_resource(plr_resource)
|
||||||
|
parent_uuid = tree.root_node.res_content.parent_uuid
|
||||||
|
if parent_uuid:
|
||||||
|
parent_resource: ResourcePLR = self.resource_tracker.uuid_to_resources.get(parent_uuid)
|
||||||
|
if parent_resource is None:
|
||||||
|
self.lab_logger().warning(f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_uuid}不存在")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
parent_resource.assign_child_resource(plr_resource, location=None)
|
||||||
|
except Exception as e:
|
||||||
|
self.lab_logger().warning(
|
||||||
|
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_resource}[{parent_uuid}]失败!\n{traceback.format_exc()}")
|
||||||
func = getattr(self.driver_instance, "resource_tree_add", None)
|
func = getattr(self.driver_instance, "resource_tree_add", None)
|
||||||
if callable(func):
|
if callable(func):
|
||||||
func(tree_set)
|
func(plr_resources)
|
||||||
results.append({"success": True, "action": "add"})
|
results.append({"success": True, "action": "add"})
|
||||||
elif action == "update":
|
elif action == "update":
|
||||||
# 更新资源
|
# 更新资源
|
||||||
|
plr_resources = tree_set.to_plr_resources()
|
||||||
|
for plr_resource, tree in zip(plr_resources, tree_set.trees):
|
||||||
|
states = plr_resource.serialize_all_state()
|
||||||
|
original_instance: ResourcePLR = self.resource_tracker.figure_resource({"uuid": tree.root_node.res_content.uuid}, try_mode=False)
|
||||||
|
original_instance.load_all_state(states)
|
||||||
|
self.lab_logger().info(f"更新了资源属性 {plr_resource}[{tree.root_node.res_content.uuid}] 及其子节点 {len(original_instance.get_all_children())} 个")
|
||||||
|
|
||||||
func = getattr(self.driver_instance, "resource_tree_update", None)
|
func = getattr(self.driver_instance, "resource_tree_update", None)
|
||||||
if callable(func):
|
if callable(func):
|
||||||
func(tree_set)
|
func(tree_set)
|
||||||
@@ -401,10 +421,12 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
# 移除资源
|
# 移除资源
|
||||||
func = getattr(self.driver_instance, "resource_tree_remove", None)
|
func = getattr(self.driver_instance, "resource_tree_remove", None)
|
||||||
if callable(func):
|
if callable(func):
|
||||||
resources_instance: List[ResourcePLR] = [self.resource_tracker.uuid_to_resources[i] for
|
resources_instances: List[ResourcePLR] = [self.resource_tracker.uuid_to_resources[i] for
|
||||||
i in resources_uuid]
|
i in resources_uuid]
|
||||||
func(resources_instance)
|
self.resource_tracker.add_resource()
|
||||||
[r.parent.unassign_child_resource(r) for r in resources_instance if r is not None]
|
[r.parent.unassign_child_resource(r) for r in resources_instances if r is not None]
|
||||||
|
func(resources_instances)
|
||||||
|
[r.parent.unassign_child_resource(r) for r in resources_instances if r is not None]
|
||||||
results.append({"success": True, "action": "remove"})
|
results.append({"success": True, "action": "remove"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Error processing {action} operation: {str(e)}"
|
error_msg = f"Error processing {action} operation: {str(e)}"
|
||||||
|
|||||||
@@ -175,9 +175,6 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
for device_config in devices_config.root_nodes:
|
for device_config in devices_config.root_nodes:
|
||||||
device_id = device_config.res_content.id
|
device_id = device_config.res_content.id
|
||||||
if device_config.res_content.type != "device":
|
if device_config.res_content.type != "device":
|
||||||
self.lab_logger().debug(
|
|
||||||
f"[Host Node] Skipping type {device_config.res_content.type} {device_id} already existed, skipping."
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
if device_id not in self.devices_names:
|
if device_id not in self.devices_names:
|
||||||
self.initialize_device(device_id, device_config)
|
self.initialize_device(device_id, device_config)
|
||||||
|
|||||||
@@ -52,18 +52,17 @@ class ResourceDict(BaseModel):
|
|||||||
schema: Dict[str, Any] = Field(description="Resource schema", default_factory=dict)
|
schema: Dict[str, Any] = Field(description="Resource schema", default_factory=dict)
|
||||||
model: Dict[str, Any] = Field(description="Resource model", default_factory=dict)
|
model: Dict[str, Any] = Field(description="Resource model", default_factory=dict)
|
||||||
icon: str = Field(description="Resource icon", default="")
|
icon: str = Field(description="Resource icon", default="")
|
||||||
parent: Optional["ResourceDict"] = Field(
|
parent_uuid: Optional["str"] = Field(description="Parent resource uuid", default=None) # 先设定parent_uuid
|
||||||
description="Parent resource object", default=None, serialization_alias="parent_uuid"
|
parent: Optional["ResourceDict"] = Field(description="Parent resource object", default=None, exclude=True)
|
||||||
)
|
|
||||||
type: Literal["device"] | str = Field(description="Resource type")
|
type: Literal["device"] | str = Field(description="Resource type")
|
||||||
klass: str = Field(alias="class", description="Resource class name")
|
klass: str = Field(alias="class", description="Resource class name")
|
||||||
position: ResourceDictPosition = Field(description="Resource position", default_factory=ResourceDictPosition)
|
position: ResourceDictPosition = Field(description="Resource position", default_factory=ResourceDictPosition)
|
||||||
config: Dict[str, Any] = Field(description="Resource configuration")
|
config: Dict[str, Any] = Field(description="Resource configuration")
|
||||||
data: Dict[str, Any] = Field(description="Resource data")
|
data: Dict[str, Any] = Field(description="Resource data")
|
||||||
|
|
||||||
@field_serializer("parent")
|
@field_serializer("parent_uuid")
|
||||||
def _serialize_parent(self, parent: Optional["ResourceDict"]):
|
def _serialize_parent(self, parent_uuid: Optional["ResourceDict"]):
|
||||||
return self.parent_uuid
|
return self.uuid_parent
|
||||||
|
|
||||||
@field_validator("parent", mode="before")
|
@field_validator("parent", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -74,13 +73,23 @@ class ResourceDict(BaseModel):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent_uuid(self) -> str:
|
def uuid_parent(self) -> str:
|
||||||
"""获取父节点的UUID"""
|
"""获取父节点的UUID"""
|
||||||
return self.parent.uuid if self.parent is not None else ""
|
parent_instance_uuid = self.parent_instance_uuid
|
||||||
|
if parent_instance_uuid is not None and self.parent_uuid and parent_instance_uuid != self.parent_uuid:
|
||||||
|
logger.warning(f"{self.name}[{self.uuid}]的parent uuid未同步!") # 现在强制要求设置
|
||||||
|
if parent_instance_uuid is not None:
|
||||||
|
return parent_instance_uuid
|
||||||
|
return self.parent_uuid
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent_name(self) -> Optional[str]:
|
def parent_instance_uuid(self) -> Optional[str]:
|
||||||
"""获取父节点的UUID"""
|
"""获取父节点的UUID"""
|
||||||
|
return self.parent.uuid if self.parent is not None else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent_instance_name(self) -> Optional[str]:
|
||||||
|
"""获取父节点的名字"""
|
||||||
return self.parent.name if self.parent is not None else None
|
return self.parent.name if self.parent is not None else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -101,7 +110,7 @@ class ResourceDictInstance(object):
|
|||||||
|
|
||||||
def __init__(self, res_content: "ResourceDict"):
|
def __init__(self, res_content: "ResourceDict"):
|
||||||
self.res_content = res_content
|
self.res_content = res_content
|
||||||
self.children = []
|
self.children: List[ResourceDictInstance] = []
|
||||||
self.typ = "dict"
|
self.typ = "dict"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -132,7 +141,7 @@ class ResourceDictInstance(object):
|
|||||||
"""获取资源实例的嵌套字典表示"""
|
"""获取资源实例的嵌套字典表示"""
|
||||||
res_dict = self.res_content.model_dump(by_alias=True)
|
res_dict = self.res_content.model_dump(by_alias=True)
|
||||||
res_dict["children"] = {child.res_content.name: child.get_nested_dict() for child in self.children}
|
res_dict["children"] = {child.res_content.name: child.get_nested_dict() for child in self.children}
|
||||||
res_dict["parent"] = self.res_content.parent_name
|
res_dict["parent"] = self.res_content.parent_instance_name
|
||||||
res_dict["position"] = self.res_content.position.position.model_dump()
|
res_dict["position"] = self.res_content.position.position.model_dump()
|
||||||
return res_dict
|
return res_dict
|
||||||
|
|
||||||
@@ -174,7 +183,7 @@ class ResourceTreeInstance(object):
|
|||||||
current_uuid = queue.pop(0)
|
current_uuid = queue.pop(0)
|
||||||
# 查找所有parent_uuid指向当前节点的子节点
|
# 查找所有parent_uuid指向当前节点的子节点
|
||||||
for uuid_str, res in uuid_map.items():
|
for uuid_str, res in uuid_map.items():
|
||||||
if res.parent_uuid == current_uuid and uuid_str not in visited:
|
if res.uuid_parent == current_uuid and uuid_str not in visited:
|
||||||
child_instance = ResourceDictInstance(res)
|
child_instance = ResourceDictInstance(res)
|
||||||
tree_nodes.append(child_instance)
|
tree_nodes.append(child_instance)
|
||||||
visited.add(uuid_str)
|
visited.add(uuid_str)
|
||||||
@@ -363,14 +372,12 @@ class ResourceTreeSet(object):
|
|||||||
trees.append(tree_instance)
|
trees.append(tree_instance)
|
||||||
return cls(trees)
|
return cls(trees)
|
||||||
|
|
||||||
def to_plr_resources(self) -> Tuple[List["PLRResource"], List[Dict[str, str]]]:
|
def to_plr_resources(self) -> List["PLRResource"]:
|
||||||
"""
|
"""
|
||||||
将 ResourceTreeSet 转换为 PLR 资源列表
|
将 ResourceTreeSet 转换为 PLR 资源列表
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[List[PLRResource], List[Dict[str, str]]]:
|
List[PLRResource]: PLR 资源实例列表
|
||||||
- PLR 资源实例列表
|
|
||||||
- 每个资源对应的 name_to_uuid 映射字典列表
|
|
||||||
"""
|
"""
|
||||||
from pylabrobot.resources import Resource as PLRResource
|
from pylabrobot.resources import Resource as PLRResource
|
||||||
from pylabrobot.utils.object_parsing import find_subclass
|
from pylabrobot.utils.object_parsing import find_subclass
|
||||||
@@ -408,7 +415,7 @@ class ResourceTreeSet(object):
|
|||||||
"rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"},
|
"rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"},
|
||||||
"category": plr_type,
|
"category": plr_type,
|
||||||
"children": [node_to_plr_dict(child, has_model) for child in node.children],
|
"children": [node_to_plr_dict(child, has_model) for child in node.children],
|
||||||
"parent_name": res.parent_name,
|
"parent_name": res.parent_instance_name,
|
||||||
**res.config,
|
**res.config,
|
||||||
}
|
}
|
||||||
if has_model:
|
if has_model:
|
||||||
@@ -416,34 +423,27 @@ class ResourceTreeSet(object):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
plr_resources = []
|
plr_resources = []
|
||||||
name_to_uuid_maps = []
|
trees = []
|
||||||
tracker = DeviceNodeResourceTracker()
|
tracker = DeviceNodeResourceTracker()
|
||||||
|
|
||||||
for tree in self.trees:
|
for tree in self.trees:
|
||||||
name_to_uuid: Dict[str, str] = {}
|
name_to_uuid: Dict[str, str] = {}
|
||||||
all_states: Dict[str, Any] = {}
|
all_states: Dict[str, Any] = {}
|
||||||
collect_node_data(tree.root_node, name_to_uuid, all_states)
|
collect_node_data(tree.root_node, name_to_uuid, all_states)
|
||||||
|
|
||||||
has_model = tree.root_node.res_content.type != "deck"
|
has_model = tree.root_node.res_content.type != "deck"
|
||||||
plr_dict = node_to_plr_dict(tree.root_node, has_model)
|
plr_dict = node_to_plr_dict(tree.root_node, has_model)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sub_cls = find_subclass(plr_dict["type"], PLRResource)
|
sub_cls = find_subclass(plr_dict["type"], PLRResource)
|
||||||
if sub_cls is None:
|
if sub_cls is None:
|
||||||
raise ValueError(f"无法找到类型 {plr_dict['type']} 对应的 PLR 资源类")
|
raise ValueError(f"无法找到类型 {plr_dict['type']} 对应的 PLR 资源类")
|
||||||
|
|
||||||
spec = inspect.signature(sub_cls)
|
spec = inspect.signature(sub_cls)
|
||||||
if "category" not in spec.parameters:
|
if "category" not in spec.parameters:
|
||||||
plr_dict.pop("category", None)
|
plr_dict.pop("category", None)
|
||||||
|
|
||||||
plr_resource = sub_cls.deserialize(plr_dict, allow_marshal=True)
|
plr_resource = sub_cls.deserialize(plr_dict, allow_marshal=True)
|
||||||
plr_resource.load_all_state(all_states)
|
plr_resource.load_all_state(all_states)
|
||||||
|
|
||||||
# 使用 DeviceNodeResourceTracker 设置 UUID
|
# 使用 DeviceNodeResourceTracker 设置 UUID
|
||||||
tracker.loop_set_uuid(plr_resource, name_to_uuid)
|
tracker.loop_set_uuid(plr_resource, name_to_uuid)
|
||||||
|
|
||||||
plr_resources.append(plr_resource)
|
plr_resources.append(plr_resource)
|
||||||
name_to_uuid_maps.append(name_to_uuid)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"转换 PLR 资源失败: {e}")
|
logger.error(f"转换 PLR 资源失败: {e}")
|
||||||
@@ -452,7 +452,7 @@ class ResourceTreeSet(object):
|
|||||||
logger.error(f"堆栈: {traceback.format_exc()}")
|
logger.error(f"堆栈: {traceback.format_exc()}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return plr_resources, name_to_uuid_maps
|
return plr_resources
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_nested_list(cls, nested_list: List[ResourceDictInstance]) -> "ResourceTreeSet":
|
def from_nested_list(cls, nested_list: List[ResourceDictInstance]) -> "ResourceTreeSet":
|
||||||
@@ -473,7 +473,7 @@ class ResourceTreeSet(object):
|
|||||||
root_instances = [
|
root_instances = [
|
||||||
ResourceTreeInstance(res_instance)
|
ResourceTreeInstance(res_instance)
|
||||||
for res_instance in nested_list
|
for res_instance in nested_list
|
||||||
if res_instance.res_content.is_root_node or res_instance.res_content.parent_uuid not in known_uuids
|
if res_instance.res_content.is_root_node or res_instance.res_content.uuid_parent not in known_uuids
|
||||||
]
|
]
|
||||||
return cls(root_instances)
|
return cls(root_instances)
|
||||||
|
|
||||||
@@ -584,6 +584,63 @@ class DeviceNodeResourceTracker(object):
|
|||||||
self.uuid_to_resources[new_uuid] = instance
|
self.uuid_to_resources[new_uuid] = instance
|
||||||
print(f"更新uuid映射: {old_uuid} -> {new_uuid} | {instance}")
|
print(f"更新uuid映射: {old_uuid} -> {new_uuid} | {instance}")
|
||||||
|
|
||||||
|
def _get_resource_attr(self, resource, attr_name: str, uuid_attr: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
获取资源的属性值,统一处理 dict 和 instance 两种类型
|
||||||
|
|
||||||
|
Args:
|
||||||
|
resource: 资源对象(dict或实例)
|
||||||
|
attr_name: dict类型使用的属性名
|
||||||
|
uuid_attr: instance类型使用的属性名(用于uuid字段),默认与attr_name相同
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
属性值,不存在则返回None
|
||||||
|
"""
|
||||||
|
if uuid_attr is None:
|
||||||
|
uuid_attr = attr_name
|
||||||
|
|
||||||
|
if isinstance(resource, dict):
|
||||||
|
return resource.get(attr_name)
|
||||||
|
else:
|
||||||
|
return getattr(resource, uuid_attr, None)
|
||||||
|
|
||||||
|
def _set_resource_uuid(self, resource, new_uuid: str):
|
||||||
|
"""
|
||||||
|
设置资源的 uuid,统一处理 dict 和 instance 两种类型
|
||||||
|
|
||||||
|
Args:
|
||||||
|
resource: 资源对象(dict或实例)
|
||||||
|
new_uuid: 新的uuid值
|
||||||
|
"""
|
||||||
|
if isinstance(resource, dict):
|
||||||
|
resource["uuid"] = new_uuid
|
||||||
|
else:
|
||||||
|
setattr(resource, "unilabos_uuid", new_uuid)
|
||||||
|
|
||||||
|
def _traverse_and_process(self, resource, process_func) -> int:
|
||||||
|
"""
|
||||||
|
递归遍历资源树,对每个节点执行处理函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
resource: 资源对象(可以是list、dict或实例)
|
||||||
|
process_func: 处理函数,接收resource参数,返回处理的节点数量
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
处理的节点总数量
|
||||||
|
"""
|
||||||
|
if isinstance(resource, list):
|
||||||
|
return sum(self._traverse_and_process(r, process_func) for r in resource)
|
||||||
|
|
||||||
|
# 先递归处理所有子节点
|
||||||
|
count = 0
|
||||||
|
children = getattr(resource, "children", [])
|
||||||
|
for child in children:
|
||||||
|
count += self._traverse_and_process(child, process_func)
|
||||||
|
|
||||||
|
# 处理当前节点
|
||||||
|
count += process_func(resource)
|
||||||
|
return count
|
||||||
|
|
||||||
def loop_set_uuid(self, resource, name_to_uuid_map: Dict[str, str]) -> int:
|
def loop_set_uuid(self, resource, name_to_uuid_map: Dict[str, str]) -> int:
|
||||||
"""
|
"""
|
||||||
递归遍历资源树,根据 name 设置所有节点的 uuid
|
递归遍历资源树,根据 name 设置所有节点的 uuid
|
||||||
@@ -595,36 +652,18 @@ class DeviceNodeResourceTracker(object):
|
|||||||
Returns:
|
Returns:
|
||||||
更新的资源数量
|
更新的资源数量
|
||||||
"""
|
"""
|
||||||
if isinstance(resource, list):
|
|
||||||
return sum(self.loop_set_uuid(r, name_to_uuid_map) for r in resource)
|
|
||||||
|
|
||||||
update_count = 0
|
def process(res):
|
||||||
|
resource_name = self._get_resource_attr(res, "name")
|
||||||
|
if resource_name and resource_name in name_to_uuid_map:
|
||||||
|
new_uuid = name_to_uuid_map[resource_name]
|
||||||
|
self._set_resource_uuid(res, new_uuid)
|
||||||
|
self.uuid_to_resources[new_uuid] = res
|
||||||
|
logger.debug(f"设置资源UUID: {resource_name} -> {new_uuid}")
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
# 先递归处理所有子节点
|
return self._traverse_and_process(resource, process)
|
||||||
children = getattr(resource, "children", [])
|
|
||||||
for child in children:
|
|
||||||
update_count += self.loop_set_uuid(child, name_to_uuid_map)
|
|
||||||
|
|
||||||
# 获取当前资源的name
|
|
||||||
if isinstance(resource, dict):
|
|
||||||
resource_name = resource.get("name")
|
|
||||||
else:
|
|
||||||
resource_name = getattr(resource, "name", None)
|
|
||||||
|
|
||||||
# 如果name在映射中,则设置uuid
|
|
||||||
if resource_name and resource_name in name_to_uuid_map:
|
|
||||||
new_uuid = name_to_uuid_map[resource_name]
|
|
||||||
# 更新资源的uuid
|
|
||||||
if isinstance(resource, dict):
|
|
||||||
resource["uuid"] = new_uuid
|
|
||||||
else:
|
|
||||||
# 对于PLR资源,设置unilabos_uuid
|
|
||||||
setattr(resource, "unilabos_uuid", new_uuid)
|
|
||||||
self.uuid_to_resources[new_uuid] = resource
|
|
||||||
update_count += 1
|
|
||||||
logger.debug(f"设置资源UUID: {resource_name} -> {new_uuid}")
|
|
||||||
|
|
||||||
return update_count
|
|
||||||
|
|
||||||
def loop_update_uuid(self, resource, uuid_map: Dict[str, str]) -> int:
|
def loop_update_uuid(self, resource, uuid_map: Dict[str, str]) -> int:
|
||||||
"""
|
"""
|
||||||
@@ -637,43 +676,56 @@ class DeviceNodeResourceTracker(object):
|
|||||||
Returns:
|
Returns:
|
||||||
更新的资源数量
|
更新的资源数量
|
||||||
"""
|
"""
|
||||||
if isinstance(resource, list):
|
|
||||||
return sum(self.loop_update_uuid(r, uuid_map) for r in resource)
|
|
||||||
|
|
||||||
update_count = 0
|
def process(res):
|
||||||
|
current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid")
|
||||||
|
if current_uuid and current_uuid in uuid_map:
|
||||||
|
new_uuid = uuid_map[current_uuid]
|
||||||
|
if current_uuid != new_uuid:
|
||||||
|
self._set_resource_uuid(res, new_uuid)
|
||||||
|
# 更新uuid_to_resources映射
|
||||||
|
if current_uuid in self.uuid_to_resources:
|
||||||
|
self.uuid_to_resources.pop(current_uuid)
|
||||||
|
self.uuid_to_resources[new_uuid] = res
|
||||||
|
logger.debug(f"更新uuid: {current_uuid} -> {new_uuid}")
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
# 先递归处理所有子节点
|
return self._traverse_and_process(resource, process)
|
||||||
children = getattr(resource, "children", [])
|
|
||||||
for child in children:
|
|
||||||
update_count += self.loop_update_uuid(child, uuid_map)
|
|
||||||
|
|
||||||
# 获取当前资源的uuid
|
def _collect_uuid_mapping(self, resource):
|
||||||
if isinstance(resource, dict):
|
"""
|
||||||
current_uuid = resource.get("uuid")
|
递归收集资源的 uuid 映射到 uuid_to_resources
|
||||||
else:
|
|
||||||
current_uuid = getattr(resource, "unilabos_uuid", None)
|
|
||||||
|
|
||||||
# 如果当前uuid在映射中,则更新
|
Args:
|
||||||
if current_uuid and current_uuid in uuid_map:
|
resource: 资源对象(可以是dict或实例)
|
||||||
new_uuid = uuid_map[current_uuid]
|
"""
|
||||||
if current_uuid != new_uuid:
|
|
||||||
# 更新资源的uuid
|
|
||||||
if isinstance(resource, dict):
|
|
||||||
resource["uuid"] = new_uuid
|
|
||||||
else:
|
|
||||||
# 对于PLR资源,更新unilabos_uuid
|
|
||||||
if hasattr(resource, "unilabos_uuid"):
|
|
||||||
setattr(resource, "unilabos_uuid", new_uuid)
|
|
||||||
|
|
||||||
# 更新uuid_to_resources映射
|
def process(res):
|
||||||
if current_uuid in self.uuid_to_resources:
|
current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid")
|
||||||
instance = self.uuid_to_resources.pop(current_uuid)
|
if current_uuid:
|
||||||
self.uuid_to_resources[new_uuid] = instance
|
self.uuid_to_resources[current_uuid] = res
|
||||||
|
logger.debug(f"收集资源UUID映射: {current_uuid} -> {res}")
|
||||||
|
return 0
|
||||||
|
|
||||||
update_count += 1
|
self._traverse_and_process(resource, process)
|
||||||
logger.debug(f"更新uuid: {current_uuid} -> {new_uuid} | {resource}")
|
|
||||||
|
|
||||||
return update_count
|
def _remove_uuid_mapping(self, resource):
|
||||||
|
"""
|
||||||
|
递归清除资源的 uuid 映射
|
||||||
|
|
||||||
|
Args:
|
||||||
|
resource: 资源对象(可以是dict或实例)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def process(res):
|
||||||
|
current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid")
|
||||||
|
if current_uuid and current_uuid in self.uuid_to_resources:
|
||||||
|
self.uuid_to_resources.pop(current_uuid)
|
||||||
|
logger.debug(f"移除资源UUID映射: {current_uuid} -> {res}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
self._traverse_and_process(resource, process)
|
||||||
|
|
||||||
def parent_resource(self, resource):
|
def parent_resource(self, resource):
|
||||||
if id(resource) in self.resource2parent_resource:
|
if id(resource) in self.resource2parent_resource:
|
||||||
@@ -682,21 +734,105 @@ class DeviceNodeResourceTracker(object):
|
|||||||
return resource
|
return resource
|
||||||
|
|
||||||
def add_resource(self, resource):
|
def add_resource(self, resource):
|
||||||
|
"""
|
||||||
|
添加资源到追踪器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
resource: 资源对象(可以是dict或实例)
|
||||||
|
"""
|
||||||
for r in self.resources:
|
for r in self.resources:
|
||||||
if id(r) == id(resource):
|
if id(r) == id(resource):
|
||||||
return
|
return
|
||||||
self.resources.append(resource)
|
self.resources.append(resource)
|
||||||
|
# 递归收集uuid映射
|
||||||
|
self._collect_uuid_mapping(resource)
|
||||||
|
|
||||||
|
def remove_resource(self, resource) -> bool:
|
||||||
|
"""
|
||||||
|
从追踪器中移除资源
|
||||||
|
|
||||||
|
Args:
|
||||||
|
resource: 资源对象(可以是dict或实例)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 如果成功移除返回True,资源不存在返回False
|
||||||
|
"""
|
||||||
|
# 从 resources 列表中移除
|
||||||
|
resource_id = id(resource)
|
||||||
|
removed = False
|
||||||
|
for i, r in enumerate(self.resources):
|
||||||
|
if id(r) == resource_id:
|
||||||
|
self.resources.pop(i)
|
||||||
|
removed = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not removed:
|
||||||
|
logger.warning(f"尝试移除不存在的资源: {resource}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 递归清除uuid映射
|
||||||
|
self._remove_uuid_mapping(resource)
|
||||||
|
|
||||||
|
# 清除 resource2parent_resource 中与该资源相关的映射
|
||||||
|
# 需要清除:1) 该资源作为 key 的映射 2) 该资源作为 value 的映射
|
||||||
|
keys_to_remove = []
|
||||||
|
for key, value in self.resource2parent_resource.items():
|
||||||
|
if id(value) == resource_id:
|
||||||
|
keys_to_remove.append(key)
|
||||||
|
|
||||||
|
if resource_id in self.resource2parent_resource:
|
||||||
|
keys_to_remove.append(resource_id)
|
||||||
|
|
||||||
|
for key in keys_to_remove:
|
||||||
|
self.resource2parent_resource.pop(key, None)
|
||||||
|
|
||||||
|
logger.debug(f"成功移除资源: {resource}")
|
||||||
|
return True
|
||||||
|
|
||||||
def clear_resource(self):
|
def clear_resource(self):
|
||||||
|
"""清空所有资源"""
|
||||||
self.resources = []
|
self.resources = []
|
||||||
|
self.uuid_to_resources.clear()
|
||||||
|
self.resource2parent_resource.clear()
|
||||||
|
|
||||||
def figure_resource(self, query_resource, try_mode=False):
|
def figure_resource(self, query_resource, try_mode=False):
|
||||||
if isinstance(query_resource, list):
|
if isinstance(query_resource, list):
|
||||||
return [self.figure_resource(r, try_mode) for r in query_resource]
|
return [self.figure_resource(r, try_mode) for r in query_resource]
|
||||||
elif (
|
elif (
|
||||||
isinstance(query_resource, dict) and "id" not in query_resource and "name" not in query_resource
|
isinstance(query_resource, dict)
|
||||||
|
and "id" not in query_resource
|
||||||
|
and "name" not in query_resource
|
||||||
|
and "uuid" not in query_resource
|
||||||
): # 临时处理,要删除的,driver有太多类型错误标注
|
): # 临时处理,要删除的,driver有太多类型错误标注
|
||||||
return [self.figure_resource(r, try_mode) for r in query_resource.values()]
|
return [self.figure_resource(r, try_mode) for r in query_resource.values()]
|
||||||
|
|
||||||
|
# 优先尝试通过 uuid 查找
|
||||||
|
res_uuid = None
|
||||||
|
if isinstance(query_resource, dict):
|
||||||
|
res_uuid = query_resource.get("uuid")
|
||||||
|
else:
|
||||||
|
res_uuid = getattr(query_resource, "unilabos_uuid", None)
|
||||||
|
|
||||||
|
# 如果有 uuid,优先使用 uuid 查找
|
||||||
|
if res_uuid:
|
||||||
|
res_list = []
|
||||||
|
for r in self.resources:
|
||||||
|
if isinstance(query_resource, dict):
|
||||||
|
res_list.extend(self.loop_find_resource(r, object, "uuid", res_uuid))
|
||||||
|
else:
|
||||||
|
res_list.extend(self.loop_find_resource(r, type(query_resource), "unilabos_uuid", res_uuid))
|
||||||
|
|
||||||
|
if not try_mode:
|
||||||
|
assert len(res_list) > 0, f"没有找到资源 (uuid={res_uuid}),请检查资源是否存在"
|
||||||
|
assert len(res_list) == 1, f"通过uuid={res_uuid} 找到多个资源,请检查资源是否唯一: {res_list}"
|
||||||
|
else:
|
||||||
|
return [i[1] for i in res_list]
|
||||||
|
|
||||||
|
self.resource2parent_resource[id(query_resource)] = res_list[0][0]
|
||||||
|
self.resource2parent_resource[id(res_list[0][1])] = res_list[0][0]
|
||||||
|
return res_list[0][1]
|
||||||
|
|
||||||
|
# 回退到 id/name 查找
|
||||||
res_id = (
|
res_id = (
|
||||||
query_resource.id # type: ignore
|
query_resource.id # type: ignore
|
||||||
if hasattr(query_resource, "id")
|
if hasattr(query_resource, "id")
|
||||||
@@ -711,7 +847,7 @@ class DeviceNodeResourceTracker(object):
|
|||||||
identifier_key = "id" if res_id else "name"
|
identifier_key = "id" if res_id else "name"
|
||||||
resource_cls_type = type(query_resource)
|
resource_cls_type = type(query_resource)
|
||||||
if res_identifier is None:
|
if res_identifier is None:
|
||||||
logger.warning(f"resource {query_resource} 没有id或name,暂不能对应figure")
|
logger.warning(f"resource {query_resource} 没有id、name或uuid,暂不能对应figure")
|
||||||
res_list = []
|
res_list = []
|
||||||
for r in self.resources:
|
for r in self.resources:
|
||||||
if isinstance(query_resource, dict):
|
if isinstance(query_resource, dict):
|
||||||
@@ -744,12 +880,16 @@ class DeviceNodeResourceTracker(object):
|
|||||||
)
|
)
|
||||||
if issubclass(type(resource), target_resource_cls_type):
|
if issubclass(type(resource), target_resource_cls_type):
|
||||||
if target_resource_cls_type == dict:
|
if target_resource_cls_type == dict:
|
||||||
|
# 对于字典类型,直接检查 identifier_key
|
||||||
if identifier_key in resource:
|
if identifier_key in resource:
|
||||||
if resource[identifier_key] == compare_value:
|
if resource[identifier_key] == compare_value:
|
||||||
res_list.append((parent_res, resource))
|
res_list.append((parent_res, resource))
|
||||||
elif hasattr(resource, identifier_key):
|
else:
|
||||||
if getattr(resource, identifier_key) == compare_value:
|
# 对于实例类型,需要特殊处理 uuid 字段
|
||||||
res_list.append((parent_res, resource))
|
# 如果查找的是 unilabos_uuid,使用 getattr
|
||||||
|
if hasattr(resource, identifier_key):
|
||||||
|
if getattr(resource, identifier_key) == compare_value:
|
||||||
|
res_list.append((parent_res, resource))
|
||||||
return res_list
|
return res_list
|
||||||
|
|
||||||
def filter_find_list(self, res_list, compare_std_dict):
|
def filter_find_list(self, res_list, compare_std_dict):
|
||||||
@@ -803,21 +943,15 @@ if __name__ == "__main__":
|
|||||||
print(f" - 所有节点数量: {len(resource_tree_set.all_nodes)}")
|
print(f" - 所有节点数量: {len(resource_tree_set.all_nodes)}")
|
||||||
|
|
||||||
# 3. 将 ResourceTreeSet 转换回 PLR 资源
|
# 3. 将 ResourceTreeSet 转换回 PLR 资源
|
||||||
plr_resources, name_to_uuid_maps = resource_tree_set.to_plr_resources()
|
plr_resources = resource_tree_set.to_plr_resources()
|
||||||
converted_plate = plr_resources[0]
|
converted_plate = plr_resources[0]
|
||||||
print(f"\n3. 转换回 PLR 资源: {converted_plate.name}")
|
print(f"\n3. 转换回 PLR 资源: {converted_plate.name}")
|
||||||
print(f" - 子节点数量: {len(converted_plate.children)}")
|
print(f" - 子节点数量: {len(converted_plate.children)}")
|
||||||
if converted_plate.children:
|
if converted_plate.children:
|
||||||
print(f" - 第一个子节点: {converted_plate.children[0].name}")
|
print(f" - 第一个子节点: {converted_plate.children[0].name}")
|
||||||
|
|
||||||
# 4. 验证 UUID 映射
|
# 4. 验证 unilabos_uuid 属性
|
||||||
name_to_uuid = name_to_uuid_maps[0]
|
print(f"\n4. 验证 unilabos_uuid 设置:")
|
||||||
print(f"\n4. UUID 映射:")
|
|
||||||
print(f" - 映射条目数: {len(name_to_uuid)}")
|
|
||||||
print(f" - 示例映射: {list(name_to_uuid.items())[:3]}")
|
|
||||||
|
|
||||||
# 5. 验证 unilabos_uuid 属性
|
|
||||||
print(f"\n5. 验证 unilabos_uuid 设置:")
|
|
||||||
if hasattr(converted_plate, "unilabos_uuid"):
|
if hasattr(converted_plate, "unilabos_uuid"):
|
||||||
print(f" - 根节点 UUID: {getattr(converted_plate, 'unilabos_uuid')}")
|
print(f" - 根节点 UUID: {getattr(converted_plate, 'unilabos_uuid')}")
|
||||||
if converted_plate.children and hasattr(converted_plate.children[0], "unilabos_uuid"):
|
if converted_plate.children and hasattr(converted_plate.children[0], "unilabos_uuid"):
|
||||||
@@ -825,18 +959,18 @@ if __name__ == "__main__":
|
|||||||
else:
|
else:
|
||||||
print(" - 警告: unilabos_uuid 未设置")
|
print(" - 警告: unilabos_uuid 未设置")
|
||||||
|
|
||||||
# 6. 验证 UUID 保持不变
|
# 5. 验证 UUID 保持不变
|
||||||
print(f"\n6. 验证 UUID 在往返过程中保持不变:")
|
print(f"\n5. 验证 UUID 在往返过程中保持不变:")
|
||||||
original_uuid = getattr(original_plate, "unilabos_uuid")
|
original_uuid = getattr(original_plate, "unilabos_uuid")
|
||||||
converted_uuid = getattr(converted_plate, "unilabos_uuid")
|
converted_uuid = getattr(converted_plate, "unilabos_uuid")
|
||||||
print(f" - 原始 UUID: {original_uuid}")
|
print(f" - 原始 UUID: {original_uuid}")
|
||||||
print(f" - 转换后 UUID: {converted_uuid}")
|
print(f" - 转换后 UUID: {converted_uuid}")
|
||||||
print(f" - UUID 保持不变: {original_uuid == converted_uuid}")
|
print(f" - UUID 保持不变: {original_uuid == converted_uuid}")
|
||||||
|
|
||||||
# 7. 再次往返转换,验证稳定性
|
# 6. 再次往返转换,验证稳定性
|
||||||
resource_tree_set_2 = ResourceTreeSet.from_plr_resources([converted_plate])
|
resource_tree_set_2 = ResourceTreeSet.from_plr_resources([converted_plate])
|
||||||
plr_resources_2, name_to_uuid_maps_2 = resource_tree_set_2.to_plr_resources()
|
plr_resources_2 = resource_tree_set_2.to_plr_resources()
|
||||||
print(f"\n7. 第二次往返转换:")
|
print(f"\n6. 第二次往返转换:")
|
||||||
print(f" - 资源名称: {plr_resources_2[0].name}")
|
print(f" - 资源名称: {plr_resources_2[0].name}")
|
||||||
print(f" - 子节点数量: {len(plr_resources_2[0].children)}")
|
print(f" - 子节点数量: {len(plr_resources_2[0].children)}")
|
||||||
print(f" - UUID 依然保持: {getattr(plr_resources_2[0], 'unilabos_uuid') == original_uuid}")
|
print(f" - UUID 依然保持: {getattr(plr_resources_2[0], 'unilabos_uuid') == original_uuid}")
|
||||||
|
|||||||
Reference in New Issue
Block a user