pybioos 0.0.3__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.

@@ -0,0 +1,123 @@
1
+ from datetime import datetime
2
+
3
+ import pandas as pd
4
+ from cachetools import TTLCache, cached
5
+
6
+ from bioos.config import Config
7
+ from bioos.resource.data_models import DataModelResource
8
+ from bioos.resource.files import FileResource
9
+ from bioos.resource.workflows import Workflow, WorkflowResource
10
+ from bioos.utils.common_tools import SingletonType, dict_str
11
+
12
+
13
+ class Workspace(metaclass=SingletonType):
14
+
15
+ def __init__(self, id_: str):
16
+ self._id = id_
17
+ self._bucket = None
18
+
19
+ def __repr__(self) -> str:
20
+ return f"WorkspaceID: {self._id}\n\n" \
21
+ f"BasicInfo:\n{dict_str(self.basic_info)}\n\n" \
22
+ f"EnvInfo:\n{self.env_info}"
23
+
24
+ @property
25
+ @cached(cache=TTLCache(maxsize=10, ttl=1)) #这里为了做缓存,同时定义property,调用时生效
26
+ def basic_info(self) -> dict:
27
+ """ Returns basic info of the workspace
28
+
29
+ :return: Basic info
30
+ :rtype: dict
31
+ """
32
+ workspace_infos = Config.service().list_workspaces(
33
+ { #这里是BioOsService对象的list_workspaces方法,非对外暴露的全局方法
34
+ "Filter": {
35
+ "IDs": [self._id]
36
+ }
37
+ }).get("Items")
38
+ if len(workspace_infos) != 1:
39
+ return {}
40
+ workspace_info = workspace_infos[0]
41
+ s3_bucket = workspace_info.get("S3Bucket")
42
+ create_time = datetime.fromtimestamp(workspace_info.get("CreateTime"))
43
+ owner = workspace_info.get("OwnerName")
44
+ description = workspace_info.get("Description")
45
+ name = workspace_info.get("Name")
46
+ return {
47
+ "name": name,
48
+ "description": description,
49
+ "s3_bucket": s3_bucket,
50
+ "owner": owner,
51
+ "create_time": create_time
52
+ }
53
+
54
+ @property
55
+ @cached(cache=TTLCache(maxsize=10, ttl=1))
56
+ def env_info(self) -> pd.DataFrame:
57
+ """ Returns cluster info of the workspace
58
+
59
+ :return: Cluster info
60
+ :rtype: pandas.DataFrame
61
+ """
62
+ notebook_env_info = Config.service().list_cluster(params={
63
+ 'Type': "notebook",
64
+ "ID": self._id
65
+ })
66
+ workflow_env_info = Config.service().list_cluster(params={
67
+ 'Type': "workflow",
68
+ "ID": self._id
69
+ })
70
+ res = []
71
+ for cluster in notebook_env_info.get('Items') + workflow_env_info.get(
72
+ 'Items'):
73
+ info = cluster["ClusterInfo"]
74
+ if info['Status'] == "Running":
75
+ res.append({
76
+ "cluster_id": info['ID'],
77
+ "name": info['Name'],
78
+ "description": info['Description'],
79
+ "type": cluster["Type"]
80
+ })
81
+ return pd.DataFrame(res)
82
+
83
+ @property
84
+ def data_models(self) -> DataModelResource:
85
+ """Returns DataModelResource object .
86
+
87
+ :return: DataModelResource object
88
+ :rtype: DataModelResource
89
+ """
90
+ return DataModelResource(self._id)
91
+
92
+ @property
93
+ def workflows(self) -> WorkflowResource:
94
+ """Returns WorkflowResource object.
95
+
96
+ :return: WorkflowResource object
97
+ :rtype: WorkflowResource
98
+ """
99
+ return WorkflowResource(self._id)
100
+
101
+ @property
102
+ def files(self) -> FileResource:
103
+ """Returns FileResource object .
104
+
105
+ :return: FileResource object
106
+ :rtype: FileResource
107
+ """
108
+ if not self._bucket:
109
+ self._bucket = self.basic_info.get("s3_bucket")
110
+
111
+ return FileResource(self._id, self._bucket)
112
+
113
+ def workflow(self, name: str) -> Workflow: # 通过这里执行的选择workflow生成wf的操作
114
+ """Returns the workflow for the given name
115
+
116
+ :param name: Workflow name
117
+ :type name: str
118
+ :return: Specified workflow object
119
+ :rtype: Workflow
120
+ """
121
+ if not self._bucket:
122
+ self._bucket = self.basic_info.get("s3_bucket")
123
+ return Workflow(name, self._id, self._bucket)
@@ -0,0 +1,191 @@
1
+ # coding:utf-8
2
+ import json
3
+ import threading
4
+ from urllib.parse import urlparse
5
+
6
+ from volcengine.ApiInfo import ApiInfo
7
+ from volcengine.base.Service import Service
8
+ from volcengine.Credentials import Credentials
9
+ from volcengine.ServiceInfo import ServiceInfo
10
+
11
+ from bioos.errors import ParameterError
12
+
13
+
14
+ class BioOsService(Service):
15
+ _instance_lock = threading.Lock()
16
+
17
+ def __new__(cls, *args, **kwargs):
18
+ if not hasattr(BioOsService, '_instance'):
19
+ with BioOsService._instance_lock:
20
+ if not hasattr(BioOsService, '_instance'):
21
+ BioOsService._instance = object.__new__(cls)
22
+ return BioOsService._instance
23
+
24
+ def __init__(self, endpoint, region):
25
+ self.service_info = BioOsService.get_service_info(endpoint, region)
26
+ self.api_info = BioOsService.get_api_info()
27
+ super(BioOsService, self).__init__(self.service_info, self.api_info)
28
+
29
+ @staticmethod
30
+ def get_service_info(endpoint, region):
31
+ parsed = urlparse(endpoint)
32
+ scheme, hostname = parsed.scheme, parsed.hostname
33
+ if not scheme or not hostname:
34
+ raise ParameterError("ENDPOINT")
35
+ service_info = ServiceInfo(hostname, {'Accept': 'application/json'},
36
+ Credentials('', '', 'bio', region),
37
+ 5,
38
+ 5,
39
+ scheme=scheme)
40
+ return service_info
41
+
42
+ @staticmethod
43
+ def get_api_info(): #新增加的API接口可以配置到这里
44
+ api_info = {
45
+ 'ListWorkspaces':
46
+ ApiInfo('POST', '/', {
47
+ 'Action': 'ListWorkspaces',
48
+ 'Version': '2021-03-04'
49
+ }, {}, {}),
50
+ 'CreateDataModel':
51
+ ApiInfo('POST', '/', {
52
+ 'Action': 'CreateDataModel',
53
+ 'Version': '2021-03-04'
54
+ }, {}, {}),
55
+ 'ListDataModels':
56
+ ApiInfo('POST', '/', {
57
+ 'Action': 'ListDataModels',
58
+ 'Version': '2021-03-04'
59
+ }, {}, {}),
60
+ 'ListDataModelRows':
61
+ ApiInfo('POST', '/', {
62
+ 'Action': 'ListDataModelRows',
63
+ 'Version': '2021-03-04'
64
+ }, {}, {}),
65
+ 'ListAllDataModelRowIDs':
66
+ ApiInfo('POST', '/', {
67
+ 'Action': 'ListAllDataModelRowIDs',
68
+ 'Version': '2021-03-04'
69
+ }, {}, {}),
70
+ 'DeleteDataModelRowsAndHeaders':
71
+ ApiInfo('POST', '/', {
72
+ 'Action': 'DeleteDataModelRowsAndHeaders',
73
+ 'Version': '2021-03-04'
74
+ }, {}, {}),
75
+ 'ListWorkflows':
76
+ ApiInfo('POST', '/', {
77
+ 'Action': 'ListWorkflows',
78
+ 'Version': '2021-03-04'
79
+ }, {}, {}),
80
+ 'CreateSubmission':
81
+ ApiInfo('POST', '/', {
82
+ 'Action': 'CreateSubmission',
83
+ 'Version': '2021-03-04'
84
+ }, {}, {}),
85
+ 'ListSubmissions':
86
+ ApiInfo('POST', '/', {
87
+ 'Action': 'ListSubmissions',
88
+ 'Version': '2021-03-04'
89
+ }, {}, {}),
90
+ 'DeleteSubmission':
91
+ ApiInfo('POST', '/', {
92
+ 'Action': 'DeleteSubmission',
93
+ 'Version': '2021-03-04'
94
+ }, {}, {}),
95
+ 'ListRuns':
96
+ ApiInfo('POST', '/', {
97
+ 'Action': 'ListRuns',
98
+ 'Version': '2021-03-04'
99
+ }, {}, {}),
100
+ 'ListTasks':
101
+ ApiInfo('POST', '/', {
102
+ 'Action': 'ListTasks',
103
+ 'Version': '2021-03-04'
104
+ }, {}, {}),
105
+ 'GetTOSAccess':
106
+ ApiInfo('POST', '/', {
107
+ 'Action': 'GetTOSAccess',
108
+ 'Version': '2021-03-04'
109
+ }, {}, {}),
110
+ 'ListClustersOfWorkspace':
111
+ ApiInfo('POST', '/', {
112
+ 'Action': 'ListClustersOfWorkspace',
113
+ 'Version': '2021-03-04'
114
+ }, {}, {}),
115
+ 'CreateWorkflow':
116
+ ApiInfo('POST', '/', {
117
+ 'Action': 'CreateWorkflow',
118
+ 'Version': '2021-03-04'
119
+ }, {}, {}),
120
+ 'CheckCreateWorkflow':
121
+ ApiInfo('POST', '/', {
122
+ 'Action': 'CheckCreateWorkflow',
123
+ 'Version': '2021-03-04'
124
+ }, {}, {}),
125
+ 'DeleteWorkflow':
126
+ ApiInfo('POST', '/', {
127
+ 'Action': 'DeleteWorkflow',
128
+ 'Version': '2021-03-04'
129
+ }, {}, {}),
130
+ }
131
+ return api_info
132
+
133
+ def list_workspaces(self, params): # 以下各方法的params需要在外部使用时构建
134
+ return self.__request('ListWorkspaces', params)
135
+
136
+ def create_data_model(self, params):
137
+ return self.__request('CreateDataModel', params)
138
+
139
+ def list_data_models(self, params):
140
+ return self.__request('ListDataModels', params)
141
+
142
+ def list_data_model_rows(self, params):
143
+ return self.__request('ListDataModelRows', params)
144
+
145
+ def list_data_model_row_ids(self, params):
146
+ return self.__request('ListAllDataModelRowIDs', params)
147
+
148
+ def delete_data_model_rows_and_headers(self, params):
149
+ return self.__request('DeleteDataModelRowsAndHeaders', params)
150
+
151
+ def list_workflows(self, params):
152
+ return self.__request('ListWorkflows', params)
153
+
154
+ def create_submission(self, params):
155
+ return self.__request('CreateSubmission', params)
156
+
157
+ def list_submissions(self, params):
158
+ return self.__request('ListSubmissions', params)
159
+
160
+ def delete_submission(self, params):
161
+ return self.__request('DeleteSubmission', params)
162
+
163
+ def list_runs(self, params):
164
+ return self.__request('ListRuns', params)
165
+
166
+ def list_tasks(self, params):
167
+ return self.__request('ListTasks', params)
168
+
169
+ def get_tos_access(self, params):
170
+ return self.__request('GetTOSAccess', params)
171
+
172
+ def list_cluster(self, params):
173
+ return self.__request('ListClustersOfWorkspace', params)
174
+
175
+ def create_workflow(self, params):
176
+ return self.__request('CreateWorkflow', params)
177
+
178
+ def check_workflow(self, params):
179
+ return self.__request("CheckCreateWorkflow", params)
180
+
181
+ def delete_workflow(self, params):
182
+ return self.__request("DeleteWorkflow", params)
183
+
184
+ def __request(self, action, params):
185
+ res = self.json(
186
+ action, dict(),
187
+ json.dumps(params)) # 这里的json来源于其父类,是字节Service中的函数。处理的是和后端交互后的返回值
188
+ if res == '':
189
+ raise Exception('empty response')
190
+ res_json = json.loads(res)
191
+ return res_json['Result']
@@ -0,0 +1 @@
1
+
bioos/service/api.py ADDED
@@ -0,0 +1,291 @@
1
+ # coding:utf-8
2
+ import csv
3
+ import json
4
+ import os
5
+ import re
6
+ from datetime import datetime
7
+
8
+ from bioos.errors import NotFoundError, ParameterError
9
+ from bioos.service.config import BioOsServiceConfig as conf
10
+ from bioos.utils import workflows
11
+
12
+
13
+ def __set_env():
14
+ conf.set_env()
15
+
16
+
17
+ def set_credential(accesskey, secret):
18
+ conf.set_access_key(accesskey)
19
+ conf.set_secret_key(secret)
20
+ conf.set_env()
21
+
22
+
23
+ def upload_entity_table(csvfile, table_name=None):
24
+ __set_env()
25
+ if not table_name:
26
+ table_name = re.sub('\.csv$', '', os.path.basename(csvfile))
27
+ headers = None
28
+ rows = []
29
+ with open(csvfile, 'rt', newline='') as f:
30
+ reader = csv.reader(f)
31
+ for row in reader:
32
+ if headers is None:
33
+ headers = row
34
+ else:
35
+ rows.append(row)
36
+ params = {
37
+ 'WorkspaceID': conf.workspace_id(),
38
+ 'Name': table_name,
39
+ 'Headers': headers,
40
+ 'Rows': rows
41
+ }
42
+ conf.service().create_data_model(params)
43
+
44
+
45
+ def list_entity_tables():
46
+ __set_env()
47
+ params = {'WorkspaceID': conf.workspace_id()}
48
+ result = conf.service().list_data_models(params)
49
+ entities = []
50
+ for table in result['Items']:
51
+ if table['Type'] == 'normal':
52
+ entities.append(table)
53
+ return entities
54
+
55
+
56
+ def get_entity_table(table_name, page_number=1, page_size=10):
57
+ l = list_entity_tables()
58
+ res = None
59
+ for table in l:
60
+ if table['Name'] == table_name:
61
+ res = table
62
+ if not res:
63
+ return None
64
+ params = {
65
+ 'WorkspaceID': conf.workspace_id(),
66
+ 'ID': res['ID'],
67
+ 'PageNumber': page_number,
68
+ 'PageSize': page_size
69
+ }
70
+ content = conf.service().list_data_model_rows(params)
71
+ res['Headers'] = content['Headers']
72
+ res['Rows'] = content['Rows']
73
+ res['RowTotalCount'] = content['TotalCount']
74
+ return res
75
+
76
+
77
+ def delete_entity_table(table_name):
78
+ __set_env()
79
+ table = get_entity_table(table_name, page_size=1)
80
+ if not table:
81
+ raise NotFoundError('Table', table_name)
82
+ params = {'WorkspaceID': conf.workspace_id(), 'ID': table['ID']}
83
+ ids = conf.service().list_data_model_row_ids(params)
84
+ params = {
85
+ 'WorkspaceID': conf.workspace_id(),
86
+ 'ID': table['ID'],
87
+ 'RowIDs': ids['RowIDs']
88
+ }
89
+ conf.service().delete_data_model_rows_and_headers(params)
90
+
91
+
92
+ def delete_entities(table_name, row_ids):
93
+ if not row_ids:
94
+ return
95
+ __set_env()
96
+ table = get_entity_table(table_name, page_size=1)
97
+ if not table:
98
+ raise NotFoundError('Table', table_name)
99
+ params = {
100
+ 'WorkspaceID': conf.workspace_id(),
101
+ 'ID': table['ID'],
102
+ 'RowIDs': row_ids
103
+ }
104
+ conf.service().delete_data_model_rows_and_headers(params)
105
+
106
+
107
+ def delete_entity_table_headers(table_name, headers):
108
+ if not headers:
109
+ return
110
+ __set_env()
111
+ table = get_entity_table(table_name, page_size=1)
112
+ if not table:
113
+ raise NotFoundError('Table', table_name)
114
+ params = {
115
+ 'WorkspaceID': conf.workspace_id(),
116
+ 'ID': table['ID'],
117
+ 'Headers': headers
118
+ }
119
+ conf.service().delete_data_model_rows_and_headers(params)
120
+
121
+
122
+ def list_workflows(search_keyword=None, page_number=1, page_size=10):
123
+ __set_env()
124
+ params = {
125
+ 'WorkspaceID': conf.workspace_id(),
126
+ 'SortBy': 'CreateTime',
127
+ 'PageNumber': page_number,
128
+ 'PageSize': page_size
129
+ }
130
+ if search_keyword:
131
+ params['Filter'] = {'Keyword': search_keyword}
132
+ return conf.service().list_workflows(params).get('Items')
133
+
134
+
135
+ def get_workflow(workflow_name):
136
+ lis = list_workflows(search_keyword=workflow_name)
137
+ if not lis:
138
+ return None
139
+ lis = [x for x in lis if x['Name'] == workflow_name]
140
+ if not lis:
141
+ return None
142
+ params = {
143
+ 'WorkspaceID': conf.workspace_id(),
144
+ 'Filter': {
145
+ 'IDs': [lis[0].get('ID')]
146
+ }
147
+ }
148
+ workflows = conf.service().list_workflows(params).get('Items')
149
+ if len(workflows) != 1:
150
+ return None
151
+ detail = workflows[0]
152
+ res = lis[0]
153
+ for k, v in detail.items():
154
+ if k != 'Item':
155
+ res[k] = v
156
+ return res
157
+
158
+
159
+ def add_submission(workflow_name,
160
+ table_name,
161
+ row_ids,
162
+ cluster_id,
163
+ inputs={},
164
+ outputs={},
165
+ submission_name_suffix=None,
166
+ submission_desc=None,
167
+ call_caching=True):
168
+ if not row_ids or not isinstance(row_ids, list):
169
+ raise ParameterError('row_ids')
170
+ if not inputs and not isinstance(inputs, dict):
171
+ raise ParameterError('inputs')
172
+ if not outputs and not isinstance(outputs, dict):
173
+ raise ParameterError('outputs')
174
+
175
+ table = get_entity_table(table_name, page_size=1)
176
+ if not table:
177
+ raise NotFoundError('Table', table_name)
178
+ workflow = get_workflow(workflow_name)
179
+ if not workflow:
180
+ raise NotFoundError('Workflow', workflow_name)
181
+ if not submission_name_suffix:
182
+ submission_name_suffix = datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
183
+ params = {
184
+ "ClusterID": cluster_id,
185
+ 'WorkspaceID': conf.workspace_id(),
186
+ 'WorkflowID': workflow['ID'],
187
+ 'Name': submission_name(workflow_name, submission_name_suffix),
188
+ 'Description': submission_desc,
189
+ 'DataModelID': table['ID'],
190
+ 'DataModelRowIDs': row_ids,
191
+ 'Inputs': json.dumps(inputs),
192
+ 'ExposedOptions': {
193
+ "ReadFromCache": call_caching
194
+ },
195
+ 'Outputs': json.dumps(outputs)
196
+ }
197
+ conf.service().create_submission(params)
198
+
199
+
200
+ def list_submissions(workflow_name=None,
201
+ search_keyword=None,
202
+ status=None,
203
+ cluster_id=None,
204
+ page_number=1,
205
+ page_size=10):
206
+ __set_env()
207
+ params = {
208
+ 'WorkspaceID': conf.workspace_id(),
209
+ 'PageNumber': page_number,
210
+ 'PageSize': page_size,
211
+ 'Filter': {}
212
+ }
213
+ if workflow_name:
214
+ workflow = get_workflow(workflow_name)
215
+ if not workflow:
216
+ raise NotFoundError('Workflow', workflow_name)
217
+ params['Filter']['WorkflowID'] = workflow['ID']
218
+ if search_keyword:
219
+ params['Filter']['Keyword'] = search_keyword
220
+ if status:
221
+ params['Filter']['Status'] = status
222
+ if cluster_id:
223
+ params['Filter']['ClusterID'] = cluster_id
224
+
225
+ return conf.service().list_submissions(params).get('Items')
226
+
227
+
228
+ def get_submission(submission_name):
229
+ lis = list_submissions(search_keyword=submission_name)
230
+ if not lis:
231
+ raise NotFoundError('Submission', submission_name)
232
+ lis = [x for x in lis if x['Name'] == submission_name]
233
+ if not lis or len(lis) < 1:
234
+ return None
235
+ submission = lis[0]
236
+ # list data entity rows by call list runs
237
+ runs = conf.service().list_runs({
238
+ 'WorkspaceID': submission.get("WorkflowID"),
239
+ "SubmissionID": submission.get("ID"),
240
+ }).get("Items")
241
+ data_entity_row_ids = set()
242
+ for run in runs:
243
+ if run.get("DataEntityRowID") != "":
244
+ data_entity_row_ids.add(run.get("DataEntityRowID"))
245
+ # get data model name by call list data models
246
+ models = conf.service().list_data_models({
247
+ 'WorkspaceID':
248
+ submission.get("WorkflowID"),
249
+ }).get("Items")
250
+ data_model = ""
251
+ for model in models:
252
+ if model["ID"] == submission.get["DataModelID"]:
253
+ data_model = model.get("Name")
254
+ break
255
+ submission["DataEntity"] = {
256
+ "ID": submission.get["DataModelID"],
257
+ "Name": data_model,
258
+ "RowIDs": list(data_entity_row_ids)
259
+ }
260
+
261
+ return submission
262
+
263
+
264
+ def delete_submission(submission_name):
265
+ submission = get_submission(submission_name)
266
+ if not submission:
267
+ raise NotFoundError('Submission', submission_name)
268
+ params = {'WorkspaceID': conf.workspace_id(), 'ID': submission['ID']}
269
+ conf.service().delete_submission(params)
270
+
271
+
272
+ def list_cluster(cluster_type):
273
+ if cluster_type not in ("notebook", "workflow"):
274
+ raise ParameterError("cluster_type")
275
+ params = {'Type': cluster_type, 'ID': conf.workspace_id()}
276
+ clusters = conf.service().list_cluster(params).get('Items')
277
+ res = []
278
+ for cluster in clusters:
279
+ info = cluster["ClusterInfo"]
280
+ if info['Status'] == "Running":
281
+ res.append({
282
+ "cluster_id": info['ID'],
283
+ "name": info['Name'],
284
+ "description": info['Description'],
285
+ "type": cluster["Type"]
286
+ })
287
+ return res
288
+
289
+
290
+ def submission_name(workflow_name, submission_name_suffix):
291
+ return workflows.submission_name(workflow_name, submission_name_suffix)
@@ -0,0 +1,37 @@
1
+ import os
2
+
3
+ from bioos.config import Config
4
+ from bioos.errors import EnvironmentConfigurationError
5
+
6
+
7
+ def get_endpoint_env():
8
+ return os.environ.get('BIOOS_ENDPOINT')
9
+
10
+
11
+ def get_workspace_id_env():
12
+ workspace_id = os.environ.get('BIOOS_WORKSPACE_ID')
13
+ if not workspace_id:
14
+ # use in MiracleCloud Notebook editor(jupyterhub)
15
+ workspace_id = os.environ.get('JUPYTERHUB_SERVER_NAME')
16
+ return workspace_id
17
+
18
+
19
+ class BioOsServiceConfig(Config):
20
+ _workspace_id: str = None
21
+
22
+ @classmethod
23
+ def set_env(cls):
24
+ if cls._service is None:
25
+ endpoint = get_endpoint_env()
26
+ if not endpoint:
27
+ raise EnvironmentConfigurationError('BIOOS_ENDPOINT')
28
+ cls.set_endpoint(endpoint)
29
+
30
+ workspace_id = get_workspace_id_env()
31
+ if not workspace_id:
32
+ raise EnvironmentConfigurationError('BIOOS_WORKSPACE_ID')
33
+ cls._workspace_id = workspace_id
34
+
35
+ @classmethod
36
+ def workspace_id(cls):
37
+ return cls._workspace_id
File without changes
bioos/tests/base.py ADDED
@@ -0,0 +1,21 @@
1
+ import unittest
2
+ from unittest.mock import patch
3
+
4
+ from volcengine.const.Const import REGION_CN_NORTH1
5
+
6
+ from bioos import bioos
7
+ from bioos.config import Config
8
+
9
+
10
+ class BaseInit(unittest.TestCase):
11
+ ak = "ak"
12
+ sk = "sk"
13
+ endpoint = "http://endpoint"
14
+ region = REGION_CN_NORTH1
15
+ workspace_id = "wccxxxxxxxxxxxxxno80"
16
+ really_login = bioos.login(endpoint, ak, sk, region)
17
+
18
+ def __init__(self, *args, **kwargs):
19
+ with patch.object(Config, "_ping_func"):
20
+ bioos.login(self.endpoint, self.ak, self.sk, self.region)
21
+ super(BaseInit, self).__init__(*args, **kwargs)