pybioos 0.0.15__py3-none-any.whl → 0.0.17__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.
Potentially problematic release.
This version of pybioos might be problematic. Click here for more details.
- bioos/__about__.py +1 -1
- bioos/bioos.py +9 -6
- bioos/bioos_workflow.py +31 -7
- 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/workflows.py +188 -5
- bioos/workflow_info.py +207 -0
- {pybioos-0.0.15.dist-info → pybioos-0.0.17.dist-info}/METADATA +1 -1
- {pybioos-0.0.15.dist-info → pybioos-0.0.17.dist-info}/RECORD +16 -15
- {pybioos-0.0.15.dist-info → pybioos-0.0.17.dist-info}/LICENSE +0 -0
- {pybioos-0.0.15.dist-info → pybioos-0.0.17.dist-info}/WHEEL +0 -0
- {pybioos-0.0.15.dist-info → pybioos-0.0.17.dist-info}/entry_points.txt +0 -0
- {pybioos-0.0.15.dist-info → pybioos-0.0.17.dist-info}/top_level.txt +0 -0
bioos/__about__.py
CHANGED
bioos/bioos.py
CHANGED
|
@@ -20,9 +20,9 @@ def status() -> Config.LoginInfo:
|
|
|
20
20
|
return Config.login_info()
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def login(
|
|
24
|
-
access_key: str,
|
|
23
|
+
def login(access_key: str,
|
|
25
24
|
secret_key: str,
|
|
25
|
+
endpoint: str = None,
|
|
26
26
|
region: str = REGION_CN_NORTH1) -> bool:
|
|
27
27
|
"""Login to the given endpoint using specified account and password.
|
|
28
28
|
|
|
@@ -35,14 +35,16 @@ def login(endpoint: str,
|
|
|
35
35
|
*Example*:
|
|
36
36
|
::
|
|
37
37
|
|
|
38
|
-
bioos.login(
|
|
38
|
+
bioos.login(access_key="xxxxxxxx", secret_key="xxxxxxxx")
|
|
39
|
+
# or specify endpoint explicitly:
|
|
40
|
+
bioos.login(access_key="xxxxxxxx", secret_key="xxxxxxxx", endpoint="https://cloud.xxxxx.xxx.cn")
|
|
39
41
|
|
|
40
|
-
:param endpoint: The environment to be logged in
|
|
41
|
-
:type endpoint: str
|
|
42
42
|
:param access_key: The specified account's access key
|
|
43
43
|
:type access_key: str
|
|
44
44
|
:param secret_key: Corresponding secret key of the access key
|
|
45
45
|
:type secret_key: str
|
|
46
|
+
:param endpoint: The environment to be logged in (optional, defaults to Config._endpoint)
|
|
47
|
+
:type endpoint: str
|
|
46
48
|
:param region: The region to be logged in
|
|
47
49
|
:type region: str
|
|
48
50
|
:return: Login result
|
|
@@ -50,7 +52,8 @@ def login(endpoint: str,
|
|
|
50
52
|
"""
|
|
51
53
|
Config.set_access_key(access_key)
|
|
52
54
|
Config.set_secret_key(secret_key)
|
|
53
|
-
|
|
55
|
+
if endpoint is not None:
|
|
56
|
+
Config.set_endpoint(endpoint)
|
|
54
57
|
Config.set_region(region)
|
|
55
58
|
return Config.login_info().login_status == "Already logged in"
|
|
56
59
|
|
bioos/bioos_workflow.py
CHANGED
|
@@ -4,12 +4,25 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
import re
|
|
6
6
|
import time
|
|
7
|
+
from typing import Dict, Any
|
|
7
8
|
|
|
8
9
|
import pandas as pd
|
|
9
10
|
|
|
10
11
|
from bioos import bioos
|
|
11
12
|
from bioos.errors import NotFoundError, ParameterError
|
|
12
|
-
|
|
13
|
+
from bioos.config import DEFAULT_ENDPOINT
|
|
14
|
+
|
|
15
|
+
def uniquify_columns(cols: list[str]) -> list[str]:
|
|
16
|
+
seen, out = {}, []
|
|
17
|
+
for col in cols:
|
|
18
|
+
base = col.split(".")[-1]
|
|
19
|
+
if base not in seen:
|
|
20
|
+
seen[base] = 0
|
|
21
|
+
out.append(base)
|
|
22
|
+
else:
|
|
23
|
+
seen[base] += 1
|
|
24
|
+
out.append(f"{base}_{seen[base]}") # fastq → fastq_1 → fastq_2
|
|
25
|
+
return out
|
|
13
26
|
|
|
14
27
|
def recognize_files_from_input_json(workflow_input_json: dict) -> dict:
|
|
15
28
|
putative_files = {}
|
|
@@ -168,6 +181,9 @@ class Bioos_workflow:
|
|
|
168
181
|
force_reupload: bool = False):
|
|
169
182
|
if not os.path.isfile(input_json_file):
|
|
170
183
|
raise ParameterError('Input_json_file is not found.')
|
|
184
|
+
#给每一个data_model加一个uuid,保证不重复
|
|
185
|
+
if data_model_name == "dm":
|
|
186
|
+
data_model_name = f"dm_{int(time.time())}"
|
|
171
187
|
|
|
172
188
|
input_json = json.load(open(input_json_file))
|
|
173
189
|
self.logger.info("Load json input successfully.")
|
|
@@ -231,7 +247,8 @@ class Bioos_workflow:
|
|
|
231
247
|
df[id_col] = [f"tmp_{x}" for x in list(range(len(df)))]
|
|
232
248
|
df = df.reindex(columns=columns)
|
|
233
249
|
columns = [key.split(".")[-1] for key in df.columns.to_list()]
|
|
234
|
-
df.columns = pd.Index(columns)
|
|
250
|
+
#df.columns = pd.Index(columns)
|
|
251
|
+
df.columns = pd.Index(uniquify_columns(df.columns.to_list()))
|
|
235
252
|
|
|
236
253
|
# write data models
|
|
237
254
|
self.ws.data_models.write({data_model_name: df.applymap(str)},
|
|
@@ -243,6 +260,7 @@ class Bioos_workflow:
|
|
|
243
260
|
for key, _ in unupdate_dict.items():
|
|
244
261
|
unupdate_dict[key] = f'this.{key.split(".")[-1]}'
|
|
245
262
|
|
|
263
|
+
|
|
246
264
|
self.params_submit["inputs"] = json.dumps(unupdate_dict)
|
|
247
265
|
self.params_submit["data_model_name"] = data_model_name
|
|
248
266
|
self.params_submit["row_ids"] = df[id_col].to_list()
|
|
@@ -254,7 +272,7 @@ class Bioos_workflow:
|
|
|
254
272
|
self.logger.info("Build params dict successfully.")
|
|
255
273
|
return self.params_submit
|
|
256
274
|
|
|
257
|
-
def postprocess(self, download=False):
|
|
275
|
+
def postprocess(self, download=False,download_dir="."):
|
|
258
276
|
# 假设全部执行完毕
|
|
259
277
|
# 对运行完成的目录进行下载
|
|
260
278
|
# 证实bioos包只能对文件的list进行下载,不支持文件夹
|
|
@@ -268,9 +286,10 @@ class Bioos_workflow:
|
|
|
268
286
|
|
|
269
287
|
files.append(file)
|
|
270
288
|
|
|
271
|
-
if download:
|
|
289
|
+
if download and files:
|
|
290
|
+
os.makedirs(download_dir, exist_ok=True)
|
|
272
291
|
try:
|
|
273
|
-
self.ws.files.download(files,
|
|
292
|
+
self.ws.files.download(files, download_dir, flatten=False)
|
|
274
293
|
except Exception as e:
|
|
275
294
|
print(f'Some file can not download. \n {e}')
|
|
276
295
|
|
|
@@ -305,7 +324,7 @@ def bioos_workflow():
|
|
|
305
324
|
parser.add_argument("--endpoint",
|
|
306
325
|
type=str,
|
|
307
326
|
help="Bio-OS instance platform endpoint",
|
|
308
|
-
default=
|
|
327
|
+
default=DEFAULT_ENDPOINT)
|
|
309
328
|
parser.add_argument(
|
|
310
329
|
"--ak",
|
|
311
330
|
type=str,
|
|
@@ -357,6 +376,11 @@ def bioos_workflow():
|
|
|
357
376
|
"--download_results",
|
|
358
377
|
action='store_true',
|
|
359
378
|
help="Download the submission run result files to local current path.")
|
|
379
|
+
parser.add_argument(
|
|
380
|
+
"--download_dir",
|
|
381
|
+
type=str,
|
|
382
|
+
default=".",
|
|
383
|
+
help="本地保存下载结果的目录(默认当前目录)")
|
|
360
384
|
|
|
361
385
|
parsed_args = parser.parse_args()
|
|
362
386
|
|
|
@@ -395,5 +419,5 @@ def bioos_workflow():
|
|
|
395
419
|
print(bw.runs)
|
|
396
420
|
|
|
397
421
|
bw.logger.info("Start to postprocess.")
|
|
398
|
-
bw.postprocess(download=parsed_args.download_results)
|
|
422
|
+
bw.postprocess(download=parsed_args.download_results,download_dir = parsed_args.download_dir)
|
|
399
423
|
bw.logger.info("Postprocess finished.")
|
bioos/bw_import.py
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# coding: utf-8
|
|
3
|
-
|
|
4
3
|
import argparse
|
|
5
4
|
import logging
|
|
6
5
|
import os
|
|
7
6
|
import sys
|
|
8
7
|
import time
|
|
9
|
-
|
|
10
8
|
from bioos import bioos
|
|
11
|
-
from bioos.config import Config
|
|
9
|
+
from bioos.config import Config, DEFAULT_ENDPOINT
|
|
12
10
|
from bioos.resource.workflows import WorkflowResource
|
|
13
|
-
|
|
14
|
-
|
|
15
11
|
def get_logger():
|
|
16
12
|
"""Setup logger"""
|
|
17
13
|
logger = logging.getLogger('bw_import')
|
|
@@ -23,14 +19,11 @@ def get_logger():
|
|
|
23
19
|
logger.addHandler(handler)
|
|
24
20
|
logger.setLevel(logging.INFO)
|
|
25
21
|
return logger
|
|
26
|
-
|
|
27
|
-
|
|
28
22
|
def bioos_workflow_import():
|
|
29
23
|
"""Command line entry point"""
|
|
30
24
|
parser = argparse.ArgumentParser(
|
|
31
25
|
description='Bio-OS Workflow Import Tool',
|
|
32
26
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
33
|
-
|
|
34
27
|
# 必需参数
|
|
35
28
|
parser.add_argument(
|
|
36
29
|
'--ak',
|
|
@@ -49,8 +42,10 @@ def bioos_workflow_import():
|
|
|
49
42
|
parser.add_argument('--workflow_source',
|
|
50
43
|
required=True,
|
|
51
44
|
help='Local WDL file path or git repository URL')
|
|
52
|
-
|
|
53
45
|
# 可选参数
|
|
46
|
+
parser.add_argument('--endpoint',
|
|
47
|
+
help='Bio-OS instance platform endpoint',
|
|
48
|
+
default=DEFAULT_ENDPOINT)
|
|
54
49
|
parser.add_argument('--workflow_desc',
|
|
55
50
|
help='Description for the workflow',
|
|
56
51
|
default='')
|
|
@@ -67,16 +62,13 @@ def bioos_workflow_import():
|
|
|
67
62
|
type=int,
|
|
68
63
|
default=60,
|
|
69
64
|
help='Time interval in seconds for checking workflow status')
|
|
70
|
-
|
|
71
65
|
args = parser.parse_args()
|
|
72
66
|
logger = get_logger()
|
|
73
|
-
|
|
74
67
|
try:
|
|
75
68
|
# 配置Bio-OS
|
|
76
69
|
Config.set_access_key(args.ak)
|
|
77
70
|
Config.set_secret_key(args.sk)
|
|
78
|
-
Config.set_endpoint(
|
|
79
|
-
|
|
71
|
+
Config.set_endpoint(args.endpoint)
|
|
80
72
|
# 获取workspace ID
|
|
81
73
|
workspaces = bioos.list_workspaces()
|
|
82
74
|
workspace_info = workspaces.query(f"Name=='{args.workspace_name}'")
|
|
@@ -84,10 +76,8 @@ def bioos_workflow_import():
|
|
|
84
76
|
logger.error(f"Workspace {args.workspace_name} not found")
|
|
85
77
|
sys.exit(1)
|
|
86
78
|
workspace_id = workspace_info["ID"].iloc[0]
|
|
87
|
-
|
|
88
79
|
# 创建WorkflowResource实例
|
|
89
80
|
workflow_resource = WorkflowResource(workspace_id)
|
|
90
|
-
|
|
91
81
|
# 导入workflow
|
|
92
82
|
try:
|
|
93
83
|
result = workflow_resource.import_workflow(
|
|
@@ -99,19 +89,15 @@ def bioos_workflow_import():
|
|
|
99
89
|
logger.info(
|
|
100
90
|
f"Successfully uploaded workflow: {result}, validating..., please wait..."
|
|
101
91
|
)
|
|
102
|
-
|
|
103
92
|
# 如果设置了monitor参数,则监控工作流状态
|
|
104
93
|
if args.monitor:
|
|
105
94
|
max_retries = 10 # 最大重试次数
|
|
106
95
|
retry_count = 0
|
|
107
|
-
|
|
108
96
|
while retry_count < max_retries:
|
|
109
97
|
df = workflow_resource.list()
|
|
110
98
|
workflow_info = df[df.Name == args.workflow_name]
|
|
111
|
-
|
|
112
99
|
if len(workflow_info) == 1:
|
|
113
100
|
status = workflow_info.iloc[0]["Status"]["Phase"]
|
|
114
|
-
|
|
115
101
|
if status == "Succeeded":
|
|
116
102
|
logger.info(
|
|
117
103
|
f"Workflow {args.workflow_name} validated successfully"
|
|
@@ -138,7 +124,6 @@ def bioos_workflow_import():
|
|
|
138
124
|
f"Workflow {args.workflow_name} not found after import"
|
|
139
125
|
)
|
|
140
126
|
sys.exit(1)
|
|
141
|
-
|
|
142
127
|
logger.error(
|
|
143
128
|
f"Workflow validation timeout after {max_retries} retries")
|
|
144
129
|
sys.exit(1)
|
|
@@ -148,15 +133,11 @@ def bioos_workflow_import():
|
|
|
148
133
|
f"Workflow {args.workflow_name} is still validating, {result}, please wait and check the status later."
|
|
149
134
|
)
|
|
150
135
|
sys.exit(0)
|
|
151
|
-
|
|
152
136
|
except Exception as e:
|
|
153
137
|
logger.error(f"Failed to import workflow: {str(e)}")
|
|
154
138
|
sys.exit(1)
|
|
155
|
-
|
|
156
139
|
except Exception as e:
|
|
157
140
|
logger.error(f"Error: {str(e)}")
|
|
158
141
|
sys.exit(1)
|
|
159
|
-
|
|
160
|
-
|
|
161
142
|
if __name__ == '__main__':
|
|
162
143
|
bioos_workflow_import()
|
bioos/bw_import_status_check.py
CHANGED
|
@@ -6,7 +6,7 @@ import logging
|
|
|
6
6
|
import sys
|
|
7
7
|
|
|
8
8
|
from bioos import bioos
|
|
9
|
-
from bioos.config import Config
|
|
9
|
+
from bioos.config import Config, DEFAULT_ENDPOINT
|
|
10
10
|
from bioos.resource.workflows import WorkflowResource
|
|
11
11
|
|
|
12
12
|
|
|
@@ -44,6 +44,11 @@ def bioos_workflow_status_check():
|
|
|
44
44
|
parser.add_argument('--workflow_id',
|
|
45
45
|
required=True,
|
|
46
46
|
help='ID of the workflow to check')
|
|
47
|
+
|
|
48
|
+
# 可选参数
|
|
49
|
+
parser.add_argument('--endpoint',
|
|
50
|
+
help='Bio-OS instance platform endpoint',
|
|
51
|
+
default=DEFAULT_ENDPOINT)
|
|
47
52
|
|
|
48
53
|
args = parser.parse_args()
|
|
49
54
|
logger = get_logger()
|
|
@@ -52,7 +57,7 @@ def bioos_workflow_status_check():
|
|
|
52
57
|
# 配置Bio-OS
|
|
53
58
|
Config.set_access_key(args.ak)
|
|
54
59
|
Config.set_secret_key(args.sk)
|
|
55
|
-
Config.set_endpoint(
|
|
60
|
+
Config.set_endpoint(args.endpoint)
|
|
56
61
|
|
|
57
62
|
# 获取workspace ID
|
|
58
63
|
workspaces = bioos.list_workspaces()
|
bioos/bw_status_check.py
CHANGED
|
@@ -6,7 +6,7 @@ import logging
|
|
|
6
6
|
import sys
|
|
7
7
|
|
|
8
8
|
from bioos import bioos
|
|
9
|
-
from bioos.config import Config
|
|
9
|
+
from bioos.config import Config, DEFAULT_ENDPOINT
|
|
10
10
|
from bioos.resource.workflows import WorkflowResource
|
|
11
11
|
|
|
12
12
|
|
|
@@ -44,6 +44,11 @@ def bioos_workflow_status_check():
|
|
|
44
44
|
parser.add_argument('--submission_id',
|
|
45
45
|
required=True,
|
|
46
46
|
help='ID of the submission to check')
|
|
47
|
+
|
|
48
|
+
# 可选参数
|
|
49
|
+
parser.add_argument('--endpoint',
|
|
50
|
+
help='Bio-OS instance platform endpoint',
|
|
51
|
+
default=DEFAULT_ENDPOINT)
|
|
47
52
|
|
|
48
53
|
args = parser.parse_args()
|
|
49
54
|
logger = get_logger()
|
|
@@ -52,7 +57,7 @@ def bioos_workflow_status_check():
|
|
|
52
57
|
# 配置Bio-OS
|
|
53
58
|
Config.set_access_key(args.ak)
|
|
54
59
|
Config.set_secret_key(args.sk)
|
|
55
|
-
Config.set_endpoint(
|
|
60
|
+
Config.set_endpoint(args.endpoint)
|
|
56
61
|
|
|
57
62
|
# 获取workspace ID
|
|
58
63
|
workspaces = bioos.list_workspaces()
|
bioos/config.py
CHANGED
|
@@ -7,6 +7,9 @@ from bioos.errors import ConfigurationError
|
|
|
7
7
|
from bioos.log import PyLogger
|
|
8
8
|
from bioos.service.BioOsService import BioOsService
|
|
9
9
|
|
|
10
|
+
# 默认的 Bio-OS endpoint
|
|
11
|
+
DEFAULT_ENDPOINT = "https://bio-top.miracle.ac.cn"
|
|
12
|
+
|
|
10
13
|
LOGIN_STATUS = Literal['Already logged in', 'Not logged in']
|
|
11
14
|
|
|
12
15
|
|
|
@@ -14,7 +17,7 @@ class Config:
|
|
|
14
17
|
_service: BioOsService = None
|
|
15
18
|
_access_key: str = os.environ.get('VOLC_ACCESSKEY')
|
|
16
19
|
_secret_key: str = os.environ.get('VOLC_SECRETKEY')
|
|
17
|
-
_endpoint: str = os.environ.get('BIOOS_ENDPOINT')
|
|
20
|
+
_endpoint: str = os.environ.get('BIOOS_ENDPOINT', DEFAULT_ENDPOINT)
|
|
18
21
|
_region: str = REGION_CN_NORTH1
|
|
19
22
|
Logger = PyLogger() # 这里是把类赋给了Logger变量
|
|
20
23
|
|
bioos/get_submission_logs.py
CHANGED
|
@@ -7,7 +7,7 @@ import os
|
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
9
|
from bioos import bioos
|
|
10
|
-
from bioos.config import Config
|
|
10
|
+
from bioos.config import Config, DEFAULT_ENDPOINT
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def get_logger():
|
|
@@ -48,6 +48,11 @@ def get_submission_logs():
|
|
|
48
48
|
'--output_dir',
|
|
49
49
|
default='.',
|
|
50
50
|
help='Local directory to save the logs (default: current directory)')
|
|
51
|
+
|
|
52
|
+
# 可选参数
|
|
53
|
+
parser.add_argument('--endpoint',
|
|
54
|
+
help='Bio-OS instance platform endpoint',
|
|
55
|
+
default=DEFAULT_ENDPOINT)
|
|
51
56
|
|
|
52
57
|
args = parser.parse_args()
|
|
53
58
|
logger = get_logger()
|
|
@@ -56,7 +61,7 @@ def get_submission_logs():
|
|
|
56
61
|
# 配置Bio-OS
|
|
57
62
|
Config.set_access_key(args.ak)
|
|
58
63
|
Config.set_secret_key(args.sk)
|
|
59
|
-
Config.set_endpoint(
|
|
64
|
+
Config.set_endpoint(args.endpoint)
|
|
60
65
|
|
|
61
66
|
# 获取workspace ID
|
|
62
67
|
workspaces = bioos.list_workspaces()
|
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
|
|
@@ -548,17 +548,62 @@ class WorkflowResource(metaclass=SingletonType):
|
|
|
548
548
|
|
|
549
549
|
|
|
550
550
|
class Workflow(metaclass=SingletonType):
|
|
551
|
+
"""Represents a workflow in Bio-OS.
|
|
552
|
+
|
|
553
|
+
This class encapsulates all the information and operations related to a workflow,
|
|
554
|
+
including its metadata, inputs, outputs, and execution capabilities.
|
|
555
|
+
"""
|
|
551
556
|
|
|
552
557
|
def __init__(self,
|
|
553
558
|
name: str,
|
|
554
559
|
workspace_id: str,
|
|
555
560
|
bucket: str,
|
|
556
561
|
check: bool = False):
|
|
562
|
+
"""Initialize a workflow instance.
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
name: The name of the workflow
|
|
566
|
+
workspace_id: The ID of the workspace containing this workflow
|
|
567
|
+
bucket: The S3 bucket associated with this workflow
|
|
568
|
+
check: Whether to check the workflow existence immediately
|
|
569
|
+
"""
|
|
557
570
|
self.name = name
|
|
558
571
|
self.workspace_id = workspace_id
|
|
559
572
|
self.bucket = bucket
|
|
573
|
+
self._description: str = ""
|
|
574
|
+
self._create_time: int = 0
|
|
575
|
+
self._update_time: int = 0
|
|
576
|
+
self._language: str = "WDL"
|
|
577
|
+
self._source: str = ""
|
|
578
|
+
self._tag: str = ""
|
|
579
|
+
self._token: Optional[str] = None
|
|
580
|
+
self._main_workflow_path: str = ""
|
|
581
|
+
self._status: Dict[str, Optional[str]] = {"Phase": "", "Message": None}
|
|
582
|
+
self._inputs: List[Dict[str, Any]] = []
|
|
583
|
+
self._outputs: List[Dict[str, Any]] = []
|
|
584
|
+
self._owner_name: str = ""
|
|
585
|
+
self._graph: str = ""
|
|
586
|
+
self._source_type: str = ""
|
|
587
|
+
|
|
560
588
|
if check:
|
|
561
|
-
self.
|
|
589
|
+
self.sync()
|
|
590
|
+
|
|
591
|
+
def __repr__(self):
|
|
592
|
+
"""Return a string representation of the workflow."""
|
|
593
|
+
info_dict = dict_str({
|
|
594
|
+
"id": self.id,
|
|
595
|
+
"name": self.name,
|
|
596
|
+
"description": self.description,
|
|
597
|
+
"language": self.language,
|
|
598
|
+
"source": self.source,
|
|
599
|
+
"tag": self.tag,
|
|
600
|
+
"main_workflow_path": self.main_workflow_path,
|
|
601
|
+
"status": self.status,
|
|
602
|
+
"owner_name": self.owner_name,
|
|
603
|
+
"create_time": self.create_time,
|
|
604
|
+
"update_time": self.update_time
|
|
605
|
+
})
|
|
606
|
+
return f"WorkflowInfo:\n{info_dict}"
|
|
562
607
|
|
|
563
608
|
@property
|
|
564
609
|
@cached(cache=TTLCache(maxsize=100, ttl=1))
|
|
@@ -574,6 +619,140 @@ class Workflow(metaclass=SingletonType):
|
|
|
574
619
|
raise ParameterError("name")
|
|
575
620
|
return res["ID"].iloc[0]
|
|
576
621
|
|
|
622
|
+
@property
|
|
623
|
+
def description(self) -> str:
|
|
624
|
+
"""Get the workflow description."""
|
|
625
|
+
if not self._description:
|
|
626
|
+
self.sync()
|
|
627
|
+
return self._description
|
|
628
|
+
|
|
629
|
+
@property
|
|
630
|
+
def create_time(self) -> int:
|
|
631
|
+
"""Get the workflow creation timestamp."""
|
|
632
|
+
if not self._create_time:
|
|
633
|
+
self.sync()
|
|
634
|
+
return self._create_time
|
|
635
|
+
|
|
636
|
+
@property
|
|
637
|
+
def update_time(self) -> int:
|
|
638
|
+
"""Get the workflow last update timestamp."""
|
|
639
|
+
if not self._update_time:
|
|
640
|
+
self.sync()
|
|
641
|
+
return self._update_time
|
|
642
|
+
|
|
643
|
+
@property
|
|
644
|
+
def language(self) -> str:
|
|
645
|
+
"""Get the workflow language (e.g., WDL)."""
|
|
646
|
+
if not self._language:
|
|
647
|
+
self.sync()
|
|
648
|
+
return self._language
|
|
649
|
+
|
|
650
|
+
@property
|
|
651
|
+
def source(self) -> str:
|
|
652
|
+
"""Get the workflow source location."""
|
|
653
|
+
if not self._source:
|
|
654
|
+
self.sync()
|
|
655
|
+
return self._source
|
|
656
|
+
|
|
657
|
+
@property
|
|
658
|
+
def tag(self) -> str:
|
|
659
|
+
"""Get the workflow version tag."""
|
|
660
|
+
if not self._tag:
|
|
661
|
+
self.sync()
|
|
662
|
+
return self._tag
|
|
663
|
+
|
|
664
|
+
@property
|
|
665
|
+
def token(self) -> Optional[str]:
|
|
666
|
+
"""Get the workflow access token if any."""
|
|
667
|
+
if not self._token:
|
|
668
|
+
self.sync()
|
|
669
|
+
return self._token
|
|
670
|
+
|
|
671
|
+
@property
|
|
672
|
+
def main_workflow_path(self) -> str:
|
|
673
|
+
"""Get the main workflow file path."""
|
|
674
|
+
if not self._main_workflow_path:
|
|
675
|
+
self.sync()
|
|
676
|
+
return self._main_workflow_path
|
|
677
|
+
|
|
678
|
+
@property
|
|
679
|
+
def status(self) -> Dict[str, Optional[str]]:
|
|
680
|
+
"""Get the workflow status information."""
|
|
681
|
+
if not self._status["Phase"]:
|
|
682
|
+
self.sync()
|
|
683
|
+
return self._status
|
|
684
|
+
@property
|
|
685
|
+
def inputs(self) -> List[Dict[str, Any]]:
|
|
686
|
+
"""Get the workflow input parameters."""
|
|
687
|
+
if not self._inputs:
|
|
688
|
+
self.sync()
|
|
689
|
+
return self._inputs
|
|
690
|
+
|
|
691
|
+
@property
|
|
692
|
+
def outputs(self) -> List[Dict[str, Any]]:
|
|
693
|
+
"""Get the workflow output parameters."""
|
|
694
|
+
if not self._outputs:
|
|
695
|
+
self.sync()
|
|
696
|
+
return self._outputs
|
|
697
|
+
|
|
698
|
+
@property
|
|
699
|
+
def owner_name(self) -> str:
|
|
700
|
+
"""Get the workflow owner's name."""
|
|
701
|
+
if not self._owner_name:
|
|
702
|
+
self.sync()
|
|
703
|
+
return self._owner_name
|
|
704
|
+
|
|
705
|
+
@property
|
|
706
|
+
def graph(self) -> str:
|
|
707
|
+
"""Get the workflow graph representation."""
|
|
708
|
+
if not self._graph:
|
|
709
|
+
self.sync()
|
|
710
|
+
return self._graph
|
|
711
|
+
|
|
712
|
+
@property
|
|
713
|
+
def source_type(self) -> str:
|
|
714
|
+
"""Get the workflow source type."""
|
|
715
|
+
if not self._source_type:
|
|
716
|
+
self.sync()
|
|
717
|
+
return self._source_type
|
|
718
|
+
|
|
719
|
+
@cached(cache=TTLCache(maxsize=100, ttl=1))
|
|
720
|
+
def sync(self):
|
|
721
|
+
"""Synchronize workflow information with the remote service."""
|
|
722
|
+
res = WorkflowResource(self.workspace_id). \
|
|
723
|
+
list().query(f"Name=='{self.name}'")
|
|
724
|
+
if res.empty:
|
|
725
|
+
raise ParameterError("name")
|
|
726
|
+
|
|
727
|
+
# Get detailed workflow information
|
|
728
|
+
params = {
|
|
729
|
+
'WorkspaceID': self.workspace_id,
|
|
730
|
+
'Filter': {
|
|
731
|
+
'IDs': [res["ID"].iloc[0]]
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
workflows = Config.service().list_workflows(params).get('Items')
|
|
735
|
+
if len(workflows) != 1:
|
|
736
|
+
raise NotFoundError("workflow", self.name)
|
|
737
|
+
|
|
738
|
+
detail = workflows[0]
|
|
739
|
+
|
|
740
|
+
# Update all properties
|
|
741
|
+
self._description = detail.get("Description", "")
|
|
742
|
+
self._create_time = detail.get("CreateTime", 0)
|
|
743
|
+
self._update_time = detail.get("UpdateTime", 0)
|
|
744
|
+
self._language = detail.get("Language", "WDL")
|
|
745
|
+
self._source = detail.get("Source", "")
|
|
746
|
+
self._tag = detail.get("Tag", "")
|
|
747
|
+
self._token = detail.get("Token")
|
|
748
|
+
self._main_workflow_path = detail.get("MainWorkflowPath", "")
|
|
749
|
+
self._status = detail.get("Status", {"Phase": "", "Message": None})
|
|
750
|
+
self._inputs = detail.get("Inputs", [])
|
|
751
|
+
self._outputs = detail.get("Outputs", [])
|
|
752
|
+
self._owner_name = detail.get("OwnerName", "")
|
|
753
|
+
self._graph = detail.get("Graph", "")
|
|
754
|
+
self._source_type = detail.get("SourceType", "")
|
|
755
|
+
|
|
577
756
|
@property
|
|
578
757
|
@cached(cache=TTLCache(maxsize=100, ttl=1))
|
|
579
758
|
def get_cluster(self):
|
|
@@ -594,11 +773,14 @@ class Workflow(metaclass=SingletonType):
|
|
|
594
773
|
return info['ID']
|
|
595
774
|
raise NotFoundError("cluster", "workflow")
|
|
596
775
|
|
|
597
|
-
def query_data_model_id(self, name: str) ->
|
|
776
|
+
def query_data_model_id(self, name: str) -> str:
|
|
598
777
|
"""Gets the id of given data_models among those accessible
|
|
599
778
|
|
|
600
|
-
:
|
|
601
|
-
|
|
779
|
+
Args:
|
|
780
|
+
name: The name of the data model
|
|
781
|
+
|
|
782
|
+
Returns:
|
|
783
|
+
str: The ID of the data model, or empty string if not found
|
|
602
784
|
"""
|
|
603
785
|
res = DataModelResource(self.workspace_id).list(). \
|
|
604
786
|
query(f"Name=='{name}'")
|
|
@@ -682,3 +864,4 @@ class Workflow(metaclass=SingletonType):
|
|
|
682
864
|
submission_id = Config.service().create_submission(params).get("ID")
|
|
683
865
|
|
|
684
866
|
return Submission(self.workspace_id, submission_id).runs
|
|
867
|
+
|
bioos/workflow_info.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
from typing import Dict, Any, List, Optional
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
from bioos import bioos
|
|
5
|
+
from bioos.config import Config, DEFAULT_ENDPOINT
|
|
6
|
+
from bioos.errors import NotFoundError
|
|
7
|
+
|
|
8
|
+
class WorkflowInfo:
|
|
9
|
+
"""Bio-OS 工作流信息查询类"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, ak: str, sk: str, endpoint: str = DEFAULT_ENDPOINT):
|
|
12
|
+
"""
|
|
13
|
+
初始化工作流信息查询类
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
ak: Bio-OS 访问密钥
|
|
17
|
+
sk: Bio-OS 私钥
|
|
18
|
+
endpoint: Bio-OS API 端点,默认为 https://bio-top.miracle.ac.cn
|
|
19
|
+
"""
|
|
20
|
+
self.ak = ak
|
|
21
|
+
self.sk = sk
|
|
22
|
+
self.endpoint = endpoint
|
|
23
|
+
# 配置 Bio-OS
|
|
24
|
+
Config.set_access_key(ak)
|
|
25
|
+
Config.set_secret_key(sk)
|
|
26
|
+
Config.set_endpoint(endpoint)
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def _fmt_default(raw: Any) -> str | None:
|
|
30
|
+
"""
|
|
31
|
+
将 Default 字段格式化成人类可读形式:
|
|
32
|
+
- None → None
|
|
33
|
+
- int/float → 123 / 1.23
|
|
34
|
+
- bool → true / false
|
|
35
|
+
- 其余字符串 → "value"(保留双引号)
|
|
36
|
+
"""
|
|
37
|
+
if raw is None:
|
|
38
|
+
return None
|
|
39
|
+
if isinstance(raw, (int, float, bool)):
|
|
40
|
+
return str(raw).lower() # bool 转成 'true' / 'false'
|
|
41
|
+
if isinstance(raw, str):
|
|
42
|
+
lo = raw.lower()
|
|
43
|
+
# 尝试将字符串视为数字或布尔
|
|
44
|
+
if lo in {"true", "false"}:
|
|
45
|
+
return lo
|
|
46
|
+
try:
|
|
47
|
+
int(raw); return raw
|
|
48
|
+
except ValueError:
|
|
49
|
+
pass
|
|
50
|
+
try:
|
|
51
|
+
float(raw); return raw
|
|
52
|
+
except ValueError:
|
|
53
|
+
pass
|
|
54
|
+
return f"\"{raw}\""
|
|
55
|
+
return str(raw)
|
|
56
|
+
|
|
57
|
+
def get_workspace_id(self, workspace_name: str) -> str:
|
|
58
|
+
"""
|
|
59
|
+
获取工作区ID
|
|
60
|
+
Args:
|
|
61
|
+
workspace_name: 工作区名称
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
str: 工作区ID
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
NotFoundError: 未找到指定工作区
|
|
68
|
+
"""
|
|
69
|
+
df = bioos.list_workspaces()
|
|
70
|
+
ser = df[df.Name == workspace_name].ID
|
|
71
|
+
if len(ser) != 1:
|
|
72
|
+
raise NotFoundError("Workspace", workspace_name)
|
|
73
|
+
return ser.to_list()[0]
|
|
74
|
+
|
|
75
|
+
def get_workflow(self, workspace_name: str, workflow_name: str):
|
|
76
|
+
"""
|
|
77
|
+
获取工作流对象
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
workspace_name: 工作区名称
|
|
81
|
+
workflow_name: 工作流名称
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Workflow: 工作流对象
|
|
85
|
+
Raises:
|
|
86
|
+
NotFoundError: 未找到指定工作区或工作流
|
|
87
|
+
"""
|
|
88
|
+
workspace_id = self.get_workspace_id(workspace_name)
|
|
89
|
+
ws = bioos.workspace(workspace_id)
|
|
90
|
+
return ws.workflow(name=workflow_name)
|
|
91
|
+
|
|
92
|
+
def list_workflows(self, workspace_name: str) -> List[Dict[str, Any]]:
|
|
93
|
+
"""
|
|
94
|
+
列出工作区下的所有工作流
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
workspace_name: 工作区名称
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
List[Dict[str, Any]]: 工作流列表
|
|
101
|
+
"""
|
|
102
|
+
workspace_id = self.get_workspace_id(workspace_name)
|
|
103
|
+
ws = bioos.workspace(workspace_id)
|
|
104
|
+
return ws.list_workflows()
|
|
105
|
+
|
|
106
|
+
def get_workflow_inputs(self, workspace_name: str, workflow_name: str) -> Dict[str, str]:
|
|
107
|
+
"""
|
|
108
|
+
获取工作流的输入参数模板
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
workspace_name: 工作区名称
|
|
112
|
+
workflow_name: 工作流名称
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Dict[str, str]: 包含工作流输入参数模板的字典,格式为:
|
|
116
|
+
{
|
|
117
|
+
"param_name": "Type (optional, default = value)", # 对于可选参数
|
|
118
|
+
"param_name": "Type" # 对于必需参数
|
|
119
|
+
}
|
|
120
|
+
其中:
|
|
121
|
+
- Type 为参数类型(如 String, Int, File 等)
|
|
122
|
+
- optional 表示参数为可选
|
|
123
|
+
- value 为默认值(数字和布尔值直接显示,字符串加引号)
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
wf = self.get_workflow(workspace_name, workflow_name)
|
|
127
|
+
result = {}
|
|
128
|
+
|
|
129
|
+
for item in wf.inputs:
|
|
130
|
+
type_str = item.get("Type", "")
|
|
131
|
+
optional = item.get("Optional", False)
|
|
132
|
+
default = self._fmt_default(item.get("Default"))
|
|
133
|
+
|
|
134
|
+
if optional:
|
|
135
|
+
value = f"{type_str} (optional" + (f", default = {default})" if default is not None else ")")
|
|
136
|
+
else:
|
|
137
|
+
value = type_str
|
|
138
|
+
|
|
139
|
+
result[item["Name"]] = value
|
|
140
|
+
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
except NotFoundError as e:
|
|
144
|
+
return {"error": str(e)}
|
|
145
|
+
except Exception as e:
|
|
146
|
+
return {"error": str(e)}
|
|
147
|
+
|
|
148
|
+
def get_workflow_outputs(self, workspace_name: str, workflow_name: str) -> Dict[str, str]:
|
|
149
|
+
"""
|
|
150
|
+
获取工作流的输出参数信息
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
workspace_name: 工作区名称
|
|
154
|
+
workflow_name: 工作流名称
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Dict[str, str]: 包含工作流输出参数的字典
|
|
158
|
+
"""
|
|
159
|
+
try:
|
|
160
|
+
wf = self.get_workflow(workspace_name, workflow_name)
|
|
161
|
+
return {output["Name"]: output["Type"] for output in wf.outputs}
|
|
162
|
+
except NotFoundError as e:
|
|
163
|
+
return {"error": str(e)}
|
|
164
|
+
except Exception as e:
|
|
165
|
+
return {"error": str(e)}
|
|
166
|
+
|
|
167
|
+
def get_workflow_metadata(self, workspace_name: str, workflow_name: str) -> Dict[str, Any]:
|
|
168
|
+
"""
|
|
169
|
+
获取工作流的元数据信息
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
workspace_name: 工作区名称
|
|
173
|
+
workflow_name: 工作流名称
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Dict[str, Any]: 包含工作流元数据的字典,包括:
|
|
177
|
+
- name: 工作流名称
|
|
178
|
+
- description: 工作流描述
|
|
179
|
+
- language: 工作流语言
|
|
180
|
+
- source: 工作流源
|
|
181
|
+
- tag: 版本标签
|
|
182
|
+
- status: 工作流状态
|
|
183
|
+
- owner_name: 所有者
|
|
184
|
+
- create_time: 创建时间
|
|
185
|
+
- update_time: 更新时间
|
|
186
|
+
- main_workflow_path: 主工作流文件路径
|
|
187
|
+
- source_type: 源类型
|
|
188
|
+
"""
|
|
189
|
+
try:
|
|
190
|
+
wf = self.get_workflow(workspace_name, workflow_name)
|
|
191
|
+
return {
|
|
192
|
+
"name": wf.name,
|
|
193
|
+
"description": wf.description,
|
|
194
|
+
"language": wf.language,
|
|
195
|
+
"source": wf.source,
|
|
196
|
+
"tag": wf.tag,
|
|
197
|
+
"status": wf.status,
|
|
198
|
+
"owner_name": wf.owner_name,
|
|
199
|
+
"create_time": wf.create_time,
|
|
200
|
+
"update_time": wf.update_time,
|
|
201
|
+
"main_workflow_path": wf.main_workflow_path,
|
|
202
|
+
"source_type": wf.source_type
|
|
203
|
+
}
|
|
204
|
+
except NotFoundError as e:
|
|
205
|
+
return {"error": str(e)}
|
|
206
|
+
except Exception as e:
|
|
207
|
+
return {"error": str(e)}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
bioos/__about__.py,sha256=
|
|
1
|
+
bioos/__about__.py,sha256=FvqUBfyX_OkxcuTYr1_lcBcEd13i1ew8F8tEpRfcor8,57
|
|
2
2
|
bioos/__init__.py,sha256=4GZKi13lDTD25YBkGakhZyEQZWTER_OWQMNPoH_UM2c,22
|
|
3
|
-
bioos/bioos.py,sha256=
|
|
4
|
-
bioos/bioos_workflow.py,sha256=
|
|
5
|
-
bioos/bw_import.py,sha256=
|
|
6
|
-
bioos/bw_import_status_check.py,sha256=
|
|
7
|
-
bioos/bw_status_check.py,sha256=
|
|
8
|
-
bioos/config.py,sha256
|
|
3
|
+
bioos/bioos.py,sha256=yhaWaJMwP23fMtid2mNWUetGwDUbUtCKwugXt27OLTs,2600
|
|
4
|
+
bioos/bioos_workflow.py,sha256=LRzYxuNnbF3R7urrMRjMRwR2hGDmH_4kP52D2Gger8k,15259
|
|
5
|
+
bioos/bw_import.py,sha256=kYS4BDbyIXyaFIOCUyOnAzIbLdHvY3l3KLPDVfAnFSM,5829
|
|
6
|
+
bioos/bw_import_status_check.py,sha256=x9Y_0I8huoC7aBgObuox-0z6foSr99TXQB7jozyQ7Eo,2972
|
|
7
|
+
bioos/bw_status_check.py,sha256=Etj_zp6tS0cj4t-nksUq221pPIWUFS0Lz0q0Uqflpes,3094
|
|
8
|
+
bioos/config.py,sha256=-1qS0uYgV-h3d67LyOuJ3lzDskEytXHI7T65-p8tnhA,4405
|
|
9
9
|
bioos/errors.py,sha256=p0fH6JSMYBjul88lMJ7PPwGNh4SYg62-7VMNuUXWl-E,2540
|
|
10
|
-
bioos/get_submission_logs.py,sha256=
|
|
10
|
+
bioos/get_submission_logs.py,sha256=e0OlWwMso8bB6u1TepJpRim2L-trxM6x3IdgZSwo7jc,4112
|
|
11
11
|
bioos/log.py,sha256=twiCvf5IgJB7uvzANwBluSlztJN8ZrxbGZUBGlZ0vps,3204
|
|
12
|
+
bioos/workflow_info.py,sha256=5PT45Lv-Js84ljZxjb9uNl0Jd5l-LYnXyKHYKrOhcjI,6840
|
|
12
13
|
bioos/internal/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
13
14
|
bioos/internal/tos.py,sha256=0R6YN2lxjjZsuMfv0yLSkBmz_LqmzQGb8GagnUMc8EY,12264
|
|
14
15
|
bioos/models/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
@@ -17,7 +18,7 @@ bioos/resource/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
|
17
18
|
bioos/resource/data_models.py,sha256=enKp8yyQI8IbRqe--0Xtyg1XzOwQQPQzoQsx_hNuZ6E,5089
|
|
18
19
|
bioos/resource/files.py,sha256=1HY0IHvq8H843VM2XZIHDdCuXXNcMrlEFhSNqWXmFzE,8456
|
|
19
20
|
bioos/resource/utility.py,sha256=emY7qVLLLvGmQYlVj-_bLAxU7i1GfQOUybdRkfEDwVA,1300
|
|
20
|
-
bioos/resource/workflows.py,sha256=
|
|
21
|
+
bioos/resource/workflows.py,sha256=RZkREd7EzyRqk3gP09HSzeuI3i7Cn7VMhqjp4jj_cjg,29418
|
|
21
22
|
bioos/resource/workspaces.py,sha256=Gmr8y_sjK7TQbhMhQ_7rxqR1KFcwU72I95YYCFrrLBQ,3995
|
|
22
23
|
bioos/service/BioOsService.py,sha256=HuYUEwomHCLpA1MYgVqGyWAQWHM-_BHB-jmy9VsOlnQ,6724
|
|
23
24
|
bioos/service/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
@@ -34,9 +35,9 @@ bioos/tests/workspaces.py,sha256=LuuRrTs2XqfE5mGQyJNl9RBtuMb4NZHBJFoO8HMZVYQ,522
|
|
|
34
35
|
bioos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
36
|
bioos/utils/common_tools.py,sha256=fgMoE_-qZjgfQtUj_pmCTyYDtbJasyfH4Gm3VQsbgBQ,1651
|
|
36
37
|
bioos/utils/workflows.py,sha256=zRbwTUigoM5V5LFOgzQPm3kwxt5Ogz95OFfefJc6Fjo,133
|
|
37
|
-
pybioos-0.0.
|
|
38
|
-
pybioos-0.0.
|
|
39
|
-
pybioos-0.0.
|
|
40
|
-
pybioos-0.0.
|
|
41
|
-
pybioos-0.0.
|
|
42
|
-
pybioos-0.0.
|
|
38
|
+
pybioos-0.0.17.dist-info/LICENSE,sha256=cPkGXsgfPgEhIns7Lt3Avxx0Uy-VbdsoP8jvNGuj3cE,1063
|
|
39
|
+
pybioos-0.0.17.dist-info/METADATA,sha256=thfygW7I6xtSkuKrMuzRw00fzmtPRtvFB5yfNIHRoj8,830
|
|
40
|
+
pybioos-0.0.17.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
|
|
41
|
+
pybioos-0.0.17.dist-info/entry_points.txt,sha256=Sc5H0_X7r03Mef4Qd70bebqgdIbVAxLU7nV7qP7cKD4,328
|
|
42
|
+
pybioos-0.0.17.dist-info/top_level.txt,sha256=llpzydkKVDSaWZgz3bsTUsQmhoQpc_JcRJg2-H-5a2U,6
|
|
43
|
+
pybioos-0.0.17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|