pybioos 0.0.15__py3-none-any.whl → 0.0.19__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bioos/__about__.py +1 -1
- bioos/bioos.py +32 -7
- bioos/bioos_workflow.py +99 -8
- bioos/bw_import.py +5 -24
- bioos/bw_import_status_check.py +7 -2
- bioos/bw_status_check.py +7 -2
- bioos/config.py +4 -1
- bioos/get_submission_logs.py +7 -2
- bioos/resource/__init__.py +1 -1
- bioos/resource/iesapp.py +806 -0
- bioos/resource/workflows.py +273 -6
- bioos/resource/workspaces.py +140 -0
- bioos/service/BioOsService.py +103 -0
- bioos/workflow_info.py +207 -0
- {pybioos-0.0.15.dist-info → pybioos-0.0.19.dist-info}/METADATA +1 -1
- {pybioos-0.0.15.dist-info → pybioos-0.0.19.dist-info}/RECORD +20 -18
- {pybioos-0.0.15.dist-info → pybioos-0.0.19.dist-info}/LICENSE +0 -0
- {pybioos-0.0.15.dist-info → pybioos-0.0.19.dist-info}/WHEEL +0 -0
- {pybioos-0.0.15.dist-info → pybioos-0.0.19.dist-info}/entry_points.txt +0 -0
- {pybioos-0.0.15.dist-info → pybioos-0.0.19.dist-info}/top_level.txt +0 -0
bioos/resource/workflows.py
CHANGED
|
@@ -3,7 +3,7 @@ import os
|
|
|
3
3
|
import zipfile
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from io import BytesIO
|
|
6
|
-
from typing import List
|
|
6
|
+
from typing import List, Dict, Optional, Any
|
|
7
7
|
|
|
8
8
|
import pandas as pd
|
|
9
9
|
from cachetools import TTLCache, cached
|
|
@@ -339,6 +339,13 @@ class Submission(metaclass=SingletonType): # 与run class行为相同
|
|
|
339
339
|
if not item.get("Status") in ("Running", "Pending"):
|
|
340
340
|
self._finish_time = item.get("FinishTime")
|
|
341
341
|
|
|
342
|
+
def delete(self):
|
|
343
|
+
"""Delete this submission from the workspace."""
|
|
344
|
+
Config.service().delete_submission({
|
|
345
|
+
"WorkspaceID": self.workspace_id,
|
|
346
|
+
"ID": self.id,
|
|
347
|
+
})
|
|
348
|
+
|
|
342
349
|
|
|
343
350
|
class WorkflowResource(metaclass=SingletonType):
|
|
344
351
|
|
|
@@ -548,17 +555,62 @@ class WorkflowResource(metaclass=SingletonType):
|
|
|
548
555
|
|
|
549
556
|
|
|
550
557
|
class Workflow(metaclass=SingletonType):
|
|
558
|
+
"""Represents a workflow in Bio-OS.
|
|
559
|
+
|
|
560
|
+
This class encapsulates all the information and operations related to a workflow,
|
|
561
|
+
including its metadata, inputs, outputs, and execution capabilities.
|
|
562
|
+
"""
|
|
551
563
|
|
|
552
564
|
def __init__(self,
|
|
553
565
|
name: str,
|
|
554
566
|
workspace_id: str,
|
|
555
567
|
bucket: str,
|
|
556
568
|
check: bool = False):
|
|
569
|
+
"""Initialize a workflow instance.
|
|
570
|
+
|
|
571
|
+
Args:
|
|
572
|
+
name: The name of the workflow
|
|
573
|
+
workspace_id: The ID of the workspace containing this workflow
|
|
574
|
+
bucket: The S3 bucket associated with this workflow
|
|
575
|
+
check: Whether to check the workflow existence immediately
|
|
576
|
+
"""
|
|
557
577
|
self.name = name
|
|
558
578
|
self.workspace_id = workspace_id
|
|
559
579
|
self.bucket = bucket
|
|
580
|
+
self._description: str = ""
|
|
581
|
+
self._create_time: int = 0
|
|
582
|
+
self._update_time: int = 0
|
|
583
|
+
self._language: str = "WDL"
|
|
584
|
+
self._source: str = ""
|
|
585
|
+
self._tag: str = ""
|
|
586
|
+
self._token: Optional[str] = None
|
|
587
|
+
self._main_workflow_path: str = ""
|
|
588
|
+
self._status: Dict[str, Optional[str]] = {"Phase": "", "Message": None}
|
|
589
|
+
self._inputs: List[Dict[str, Any]] = []
|
|
590
|
+
self._outputs: List[Dict[str, Any]] = []
|
|
591
|
+
self._owner_name: str = ""
|
|
592
|
+
self._graph: str = ""
|
|
593
|
+
self._source_type: str = ""
|
|
594
|
+
|
|
560
595
|
if check:
|
|
561
|
-
self.
|
|
596
|
+
self.sync()
|
|
597
|
+
|
|
598
|
+
def __repr__(self):
|
|
599
|
+
"""Return a string representation of the workflow."""
|
|
600
|
+
info_dict = dict_str({
|
|
601
|
+
"id": self.id,
|
|
602
|
+
"name": self.name,
|
|
603
|
+
"description": self.description,
|
|
604
|
+
"language": self.language,
|
|
605
|
+
"source": self.source,
|
|
606
|
+
"tag": self.tag,
|
|
607
|
+
"main_workflow_path": self.main_workflow_path,
|
|
608
|
+
"status": self.status,
|
|
609
|
+
"owner_name": self.owner_name,
|
|
610
|
+
"create_time": self.create_time,
|
|
611
|
+
"update_time": self.update_time
|
|
612
|
+
})
|
|
613
|
+
return f"Workflow:\n{info_dict}"
|
|
562
614
|
|
|
563
615
|
@property
|
|
564
616
|
@cached(cache=TTLCache(maxsize=100, ttl=1))
|
|
@@ -574,6 +626,215 @@ class Workflow(metaclass=SingletonType):
|
|
|
574
626
|
raise ParameterError("name")
|
|
575
627
|
return res["ID"].iloc[0]
|
|
576
628
|
|
|
629
|
+
@property
|
|
630
|
+
def description(self) -> str:
|
|
631
|
+
"""Get the workflow description."""
|
|
632
|
+
if not self._description:
|
|
633
|
+
self.sync()
|
|
634
|
+
return self._description
|
|
635
|
+
|
|
636
|
+
@property
|
|
637
|
+
def create_time(self) -> int:
|
|
638
|
+
"""Get the workflow creation timestamp."""
|
|
639
|
+
if not self._create_time:
|
|
640
|
+
self.sync()
|
|
641
|
+
return self._create_time
|
|
642
|
+
|
|
643
|
+
@property
|
|
644
|
+
def update_time(self) -> int:
|
|
645
|
+
"""Get the workflow last update timestamp."""
|
|
646
|
+
if not self._update_time:
|
|
647
|
+
self.sync()
|
|
648
|
+
return self._update_time
|
|
649
|
+
|
|
650
|
+
@property
|
|
651
|
+
def language(self) -> str:
|
|
652
|
+
"""Get the workflow language (e.g., WDL)."""
|
|
653
|
+
if not self._language:
|
|
654
|
+
self.sync()
|
|
655
|
+
return self._language
|
|
656
|
+
|
|
657
|
+
@property
|
|
658
|
+
def source(self) -> str:
|
|
659
|
+
"""Get the workflow source location."""
|
|
660
|
+
if not self._source:
|
|
661
|
+
self.sync()
|
|
662
|
+
return self._source
|
|
663
|
+
|
|
664
|
+
@property
|
|
665
|
+
def tag(self) -> str:
|
|
666
|
+
"""Get the workflow version tag."""
|
|
667
|
+
if not self._tag:
|
|
668
|
+
self.sync()
|
|
669
|
+
return self._tag
|
|
670
|
+
|
|
671
|
+
@property
|
|
672
|
+
def token(self) -> Optional[str]:
|
|
673
|
+
"""Get the workflow access token if any."""
|
|
674
|
+
if not self._token:
|
|
675
|
+
self.sync()
|
|
676
|
+
return self._token
|
|
677
|
+
|
|
678
|
+
@property
|
|
679
|
+
def main_workflow_path(self) -> str:
|
|
680
|
+
"""Get the main workflow file path."""
|
|
681
|
+
if not self._main_workflow_path:
|
|
682
|
+
self.sync()
|
|
683
|
+
return self._main_workflow_path
|
|
684
|
+
|
|
685
|
+
@property
|
|
686
|
+
def status(self) -> Dict[str, Optional[str]]:
|
|
687
|
+
"""Get the workflow status information."""
|
|
688
|
+
if not self._status["Phase"]:
|
|
689
|
+
self.sync()
|
|
690
|
+
return self._status
|
|
691
|
+
@property
|
|
692
|
+
def inputs(self) -> List[Dict[str, Any]]:
|
|
693
|
+
"""Get the workflow input parameters."""
|
|
694
|
+
if not self._inputs:
|
|
695
|
+
self.sync()
|
|
696
|
+
return self._inputs
|
|
697
|
+
|
|
698
|
+
@property
|
|
699
|
+
def outputs(self) -> List[Dict[str, Any]]:
|
|
700
|
+
"""Get the workflow output parameters."""
|
|
701
|
+
if not self._outputs:
|
|
702
|
+
self.sync()
|
|
703
|
+
return self._outputs
|
|
704
|
+
|
|
705
|
+
@property
|
|
706
|
+
def owner_name(self) -> str:
|
|
707
|
+
"""Get the workflow owner's name."""
|
|
708
|
+
if not self._owner_name:
|
|
709
|
+
self.sync()
|
|
710
|
+
return self._owner_name
|
|
711
|
+
|
|
712
|
+
@property
|
|
713
|
+
def graph(self) -> str:
|
|
714
|
+
"""Get the workflow graph representation."""
|
|
715
|
+
if not self._graph:
|
|
716
|
+
self.sync()
|
|
717
|
+
return self._graph
|
|
718
|
+
|
|
719
|
+
@property
|
|
720
|
+
def source_type(self) -> str:
|
|
721
|
+
"""Get the workflow source type."""
|
|
722
|
+
if not self._source_type:
|
|
723
|
+
self.sync()
|
|
724
|
+
return self._source_type
|
|
725
|
+
|
|
726
|
+
@cached(cache=TTLCache(maxsize=100, ttl=1))
|
|
727
|
+
def sync(self):
|
|
728
|
+
"""Synchronize workflow information with the remote service."""
|
|
729
|
+
res = WorkflowResource(self.workspace_id). \
|
|
730
|
+
list().query(f"Name=='{self.name}'")
|
|
731
|
+
if res.empty:
|
|
732
|
+
raise ParameterError("name")
|
|
733
|
+
|
|
734
|
+
# Get detailed workflow information
|
|
735
|
+
params = {
|
|
736
|
+
'WorkspaceID': self.workspace_id,
|
|
737
|
+
'Filter': {
|
|
738
|
+
'IDs': [res["ID"].iloc[0]]
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
workflows = Config.service().list_workflows(params).get('Items')
|
|
742
|
+
if len(workflows) != 1:
|
|
743
|
+
raise NotFoundError("workflow", self.name)
|
|
744
|
+
|
|
745
|
+
detail = workflows[0]
|
|
746
|
+
|
|
747
|
+
# Update all properties
|
|
748
|
+
self._description = detail.get("Description", "")
|
|
749
|
+
self._create_time = detail.get("CreateTime", 0)
|
|
750
|
+
self._update_time = detail.get("UpdateTime", 0)
|
|
751
|
+
self._language = detail.get("Language", "WDL")
|
|
752
|
+
self._source = detail.get("Source", "")
|
|
753
|
+
self._tag = detail.get("Tag", "")
|
|
754
|
+
self._token = detail.get("Token")
|
|
755
|
+
self._main_workflow_path = detail.get("MainWorkflowPath", "")
|
|
756
|
+
self._status = detail.get("Status", {"Phase": "", "Message": None})
|
|
757
|
+
self._inputs = detail.get("Inputs", [])
|
|
758
|
+
self._outputs = detail.get("Outputs", [])
|
|
759
|
+
self._owner_name = detail.get("OwnerName", "")
|
|
760
|
+
self._graph = detail.get("Graph", "")
|
|
761
|
+
self._source_type = detail.get("SourceType", "")
|
|
762
|
+
|
|
763
|
+
def get_input_template(self) -> Dict[str, str]:
|
|
764
|
+
"""Return a readable template of input parameters.
|
|
765
|
+
|
|
766
|
+
Each entry maps parameter name to a human-friendly description,
|
|
767
|
+
e.g. "String (optional, default = \"abc\")" or "Int".
|
|
768
|
+
"""
|
|
769
|
+
result: Dict[str, str] = {}
|
|
770
|
+
inputs = self.inputs or []
|
|
771
|
+
for item in inputs:
|
|
772
|
+
name = item.get("Name", "")
|
|
773
|
+
if not name:
|
|
774
|
+
continue
|
|
775
|
+
type_str = item.get("Type", "")
|
|
776
|
+
optional = item.get("Optional", False)
|
|
777
|
+
default = self._fmt_default(item.get("Default"))
|
|
778
|
+
if optional:
|
|
779
|
+
value = f"{type_str} (optional" + (f", default = {default})" if default is not None else ")")
|
|
780
|
+
else:
|
|
781
|
+
value = type_str
|
|
782
|
+
result[name] = value
|
|
783
|
+
return result
|
|
784
|
+
|
|
785
|
+
def get_output_types(self) -> Dict[str, str]:
|
|
786
|
+
"""Return a mapping of output parameter name to its type."""
|
|
787
|
+
outputs = self.outputs or []
|
|
788
|
+
return {item.get("Name", ""): item.get("Type", "") for item in outputs if item.get("Name")}
|
|
789
|
+
|
|
790
|
+
def get_metadata(self) -> Dict[str, Any]:
|
|
791
|
+
"""Return workflow metadata in a flat dictionary."""
|
|
792
|
+
return {
|
|
793
|
+
"name": self.name,
|
|
794
|
+
"description": self.description,
|
|
795
|
+
"language": self.language,
|
|
796
|
+
"source": self.source,
|
|
797
|
+
"tag": self.tag,
|
|
798
|
+
"status": self.status,
|
|
799
|
+
"owner_name": self.owner_name,
|
|
800
|
+
"create_time": self.create_time,
|
|
801
|
+
"update_time": self.update_time,
|
|
802
|
+
"main_workflow_path": self.main_workflow_path,
|
|
803
|
+
"source_type": self.source_type,
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
@staticmethod
|
|
807
|
+
def _fmt_default(raw: Any) -> Optional[str]:
|
|
808
|
+
"""Format default values into a readable string.
|
|
809
|
+
|
|
810
|
+
- None -> None
|
|
811
|
+
- bool -> "true" / "false"
|
|
812
|
+
- int/float -> numeric string
|
|
813
|
+
- other strings -> quoted string
|
|
814
|
+
"""
|
|
815
|
+
if raw is None:
|
|
816
|
+
return None
|
|
817
|
+
if isinstance(raw, bool):
|
|
818
|
+
return str(raw).lower()
|
|
819
|
+
if isinstance(raw, (int, float)):
|
|
820
|
+
return str(raw)
|
|
821
|
+
if isinstance(raw, str):
|
|
822
|
+
lo = raw.lower()
|
|
823
|
+
if lo in {"true", "false"}:
|
|
824
|
+
return lo
|
|
825
|
+
try:
|
|
826
|
+
int(raw)
|
|
827
|
+
return raw
|
|
828
|
+
except ValueError:
|
|
829
|
+
pass
|
|
830
|
+
try:
|
|
831
|
+
float(raw)
|
|
832
|
+
return raw
|
|
833
|
+
except ValueError:
|
|
834
|
+
pass
|
|
835
|
+
return f'"{raw}"'
|
|
836
|
+
return str(raw)
|
|
837
|
+
|
|
577
838
|
@property
|
|
578
839
|
@cached(cache=TTLCache(maxsize=100, ttl=1))
|
|
579
840
|
def get_cluster(self):
|
|
@@ -594,11 +855,14 @@ class Workflow(metaclass=SingletonType):
|
|
|
594
855
|
return info['ID']
|
|
595
856
|
raise NotFoundError("cluster", "workflow")
|
|
596
857
|
|
|
597
|
-
def query_data_model_id(self, name: str) ->
|
|
858
|
+
def query_data_model_id(self, name: str) -> str:
|
|
598
859
|
"""Gets the id of given data_models among those accessible
|
|
599
860
|
|
|
600
|
-
:
|
|
601
|
-
|
|
861
|
+
Args:
|
|
862
|
+
name: The name of the data model
|
|
863
|
+
|
|
864
|
+
Returns:
|
|
865
|
+
str: The ID of the data model, or empty string if not found
|
|
602
866
|
"""
|
|
603
867
|
res = DataModelResource(self.workspace_id).list(). \
|
|
604
868
|
query(f"Name=='{name}'")
|
|
@@ -613,7 +877,8 @@ class Workflow(metaclass=SingletonType):
|
|
|
613
877
|
call_caching: bool,
|
|
614
878
|
submission_name_suffix: str = "",
|
|
615
879
|
row_ids: List[str] = [],
|
|
616
|
-
data_model_name: str = ''
|
|
880
|
+
data_model_name: str = '',
|
|
881
|
+
mount_tos: bool = False) -> List[Run]:
|
|
617
882
|
"""Submit an existed workflow.
|
|
618
883
|
|
|
619
884
|
*Example*:
|
|
@@ -664,6 +929,7 @@ class Workflow(metaclass=SingletonType):
|
|
|
664
929
|
'Inputs': inputs,
|
|
665
930
|
'ExposedOptions': {
|
|
666
931
|
"ReadFromCache": call_caching,
|
|
932
|
+
"MountTOS": mount_tos,
|
|
667
933
|
# TODO this may change in the future
|
|
668
934
|
"ExecutionRootDir": f"s3://{self.bucket}"
|
|
669
935
|
},
|
|
@@ -682,3 +948,4 @@ class Workflow(metaclass=SingletonType):
|
|
|
682
948
|
submission_id = Config.service().create_submission(params).get("ID")
|
|
683
949
|
|
|
684
950
|
return Submission(self.workspace_id, submission_id).runs
|
|
951
|
+
|
bioos/resource/workspaces.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
import urllib.request
|
|
2
5
|
|
|
3
6
|
import pandas as pd
|
|
4
7
|
from cachetools import TTLCache, cached
|
|
@@ -7,6 +10,7 @@ from bioos.config import Config
|
|
|
7
10
|
from bioos.resource.data_models import DataModelResource
|
|
8
11
|
from bioos.resource.files import FileResource
|
|
9
12
|
from bioos.resource.workflows import Workflow, WorkflowResource
|
|
13
|
+
from bioos.resource.iesapp import WebInstanceApp, WebInstanceAppResource
|
|
10
14
|
from bioos.utils.common_tools import SingletonType, dict_str
|
|
11
15
|
|
|
12
16
|
|
|
@@ -110,6 +114,15 @@ class Workspace(metaclass=SingletonType):
|
|
|
110
114
|
|
|
111
115
|
return FileResource(self._id, self._bucket)
|
|
112
116
|
|
|
117
|
+
@property
|
|
118
|
+
def webinstanceapps(self) -> WebInstanceAppResource:
|
|
119
|
+
"""Returns WebInstanceAppResource object.
|
|
120
|
+
|
|
121
|
+
:return: WebInstanceAppResource object
|
|
122
|
+
:rtype: WebInstanceAppResource
|
|
123
|
+
"""
|
|
124
|
+
return WebInstanceAppResource(self._id)
|
|
125
|
+
|
|
113
126
|
def workflow(self, name: str) -> Workflow: # 通过这里执行的选择workflow生成wf的操作
|
|
114
127
|
"""Returns the workflow for the given name
|
|
115
128
|
|
|
@@ -121,3 +134,130 @@ class Workspace(metaclass=SingletonType):
|
|
|
121
134
|
if not self._bucket:
|
|
122
135
|
self._bucket = self.basic_info.get("s3_bucket")
|
|
123
136
|
return Workflow(name, self._id, self._bucket)
|
|
137
|
+
|
|
138
|
+
def webinstanceapp(self, name: str) -> WebInstanceApp:
|
|
139
|
+
"""Returns the webinstanceapp for the given name
|
|
140
|
+
|
|
141
|
+
:param name: WebInstanceApp name
|
|
142
|
+
:type name: str
|
|
143
|
+
:return: Specified webinstanceapp object
|
|
144
|
+
:rtype: WebInstanceApp
|
|
145
|
+
"""
|
|
146
|
+
return WebInstanceApp(name, self._id)
|
|
147
|
+
|
|
148
|
+
def bind_cluster(self, cluster_id: str, type_: str = "workflow") -> dict:
|
|
149
|
+
"""把当前 Workspace 绑定到指定集群"""
|
|
150
|
+
params = {"ClusterID": cluster_id, "Type": type_, "ID": self._id}
|
|
151
|
+
return Config.service().bind_cluster_to_workspace(params)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def export_workspace_v2(self,
|
|
155
|
+
download_path: str = "./",
|
|
156
|
+
monitor: bool = True,
|
|
157
|
+
monitor_interval: int = 5,
|
|
158
|
+
max_retries: int = 60) -> dict:
|
|
159
|
+
"""导出当前 Workspace 的所有元信息并下载到本地
|
|
160
|
+
|
|
161
|
+
:param download_path: 下载文件保存路径,默认当前目录
|
|
162
|
+
:type download_path: str
|
|
163
|
+
:param monitor: 是否监控导出状态直到完成,默认 True
|
|
164
|
+
:type monitor: bool
|
|
165
|
+
:param monitor_interval: 轮询间隔(秒),默认 5 秒
|
|
166
|
+
:type monitor_interval: int
|
|
167
|
+
:param max_retries: 最大重试次数,默认 60 次(5 分钟)
|
|
168
|
+
:type max_retries: int
|
|
169
|
+
:return: 导出结果信息,包含 status、schema_id、file_path 等
|
|
170
|
+
:rtype: dict
|
|
171
|
+
"""
|
|
172
|
+
params = {"WorkspaceID": self._id}
|
|
173
|
+
result = Config.service().export_workspace_v2(params)
|
|
174
|
+
schema_id = result.get('ID')
|
|
175
|
+
|
|
176
|
+
if not schema_id:
|
|
177
|
+
raise Exception("Failed to create export task: No schema ID returned")
|
|
178
|
+
|
|
179
|
+
# 如果不监控,直接返回任务 ID
|
|
180
|
+
if not monitor:
|
|
181
|
+
return {
|
|
182
|
+
"status": "submitted",
|
|
183
|
+
"schema_id": schema_id,
|
|
184
|
+
"message": "Export task submitted. Use list_schemas to check status."
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# 步骤 2: 轮询查询导出状态
|
|
188
|
+
Config.Logger.info(f"Export task created with schema ID: {schema_id}")
|
|
189
|
+
Config.Logger.info(f"Monitoring export status (checking every {monitor_interval}s, max {max_retries} retries)...")
|
|
190
|
+
|
|
191
|
+
retry_count = 0
|
|
192
|
+
schema_key = None
|
|
193
|
+
|
|
194
|
+
while retry_count < max_retries:
|
|
195
|
+
# 查询所有 schemas
|
|
196
|
+
schemas_result = Config.service().list_schemas({"Filter": {}})
|
|
197
|
+
schemas = schemas_result.get("Items", [])
|
|
198
|
+
|
|
199
|
+
# 查找当前导出任务
|
|
200
|
+
for schema in schemas:
|
|
201
|
+
if schema.get("ID") == schema_id:
|
|
202
|
+
phase = schema.get("Status", {}).get("Phase", "")
|
|
203
|
+
message = schema.get("Status", {}).get("Message", "")
|
|
204
|
+
|
|
205
|
+
if phase == "Succeeded":
|
|
206
|
+
Config.Logger.info("Export task succeeded!")
|
|
207
|
+
schema_key = schema.get("SchemaKey")
|
|
208
|
+
break
|
|
209
|
+
elif phase == "Failed":
|
|
210
|
+
error_msg = f"Export task failed: {message}"
|
|
211
|
+
Config.Logger.error(error_msg)
|
|
212
|
+
raise Exception(error_msg)
|
|
213
|
+
else:
|
|
214
|
+
Config.Logger.info(f"Export task status: {phase}")
|
|
215
|
+
|
|
216
|
+
# 如果成功找到文件,跳出循环
|
|
217
|
+
if schema_key:
|
|
218
|
+
break
|
|
219
|
+
|
|
220
|
+
# 继续等待
|
|
221
|
+
time.sleep(monitor_interval)
|
|
222
|
+
retry_count += 1
|
|
223
|
+
|
|
224
|
+
# 超时检查
|
|
225
|
+
if not schema_key:
|
|
226
|
+
raise Exception(f"Export task timeout after {max_retries * monitor_interval} seconds")
|
|
227
|
+
|
|
228
|
+
# 步骤 3: 获取预签名 URL
|
|
229
|
+
Config.Logger.info(f"Getting presigned URL for schema: {schema_id}")
|
|
230
|
+
presigned_params = {
|
|
231
|
+
"ID": schema_id,
|
|
232
|
+
"WorkspaceID": self._id
|
|
233
|
+
}
|
|
234
|
+
presigned_result = Config.service().get_export_workspace_presigned_url(presigned_params)
|
|
235
|
+
presigned_url = presigned_result.get("PreSignedURL")
|
|
236
|
+
|
|
237
|
+
if not presigned_url:
|
|
238
|
+
raise Exception("Failed to get presigned URL for export file")
|
|
239
|
+
|
|
240
|
+
Config.Logger.info(f"Downloading export file from presigned URL...")
|
|
241
|
+
|
|
242
|
+
# 步骤 4: 使用 HTTP 请求下载文件
|
|
243
|
+
# 确保下载目录存在
|
|
244
|
+
os.makedirs(download_path, exist_ok=True)
|
|
245
|
+
|
|
246
|
+
# 从 URL 中提取文件名
|
|
247
|
+
filename = os.path.basename(schema_key)
|
|
248
|
+
local_file_path = os.path.join(download_path, filename)
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
# 使用 urllib 下载文件
|
|
252
|
+
urllib.request.urlretrieve(presigned_url, local_file_path)
|
|
253
|
+
Config.Logger.info(f"Export file downloaded successfully to: {local_file_path}")
|
|
254
|
+
except Exception as e:
|
|
255
|
+
raise Exception(f"Failed to download export file from presigned URL: {str(e)}")
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
"status": "success",
|
|
259
|
+
"schema_id": schema_id,
|
|
260
|
+
"schema_key": schema_key,
|
|
261
|
+
"file_path": local_file_path,
|
|
262
|
+
"message": f"Workspace exported and downloaded successfully"
|
|
263
|
+
}
|
bioos/service/BioOsService.py
CHANGED
|
@@ -47,6 +47,11 @@ class BioOsService(Service):
|
|
|
47
47
|
'Action': 'ListWorkspaces',
|
|
48
48
|
'Version': '2021-03-04'
|
|
49
49
|
}, {}, {}),
|
|
50
|
+
'CreateWorkspace':
|
|
51
|
+
ApiInfo('POST', '/', {
|
|
52
|
+
'Action': 'CreateWorkspace',
|
|
53
|
+
'Version': '2021-03-04'
|
|
54
|
+
}, {}, {}),
|
|
50
55
|
'CreateDataModel':
|
|
51
56
|
ApiInfo('POST', '/', {
|
|
52
57
|
'Action': 'CreateDataModel',
|
|
@@ -127,12 +132,74 @@ class BioOsService(Service):
|
|
|
127
132
|
'Action': 'DeleteWorkflow',
|
|
128
133
|
'Version': '2021-03-04'
|
|
129
134
|
}, {}, {}),
|
|
135
|
+
'BindClusterToWorkspace':
|
|
136
|
+
ApiInfo('POST', '/', {
|
|
137
|
+
'Action': 'BindClusterToWorkspace',
|
|
138
|
+
'Version': '2021-03-04'
|
|
139
|
+
}, {}, {}),
|
|
140
|
+
'ListWebappInstances':
|
|
141
|
+
ApiInfo('POST', '/', {
|
|
142
|
+
'Action': 'ListWebappInstances',
|
|
143
|
+
'Version': '2021-03-04'
|
|
144
|
+
}, {}, {}),
|
|
145
|
+
'CreateWebappInstance':
|
|
146
|
+
ApiInfo('POST', '/', {
|
|
147
|
+
'Action': 'CreateWebappInstance',
|
|
148
|
+
'Version': '2021-03-04'
|
|
149
|
+
}, {}, {}),
|
|
150
|
+
'CheckCreateWebappInstance':
|
|
151
|
+
ApiInfo('POST', '/', {
|
|
152
|
+
'Action': 'CheckCreateWebappInstance',
|
|
153
|
+
'Version': '2021-03-04'
|
|
154
|
+
}, {}, {}),
|
|
155
|
+
'DeleteWebappInstance':
|
|
156
|
+
ApiInfo('POST', '/', {
|
|
157
|
+
'Action': 'DeleteWebappInstance',
|
|
158
|
+
'Version': '2021-03-04'
|
|
159
|
+
}, {}, {}),
|
|
160
|
+
'StartWebappInstance':
|
|
161
|
+
ApiInfo('POST', '/', {
|
|
162
|
+
'Action': 'StartWebappInstance',
|
|
163
|
+
'Version': '2021-03-04'
|
|
164
|
+
}, {}, {}),
|
|
165
|
+
'StopWebappInstance':
|
|
166
|
+
ApiInfo('POST', '/', {
|
|
167
|
+
'Action': 'StopWebappInstance',
|
|
168
|
+
'Version': '2021-03-04'
|
|
169
|
+
}, {}, {}),
|
|
170
|
+
'ListWebappInstanceEvents':
|
|
171
|
+
ApiInfo('POST', '/', {
|
|
172
|
+
'Action': 'ListWebappInstanceEvents',
|
|
173
|
+
'Version': '2021-03-04'
|
|
174
|
+
}, {}, {}),
|
|
175
|
+
'CommitIESImage':
|
|
176
|
+
ApiInfo('POST', '/', {
|
|
177
|
+
'Action': 'CommitIESImage',
|
|
178
|
+
'Version': '2021-03-04'
|
|
179
|
+
}, {}, {}),
|
|
180
|
+
'ExportWorkspaceV2':
|
|
181
|
+
ApiInfo('POST', '/', {
|
|
182
|
+
'Action': 'ExportWorkspaceV2',
|
|
183
|
+
'Version': '2021-03-04'
|
|
184
|
+
}, {}, {}),
|
|
185
|
+
'ListSchemas':
|
|
186
|
+
ApiInfo('POST', '/', {
|
|
187
|
+
'Action': 'ListSchemas',
|
|
188
|
+
'Version': '2021-03-04'
|
|
189
|
+
}, {}, {}),
|
|
190
|
+
'GetExportWorkspacePreSignedURL':
|
|
191
|
+
ApiInfo('POST', '/', {
|
|
192
|
+
'Action': 'GetExportWorkspacePreSignedURL',
|
|
193
|
+
'Version': '2021-03-04'
|
|
194
|
+
}, {}, {}),
|
|
130
195
|
}
|
|
131
196
|
return api_info
|
|
132
197
|
|
|
133
198
|
def list_workspaces(self, params): # 以下各方法的params需要在外部使用时构建
|
|
134
199
|
return self.__request('ListWorkspaces', params)
|
|
135
200
|
|
|
201
|
+
def create_workspace(self, params):
|
|
202
|
+
return self.__request('CreateWorkspace', params)
|
|
136
203
|
def create_data_model(self, params):
|
|
137
204
|
return self.__request('CreateDataModel', params)
|
|
138
205
|
|
|
@@ -181,6 +248,42 @@ class BioOsService(Service):
|
|
|
181
248
|
def delete_workflow(self, params):
|
|
182
249
|
return self.__request("DeleteWorkflow", params)
|
|
183
250
|
|
|
251
|
+
def bind_cluster_to_workspace(self, params):
|
|
252
|
+
return self.__request("BindClusterToWorkspace", params)
|
|
253
|
+
|
|
254
|
+
def list_webinstance_apps(self, params):
|
|
255
|
+
return self.__request('ListWebappInstances', params)
|
|
256
|
+
|
|
257
|
+
def create_webinstance_app(self, params):
|
|
258
|
+
return self.__request('CreateWebappInstance', params)
|
|
259
|
+
|
|
260
|
+
def check_webinstance_app(self, params):
|
|
261
|
+
return self.__request("CheckCreateWebappInstance", params)
|
|
262
|
+
|
|
263
|
+
def delete_webinstance_app(self, params):
|
|
264
|
+
return self.__request("DeleteWebappInstance", params)
|
|
265
|
+
|
|
266
|
+
def start_webinstance_app(self, params):
|
|
267
|
+
return self.__request("StartWebappInstance", params)
|
|
268
|
+
|
|
269
|
+
def stop_webinstance_app(self, params):
|
|
270
|
+
return self.__request("StopWebappInstance", params)
|
|
271
|
+
|
|
272
|
+
def list_webinstance_events(self, params):
|
|
273
|
+
return self.__request("ListWebappInstanceEvents", params)
|
|
274
|
+
|
|
275
|
+
def commit_ies_image(self, params):
|
|
276
|
+
return self.__request("CommitIESImage", params)
|
|
277
|
+
|
|
278
|
+
def export_workspace_v2(self, params):
|
|
279
|
+
return self.__request("ExportWorkspaceV2", params)
|
|
280
|
+
|
|
281
|
+
def list_schemas(self, params):
|
|
282
|
+
return self.__request("ListSchemas", params)
|
|
283
|
+
|
|
284
|
+
def get_export_workspace_presigned_url(self, params):
|
|
285
|
+
return self.__request("GetExportWorkspacePreSignedURL", params)
|
|
286
|
+
|
|
184
287
|
def __request(self, action, params):
|
|
185
288
|
res = self.json(
|
|
186
289
|
action, dict(),
|