Skip to content

Commit 5179a7e

Browse files
committed
workflow upload & set liquid fix & add set liquid with plate
fix upload workflow json save class name when deserialize & protocol execute test Support root node change pos add unilabos_class gather query
1 parent 3a2d9e9 commit 5179a7e

15 files changed

Lines changed: 2618 additions & 472 deletions

File tree

docs/user_guide/best_practice.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,9 @@ unilab --ak your_ak --sk your_sk -g test/experiments/mock_devices/mock_all.json
439439
1. 访问 Web 界面,进入"仪器耗材"模块
440440
2. 在"仪器设备"区域找到并添加上述设备
441441
3. 在"物料耗材"区域找到并添加容器
442+
4. 在workstation中配置protocol_type包含PumpTransferProtocol
443+
444+
![添加Protocol类型](image/add_protocol.png)
442445

443446
![物料列表](image/material.png)
444447

81.4 KB
Loading

tests/workflow/test.json

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
{
2+
"workflow": [
3+
{
4+
"action": "transfer_liquid",
5+
"action_args": {
6+
"sources": "cell_lines",
7+
"targets": "Liquid_1",
8+
"asp_vol": 100.0,
9+
"dis_vol": 74.75,
10+
"asp_flow_rate": 94.0,
11+
"dis_flow_rate": 95.5
12+
}
13+
},
14+
{
15+
"action": "transfer_liquid",
16+
"action_args": {
17+
"sources": "cell_lines",
18+
"targets": "Liquid_2",
19+
"asp_vol": 100.0,
20+
"dis_vol": 74.75,
21+
"asp_flow_rate": 94.0,
22+
"dis_flow_rate": 95.5
23+
}
24+
},
25+
{
26+
"action": "transfer_liquid",
27+
"action_args": {
28+
"sources": "cell_lines",
29+
"targets": "Liquid_3",
30+
"asp_vol": 100.0,
31+
"dis_vol": 74.75,
32+
"asp_flow_rate": 94.0,
33+
"dis_flow_rate": 95.5
34+
}
35+
},
36+
{
37+
"action": "transfer_liquid",
38+
"action_args": {
39+
"sources": "cell_lines_2",
40+
"targets": "Liquid_4",
41+
"asp_vol": 100.0,
42+
"dis_vol": 74.75,
43+
"asp_flow_rate": 94.0,
44+
"dis_flow_rate": 95.5
45+
}
46+
},
47+
{
48+
"action": "transfer_liquid",
49+
"action_args": {
50+
"sources": "cell_lines_2",
51+
"targets": "Liquid_5",
52+
"asp_vol": 100.0,
53+
"dis_vol": 74.75,
54+
"asp_flow_rate": 94.0,
55+
"dis_flow_rate": 95.5
56+
}
57+
},
58+
{
59+
"action": "transfer_liquid",
60+
"action_args": {
61+
"sources": "cell_lines_2",
62+
"targets": "Liquid_6",
63+
"asp_vol": 100.0,
64+
"dis_vol": 74.75,
65+
"asp_flow_rate": 94.0,
66+
"dis_flow_rate": 95.5
67+
}
68+
},
69+
{
70+
"action": "transfer_liquid",
71+
"action_args": {
72+
"sources": "cell_lines_3",
73+
"targets": "dest_set",
74+
"asp_vol": 100.0,
75+
"dis_vol": 74.75,
76+
"asp_flow_rate": 94.0,
77+
"dis_flow_rate": 95.5
78+
}
79+
},
80+
{
81+
"action": "transfer_liquid",
82+
"action_args": {
83+
"sources": "cell_lines_3",
84+
"targets": "dest_set_2",
85+
"asp_vol": 100.0,
86+
"dis_vol": 74.75,
87+
"asp_flow_rate": 94.0,
88+
"dis_flow_rate": 95.5
89+
}
90+
},
91+
{
92+
"action": "transfer_liquid",
93+
"action_args": {
94+
"sources": "cell_lines_3",
95+
"targets": "dest_set_3",
96+
"asp_vol": 100.0,
97+
"dis_vol": 74.75,
98+
"asp_flow_rate": 94.0,
99+
"dis_flow_rate": 95.5
100+
}
101+
}
102+
],
103+
"reagent": {
104+
"Liquid_1": {
105+
"slot": 1,
106+
"well": [
107+
"A4",
108+
"A7",
109+
"A10"
110+
],
111+
"labware": "rep 1"
112+
},
113+
"Liquid_4": {
114+
"slot": 1,
115+
"well": [
116+
"A4",
117+
"A7",
118+
"A10"
119+
],
120+
"labware": "rep 1"
121+
},
122+
"dest_set": {
123+
"slot": 1,
124+
"well": [
125+
"A4",
126+
"A7",
127+
"A10"
128+
],
129+
"labware": "rep 1"
130+
},
131+
"Liquid_2": {
132+
"slot": 2,
133+
"well": [
134+
"A3",
135+
"A5",
136+
"A8"
137+
],
138+
"labware": "rep 2"
139+
},
140+
"Liquid_5": {
141+
"slot": 2,
142+
"well": [
143+
"A3",
144+
"A5",
145+
"A8"
146+
],
147+
"labware": "rep 2"
148+
},
149+
"dest_set_2": {
150+
"slot": 2,
151+
"well": [
152+
"A3",
153+
"A5",
154+
"A8"
155+
],
156+
"labware": "rep 2"
157+
},
158+
"Liquid_3": {
159+
"slot": 3,
160+
"well": [
161+
"A4",
162+
"A6",
163+
"A10"
164+
],
165+
"labware": "rep 3"
166+
},
167+
"Liquid_6": {
168+
"slot": 3,
169+
"well": [
170+
"A4",
171+
"A6",
172+
"A10"
173+
],
174+
"labware": "rep 3"
175+
},
176+
"dest_set_3": {
177+
"slot": 3,
178+
"well": [
179+
"A4",
180+
"A6",
181+
"A10"
182+
],
183+
"labware": "rep 3"
184+
},
185+
"cell_lines": {
186+
"slot": 4,
187+
"well": [
188+
"A1",
189+
"A3",
190+
"A5"
191+
],
192+
"labware": "DRUG + YOYO-MEDIA"
193+
},
194+
"cell_lines_2": {
195+
"slot": 4,
196+
"well": [
197+
"A1",
198+
"A3",
199+
"A5"
200+
],
201+
"labware": "DRUG + YOYO-MEDIA"
202+
},
203+
"cell_lines_3": {
204+
"slot": 4,
205+
"well": [
206+
"A1",
207+
"A3",
208+
"A5"
209+
],
210+
"labware": "DRUG + YOYO-MEDIA"
211+
}
212+
}
213+
}

unilabos/app/web/client.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ def resource_tree_add(self, resources: ResourceTreeSet, mount_uuid: str, first_a
7474
Dict[str, str]: 旧UUID到新UUID的映射关系 {old_uuid: new_uuid}
7575
"""
7676
with open(os.path.join(BasicConfig.working_dir, "req_resource_tree_add.json"), "w", encoding="utf-8") as f:
77-
f.write(json.dumps({"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid}, indent=4))
77+
payload = {"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid}
78+
f.write(json.dumps(payload, indent=4))
7879
# 从序列化数据中提取所有节点的UUID(保存旧UUID)
7980
old_uuids = {n.res_content.uuid: n for n in resources.all_nodes}
8081
if not self.initialized or first_add:
@@ -333,6 +334,65 @@ def request_startup_json(self) -> Optional[Dict[str, Any]]:
333334
logger.error(f"响应内容: {response.text}")
334335
return None
335336

337+
def workflow_import(
338+
self,
339+
name: str,
340+
workflow_uuid: str,
341+
workflow_name: str,
342+
nodes: List[Dict[str, Any]],
343+
edges: List[Dict[str, Any]],
344+
tags: Optional[List[str]] = None,
345+
published: bool = False,
346+
) -> Dict[str, Any]:
347+
"""
348+
导入工作流到服务器
349+
350+
Args:
351+
name: 工作流名称(顶层)
352+
workflow_uuid: 工作流UUID
353+
workflow_name: 工作流名称(data内部)
354+
nodes: 工作流节点列表
355+
edges: 工作流边列表
356+
tags: 工作流标签列表,默认为空列表
357+
published: 是否发布工作流,默认为False
358+
359+
Returns:
360+
Dict: API响应数据,包含 code 和 data (uuid, name)
361+
"""
362+
payload = {
363+
"name": name,
364+
"data": {
365+
"workflow_uuid": workflow_uuid,
366+
"workflow_name": workflow_name,
367+
"nodes": nodes,
368+
"edges": edges,
369+
"tags": tags if tags is not None else [],
370+
"published": published,
371+
},
372+
}
373+
# 保存请求到文件
374+
with open(os.path.join(BasicConfig.working_dir, "req_workflow_upload.json"), "w", encoding="utf-8") as f:
375+
f.write(json.dumps(payload, indent=4, ensure_ascii=False))
376+
377+
response = requests.post(
378+
f"{self.remote_addr}/lab/workflow/owner/import",
379+
json=payload,
380+
headers={"Authorization": f"Lab {self.auth}"},
381+
timeout=60,
382+
)
383+
# 保存响应到文件
384+
with open(os.path.join(BasicConfig.working_dir, "res_workflow_upload.json"), "w", encoding="utf-8") as f:
385+
f.write(f"{response.status_code}" + "\n" + response.text)
386+
387+
if response.status_code == 200:
388+
res = response.json()
389+
if "code" in res and res["code"] != 0:
390+
logger.error(f"导入工作流失败: {response.text}")
391+
return res
392+
else:
393+
logger.error(f"导入工作流失败: {response.status_code}, {response.text}")
394+
return {"code": response.status_code, "message": response.text}
395+
336396

337397
# 创建默认客户端实例
338398
http_client = HTTPClient()

0 commit comments

Comments
 (0)