pybiolib 1.1.1747__py3-none-any.whl → 1.1.2193__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.
- biolib/__init__.py +18 -5
- biolib/_data_record/data_record.py +278 -0
- biolib/_internal/data_record/__init__.py +1 -0
- biolib/_internal/data_record/data_record.py +97 -0
- biolib/_internal/data_record/remote_storage_endpoint.py +38 -0
- biolib/_internal/file_utils.py +77 -0
- biolib/_internal/fuse_mount/__init__.py +1 -0
- biolib/_internal/fuse_mount/experiment_fuse_mount.py +209 -0
- biolib/_internal/http_client.py +42 -23
- biolib/_internal/lfs/__init__.py +1 -0
- biolib/_internal/libs/__init__.py +1 -0
- biolib/_internal/libs/fusepy/__init__.py +1257 -0
- biolib/_internal/push_application.py +22 -37
- biolib/_internal/runtime.py +19 -0
- biolib/_internal/types/__init__.py +4 -0
- biolib/_internal/types/app.py +9 -0
- biolib/_internal/types/data_record.py +40 -0
- biolib/_internal/types/experiment.py +10 -0
- biolib/_internal/types/resource.py +14 -0
- biolib/_internal/types/typing.py +7 -0
- biolib/_internal/utils/__init__.py +18 -0
- biolib/_runtime/runtime.py +80 -0
- biolib/api/__init__.py +1 -0
- biolib/api/client.py +39 -17
- biolib/app/app.py +40 -72
- biolib/app/search_apps.py +8 -12
- biolib/biolib_api_client/api_client.py +22 -10
- biolib/biolib_api_client/app_types.py +2 -1
- biolib/biolib_api_client/biolib_app_api.py +1 -1
- biolib/biolib_api_client/biolib_job_api.py +6 -0
- biolib/biolib_api_client/job_types.py +4 -4
- biolib/biolib_api_client/lfs_types.py +8 -2
- biolib/biolib_binary_format/remote_endpoints.py +12 -10
- biolib/biolib_binary_format/utils.py +41 -4
- biolib/cli/__init__.py +6 -2
- biolib/cli/auth.py +58 -0
- biolib/cli/data_record.py +80 -0
- biolib/cli/download_container.py +3 -1
- biolib/cli/init.py +1 -0
- biolib/cli/lfs.py +45 -11
- biolib/cli/push.py +1 -1
- biolib/cli/run.py +3 -2
- biolib/cli/start.py +1 -0
- biolib/compute_node/cloud_utils/cloud_utils.py +15 -18
- biolib/compute_node/job_worker/cache_state.py +1 -1
- biolib/compute_node/job_worker/executors/docker_executor.py +134 -114
- biolib/compute_node/job_worker/job_storage.py +3 -4
- biolib/compute_node/job_worker/job_worker.py +31 -15
- biolib/compute_node/remote_host_proxy.py +75 -70
- biolib/compute_node/webserver/webserver_types.py +0 -1
- biolib/experiments/experiment.py +75 -44
- biolib/jobs/job.py +125 -47
- biolib/jobs/job_result.py +46 -21
- biolib/jobs/types.py +1 -1
- biolib/runtime/__init__.py +14 -1
- biolib/sdk/__init__.py +29 -5
- biolib/typing_utils.py +2 -7
- biolib/user/sign_in.py +10 -14
- biolib/utils/__init__.py +1 -1
- biolib/utils/app_uri.py +11 -4
- biolib/utils/cache_state.py +2 -2
- biolib/utils/seq_util.py +38 -30
- {pybiolib-1.1.1747.dist-info → pybiolib-1.1.2193.dist-info}/METADATA +1 -1
- pybiolib-1.1.2193.dist-info/RECORD +123 -0
- {pybiolib-1.1.1747.dist-info → pybiolib-1.1.2193.dist-info}/WHEEL +1 -1
- biolib/biolib_api_client/biolib_account_api.py +0 -8
- biolib/biolib_api_client/biolib_large_file_system_api.py +0 -34
- biolib/experiments/types.py +0 -9
- biolib/lfs/__init__.py +0 -6
- biolib/lfs/utils.py +0 -237
- biolib/runtime/results.py +0 -20
- pybiolib-1.1.1747.dist-info/RECORD +0 -108
- /biolib/{lfs → _internal/lfs}/cache.py +0 -0
- {pybiolib-1.1.1747.dist-info → pybiolib-1.1.2193.dist-info}/LICENSE +0 -0
- {pybiolib-1.1.1747.dist-info → pybiolib-1.1.2193.dist-info}/entry_points.txt +0 -0
@@ -1,17 +1,18 @@
|
|
1
1
|
import os
|
2
2
|
import re
|
3
3
|
from pathlib import Path
|
4
|
-
|
4
|
+
|
5
5
|
import rich.progress
|
6
|
+
import yaml
|
6
7
|
|
7
|
-
from biolib
|
8
|
-
from biolib.
|
8
|
+
from biolib import api, utils
|
9
|
+
from biolib._internal.file_utils import get_files_and_size_of_directory, get_iterable_zip_stream
|
9
10
|
from biolib.biolib_api_client import BiolibApiClient
|
10
|
-
from biolib.biolib_docker_client import BiolibDockerClient
|
11
11
|
from biolib.biolib_api_client.biolib_app_api import BiolibAppApi
|
12
|
+
from biolib.biolib_docker_client import BiolibDockerClient
|
12
13
|
from biolib.biolib_errors import BioLibError
|
13
14
|
from biolib.biolib_logging import logger
|
14
|
-
from biolib import
|
15
|
+
from biolib.typing_utils import Iterable, Optional, Set, TypedDict
|
15
16
|
|
16
17
|
REGEX_MARKDOWN_INLINE_IMAGE = re.compile(r'!\[(?P<alt>.*)\]\((?P<src>.*)\)')
|
17
18
|
|
@@ -38,9 +39,7 @@ def process_docker_status_updates(status_updates: Iterable[DockerStatusUpdate],
|
|
38
39
|
progress_detail = update['progressDetail']
|
39
40
|
|
40
41
|
if layer_id not in layer_id_to_task_id:
|
41
|
-
layer_id_to_task_id[layer_id] = progress.add_task(
|
42
|
-
description=f'[cyan]{action} layer {layer_id}'
|
43
|
-
)
|
42
|
+
layer_id_to_task_id[layer_id] = progress.add_task(description=f'[cyan]{action} layer {layer_id}')
|
44
43
|
|
45
44
|
if progress_detail and 'current' in progress_detail and 'total' in progress_detail:
|
46
45
|
progress.update(
|
@@ -60,7 +59,7 @@ def process_docker_status_updates(status_updates: Iterable[DockerStatusUpdate],
|
|
60
59
|
|
61
60
|
|
62
61
|
def set_app_version_as_active(
|
63
|
-
|
62
|
+
app_version_uuid: str,
|
64
63
|
):
|
65
64
|
logger.debug(f'Setting app version {app_version_uuid} as active.')
|
66
65
|
api.client.patch(
|
@@ -70,10 +69,10 @@ def set_app_version_as_active(
|
|
70
69
|
|
71
70
|
|
72
71
|
def push_application(
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
72
|
+
app_uri: str,
|
73
|
+
app_path: str,
|
74
|
+
app_version_to_copy_images_from: Optional[str],
|
75
|
+
is_dev_version: Optional[bool],
|
77
76
|
):
|
78
77
|
app_path_absolute = Path(app_path).resolve()
|
79
78
|
|
@@ -96,7 +95,7 @@ def push_application(
|
|
96
95
|
|
97
96
|
input_files_maps_to_root = False
|
98
97
|
try:
|
99
|
-
with open(config_yml_path
|
98
|
+
with open(config_yml_path) as config_yml_file:
|
100
99
|
config = yaml.safe_load(config_yml_file.read())
|
101
100
|
|
102
101
|
license_file_relative_path = config.get('license_file', 'LICENSE')
|
@@ -109,7 +108,7 @@ def push_application(
|
|
109
108
|
raise BioLibError(f'Could not find {description_file_relative_path}')
|
110
109
|
|
111
110
|
zip_filters.add(description_file_relative_path)
|
112
|
-
with open(description_file_absolute_path
|
111
|
+
with open(description_file_absolute_path) as description_file:
|
113
112
|
description_file_content = description_file.read()
|
114
113
|
|
115
114
|
for _, img_src_path in re.findall(REGEX_MARKDOWN_INLINE_IMAGE, description_file_content):
|
@@ -171,8 +170,9 @@ def push_application(
|
|
171
170
|
author=app['account_handle'],
|
172
171
|
set_as_active=False,
|
173
172
|
zip_binary=source_files_zip_bytes,
|
174
|
-
app_version_id_to_copy_images_from=app_response['app_version']['public_id']
|
175
|
-
|
173
|
+
app_version_id_to_copy_images_from=app_response['app_version']['public_id']
|
174
|
+
if app_version_to_copy_images_from
|
175
|
+
else None,
|
176
176
|
)
|
177
177
|
|
178
178
|
# Don't push docker images if copying from another app version
|
@@ -180,18 +180,6 @@ def push_application(
|
|
180
180
|
if not app_version_to_copy_images_from and docker_tags:
|
181
181
|
logger.info('Found docker images to push.')
|
182
182
|
|
183
|
-
try:
|
184
|
-
yaml_file = open(f'{app_path}/.biolib/config.yml', 'r', encoding='utf-8')
|
185
|
-
|
186
|
-
except Exception as error: # pylint: disable=broad-except
|
187
|
-
raise BioLibError('Could not open the config file .biolib/config.yml') from error
|
188
|
-
|
189
|
-
try:
|
190
|
-
config_data = yaml.safe_load(yaml_file)
|
191
|
-
|
192
|
-
except Exception as error: # pylint: disable=broad-except
|
193
|
-
raise BioLibError('Could not parse .biolib/config.yml. Please make sure it is valid YAML') from error
|
194
|
-
|
195
183
|
# Auth to be sent to proxy
|
196
184
|
# The tokens are sent as "{access_token},{job_id}". We leave job_id blank on push.
|
197
185
|
tokens = f'{BiolibApiClient.get().access_token},'
|
@@ -200,14 +188,12 @@ def push_application(
|
|
200
188
|
docker_client = BiolibDockerClient.get_docker_client()
|
201
189
|
|
202
190
|
for module_name, repo_and_tag in docker_tags.items():
|
203
|
-
docker_image_definition =
|
191
|
+
docker_image_definition = config['modules'][module_name]['image']
|
204
192
|
repo, tag = repo_and_tag.split(':')
|
205
193
|
|
206
194
|
if docker_image_definition.startswith('dockerhub://'):
|
207
195
|
docker_image_name = docker_image_definition.replace('dockerhub://', 'docker.io/', 1)
|
208
|
-
logger.info(
|
209
|
-
f'Pulling image {docker_image_name} defined on module {module_name} from Dockerhub.'
|
210
|
-
)
|
196
|
+
logger.info(f'Pulling image {docker_image_name} defined on module {module_name} from Dockerhub.')
|
211
197
|
dockerhub_repo, dockerhub_tag = docker_image_name.split(':')
|
212
198
|
pull_status_updates: Iterable[DockerStatusUpdate] = docker_client.api.pull(
|
213
199
|
decode=True,
|
@@ -238,7 +224,7 @@ def push_application(
|
|
238
224
|
|
239
225
|
process_docker_status_updates(push_status_updates, action='Pushing')
|
240
226
|
|
241
|
-
except Exception as exception:
|
227
|
+
except Exception as exception:
|
242
228
|
raise BioLibError(f'Failed to tag and push image {docker_image_name}.') from exception
|
243
229
|
|
244
230
|
logger.info(f'Successfully pushed {docker_image_name}')
|
@@ -249,10 +235,9 @@ def push_application(
|
|
249
235
|
data={'set_as_active': not is_dev_version},
|
250
236
|
)
|
251
237
|
|
252
|
-
sematic_version =
|
253
|
-
f"{new_app_version_json['major']}.{new_app_version_json['minor']}.{new_app_version_json['patch']}"
|
238
|
+
sematic_version = f"{new_app_version_json['major']}.{new_app_version_json['minor']}.{new_app_version_json['patch']}"
|
254
239
|
logger.info(
|
255
240
|
f"Successfully pushed new {'development ' if is_dev_version else ''}version {sematic_version} of {app_uri}."
|
256
241
|
)
|
257
242
|
|
258
|
-
return {
|
243
|
+
return {'app_uri': app_uri, 'sematic_version': sematic_version}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from biolib.typing_utils import TypedDict
|
2
|
+
|
3
|
+
|
4
|
+
class RuntimeJobDataDict(TypedDict):
|
5
|
+
version: str
|
6
|
+
job_requested_machine: str
|
7
|
+
job_uuid: str
|
8
|
+
job_auth_token: str
|
9
|
+
app_uri: str
|
10
|
+
|
11
|
+
|
12
|
+
class BioLibRuntimeError(Exception):
|
13
|
+
pass
|
14
|
+
|
15
|
+
|
16
|
+
class BioLibRuntimeNotRecognizedError(BioLibRuntimeError):
|
17
|
+
def __init__(self, message='The runtime is not recognized as a BioLib app'):
|
18
|
+
self.message = message
|
19
|
+
super().__init__(self.message)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from .typing import Dict, List, Literal, Optional, TypedDict, Union
|
2
|
+
|
3
|
+
|
4
|
+
class SqliteV1ForeignKey(TypedDict):
|
5
|
+
table: str
|
6
|
+
column: str
|
7
|
+
|
8
|
+
|
9
|
+
class SqliteV1Column(TypedDict):
|
10
|
+
type: Literal['INTEGER', 'REAL', 'TEXT', 'JSON'] # noqa:F821
|
11
|
+
nullable: Optional[bool]
|
12
|
+
foreign_key: Optional[SqliteV1ForeignKey]
|
13
|
+
json_schema: Optional[Dict]
|
14
|
+
|
15
|
+
|
16
|
+
class SqliteV1Table(TypedDict):
|
17
|
+
columns: Dict[str, SqliteV1Column]
|
18
|
+
|
19
|
+
|
20
|
+
class SqliteV1DatabaseSchema(TypedDict):
|
21
|
+
tables: Dict[str, SqliteV1Table]
|
22
|
+
|
23
|
+
|
24
|
+
class DataRecordValidationRuleDict(TypedDict):
|
25
|
+
path: str
|
26
|
+
type: str
|
27
|
+
rule: Union[SqliteV1DatabaseSchema]
|
28
|
+
|
29
|
+
|
30
|
+
class DataRecordTypeDict(TypedDict):
|
31
|
+
name: str
|
32
|
+
validation_rules: List[DataRecordValidationRuleDict]
|
33
|
+
|
34
|
+
|
35
|
+
class DataRecordSlimDict(TypedDict):
|
36
|
+
pass
|
37
|
+
|
38
|
+
|
39
|
+
class DataRecordDetailedDict(DataRecordSlimDict):
|
40
|
+
type: Optional[DataRecordTypeDict]
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from .app import AppSlimDict
|
2
|
+
from .data_record import DataRecordSlimDict
|
3
|
+
from .experiment import ExperimentSlimDict
|
4
|
+
from .typing import Optional, TypedDict
|
5
|
+
|
6
|
+
|
7
|
+
class ResourceDict(TypedDict):
|
8
|
+
uuid: str
|
9
|
+
uri: str
|
10
|
+
name: str
|
11
|
+
created_at: str
|
12
|
+
app: Optional[AppSlimDict]
|
13
|
+
data_record: Optional[DataRecordSlimDict]
|
14
|
+
experiment: Optional[ExperimentSlimDict]
|
@@ -0,0 +1,7 @@
|
|
1
|
+
import sys
|
2
|
+
|
3
|
+
# import and expose everything from the typing module
|
4
|
+
from typing import * # noqa:F403 pylint: disable=wildcard-import, unused-wildcard-import
|
5
|
+
|
6
|
+
if sys.version_info < (3, 8): # noqa: UP036
|
7
|
+
from typing_extensions import Literal, TypedDict # pylint: disable=unused-import
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import time
|
2
|
+
import uuid
|
3
|
+
|
4
|
+
|
5
|
+
def open_browser_window_from_notebook(url_to_open: str) -> None:
|
6
|
+
try:
|
7
|
+
from IPython.display import ( # type:ignore # pylint: disable=import-error, import-outside-toplevel
|
8
|
+
Javascript,
|
9
|
+
display,
|
10
|
+
update_display,
|
11
|
+
)
|
12
|
+
except ImportError as error:
|
13
|
+
raise Exception('Unexpected environment. This function can only be called from a notebook.') from error
|
14
|
+
|
15
|
+
display_id = str(uuid.uuid4())
|
16
|
+
display(Javascript(f'window.open("{url_to_open}");'), display_id=display_id)
|
17
|
+
time.sleep(1)
|
18
|
+
update_display(Javascript(''), display_id=display_id)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import json
|
2
|
+
import re
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from biolib import api
|
6
|
+
from biolib._internal.runtime import BioLibRuntimeError, BioLibRuntimeNotRecognizedError, RuntimeJobDataDict
|
7
|
+
from biolib.typing_utils import cast
|
8
|
+
|
9
|
+
|
10
|
+
class Runtime:
|
11
|
+
_job_data: Optional[RuntimeJobDataDict] = None
|
12
|
+
|
13
|
+
@staticmethod
|
14
|
+
def check_is_environment_biolib_app() -> bool:
|
15
|
+
return bool(Runtime._try_to_get_job_data())
|
16
|
+
|
17
|
+
@staticmethod
|
18
|
+
def get_job_id() -> str:
|
19
|
+
return Runtime._get_job_data()['job_uuid']
|
20
|
+
|
21
|
+
@staticmethod
|
22
|
+
def get_job_auth_token() -> str:
|
23
|
+
return Runtime._get_job_data()['job_auth_token']
|
24
|
+
|
25
|
+
@staticmethod
|
26
|
+
def get_job_requested_machine() -> str:
|
27
|
+
return Runtime._get_job_data()['job_requested_machine']
|
28
|
+
|
29
|
+
@staticmethod
|
30
|
+
def get_app_uri() -> str:
|
31
|
+
return Runtime._get_job_data()['app_uri']
|
32
|
+
|
33
|
+
@staticmethod
|
34
|
+
def get_secret(secret_name: str) -> bytes:
|
35
|
+
assert re.match(
|
36
|
+
'^[a-zA-Z0-9_-]*$', secret_name
|
37
|
+
), 'Secret name can only contain alphanumeric characters and dashes or underscores '
|
38
|
+
try:
|
39
|
+
with open(f'/biolib/secrets/{secret_name}', 'rb') as file:
|
40
|
+
return file.read()
|
41
|
+
except BaseException as error:
|
42
|
+
raise BioLibRuntimeError(f'Unable to get system secret: {secret_name}') from error
|
43
|
+
|
44
|
+
@staticmethod
|
45
|
+
def set_main_result_prefix(result_prefix: str) -> None:
|
46
|
+
job_data = Runtime._get_job_data()
|
47
|
+
api.client.patch(
|
48
|
+
data={'result_name_prefix': result_prefix},
|
49
|
+
headers={'Job-Auth-Token': job_data['job_auth_token']},
|
50
|
+
path=f"/jobs/{job_data['job_uuid']}/main_result/",
|
51
|
+
)
|
52
|
+
|
53
|
+
@staticmethod
|
54
|
+
def create_result_note(note: str) -> None:
|
55
|
+
job_id = Runtime.get_job_id()
|
56
|
+
# Note: Authentication is added by app caller proxy in compute node
|
57
|
+
api.client.post(data={'note': note}, path=f'/jobs/{job_id}/notes/')
|
58
|
+
|
59
|
+
@staticmethod
|
60
|
+
def _try_to_get_job_data() -> Optional[RuntimeJobDataDict]:
|
61
|
+
if not Runtime._job_data:
|
62
|
+
try:
|
63
|
+
with open('/biolib/secrets/biolib_system_secret') as file:
|
64
|
+
job_data: RuntimeJobDataDict = json.load(file)
|
65
|
+
except BaseException:
|
66
|
+
return None
|
67
|
+
|
68
|
+
if not job_data['version'].startswith('1.'):
|
69
|
+
raise BioLibRuntimeError(f"Unexpected system secret version {job_data['version']} expected 1.x.x")
|
70
|
+
|
71
|
+
Runtime._job_data = job_data
|
72
|
+
|
73
|
+
return cast(RuntimeJobDataDict, Runtime._job_data)
|
74
|
+
|
75
|
+
@staticmethod
|
76
|
+
def _get_job_data() -> RuntimeJobDataDict:
|
77
|
+
job_data = Runtime._try_to_get_job_data()
|
78
|
+
if not job_data:
|
79
|
+
raise BioLibRuntimeNotRecognizedError() from None
|
80
|
+
return job_data
|
biolib/api/__init__.py
CHANGED
biolib/api/client.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
from urllib.parse import
|
1
|
+
from urllib.parse import urlencode, urljoin
|
2
2
|
|
3
|
-
|
3
|
+
import importlib_metadata
|
4
|
+
|
5
|
+
from biolib._internal.http_client import HttpClient, HttpResponse
|
4
6
|
from biolib.biolib_api_client import BiolibApiClient as DeprecatedApiClient
|
5
|
-
from biolib.
|
7
|
+
from biolib.typing_utils import Dict, Optional, Union, cast
|
6
8
|
|
7
9
|
OptionalHeaders = Union[
|
8
10
|
Optional[Dict[str, str]],
|
@@ -10,14 +12,24 @@ OptionalHeaders = Union[
|
|
10
12
|
]
|
11
13
|
|
12
14
|
|
15
|
+
def _get_biolib_package_version() -> str:
|
16
|
+
# try fetching version, if it fails (usually when in dev), add default
|
17
|
+
try:
|
18
|
+
return cast(str, importlib_metadata.version('pybiolib'))
|
19
|
+
except importlib_metadata.PackageNotFoundError:
|
20
|
+
return '0.0.0'
|
21
|
+
|
22
|
+
|
13
23
|
class ApiClient(HttpClient):
|
24
|
+
_biolib_package_version: str = _get_biolib_package_version()
|
25
|
+
|
14
26
|
def get(
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
27
|
+
self,
|
28
|
+
path: str,
|
29
|
+
params: Optional[Dict[str, Union[str, int]]] = None,
|
30
|
+
headers: OptionalHeaders = None,
|
31
|
+
authenticate: bool = True,
|
32
|
+
retries: int = 10,
|
21
33
|
) -> HttpResponse:
|
22
34
|
return self.request(
|
23
35
|
headers=self._get_headers(opt_headers=headers, authenticate=authenticate),
|
@@ -27,12 +39,12 @@ class ApiClient(HttpClient):
|
|
27
39
|
)
|
28
40
|
|
29
41
|
def post(
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
42
|
+
self,
|
43
|
+
path: str,
|
44
|
+
data: Optional[Union[Dict, bytes]] = None,
|
45
|
+
headers: OptionalHeaders = None,
|
46
|
+
authenticate: bool = True,
|
47
|
+
retries: int = 50, # TODO: reduce this back to 5 when timeout errors have been solved
|
36
48
|
) -> HttpResponse:
|
37
49
|
return self.request(
|
38
50
|
data=data,
|
@@ -42,13 +54,20 @@ class ApiClient(HttpClient):
|
|
42
54
|
url=self._get_absolute_url(path=path, query_params=None),
|
43
55
|
)
|
44
56
|
|
45
|
-
def patch(
|
57
|
+
def patch(
|
58
|
+
self,
|
59
|
+
path: str,
|
60
|
+
data: Dict,
|
61
|
+
headers: OptionalHeaders = None,
|
62
|
+
retries: int = 5,
|
63
|
+
params: Optional[Dict[str, Union[str, int]]] = None,
|
64
|
+
) -> HttpResponse:
|
46
65
|
return self.request(
|
47
66
|
data=data,
|
48
67
|
headers=self._get_headers(opt_headers=headers, authenticate=True),
|
49
68
|
method='PATCH',
|
50
69
|
retries=retries,
|
51
|
-
url=self._get_absolute_url(path=path, query_params=
|
70
|
+
url=self._get_absolute_url(path=path, query_params=params),
|
52
71
|
)
|
53
72
|
|
54
73
|
@staticmethod
|
@@ -67,6 +86,9 @@ class ApiClient(HttpClient):
|
|
67
86
|
if access_token and authenticate:
|
68
87
|
headers['Authorization'] = f'Bearer {access_token}'
|
69
88
|
|
89
|
+
headers['client-type'] = 'biolib-python'
|
90
|
+
headers['client-version'] = ApiClient._biolib_package_version
|
91
|
+
|
70
92
|
return headers
|
71
93
|
|
72
94
|
@staticmethod
|
biolib/app/app.py
CHANGED
@@ -1,29 +1,26 @@
|
|
1
|
-
import os
|
2
1
|
import io
|
3
|
-
import random
|
4
2
|
import json
|
3
|
+
import os
|
4
|
+
import random
|
5
5
|
import string
|
6
|
-
|
7
6
|
from pathlib import Path
|
7
|
+
|
8
8
|
from biolib import utils
|
9
|
-
from biolib.
|
10
|
-
from biolib.compute_node.job_worker.job_worker import JobWorker
|
11
|
-
from biolib.experiments.experiment import Experiment
|
12
|
-
from biolib.jobs import Job
|
13
|
-
from biolib.typing_utils import Optional, cast
|
14
|
-
from biolib.biolib_api_client import CreatedJobDict, JobState
|
15
|
-
from biolib.jobs.types import JobDict
|
9
|
+
from biolib.biolib_api_client import JobState
|
16
10
|
from biolib.biolib_api_client.app_types import App, AppVersion
|
17
|
-
from biolib.biolib_api_client.biolib_job_api import BiolibJobApi
|
18
11
|
from biolib.biolib_api_client.biolib_app_api import BiolibAppApi
|
12
|
+
from biolib.biolib_api_client.biolib_job_api import BiolibJobApi
|
19
13
|
from biolib.biolib_binary_format import ModuleInput
|
20
14
|
from biolib.biolib_errors import BioLibError
|
21
15
|
from biolib.biolib_logging import logger
|
16
|
+
from biolib.compute_node.job_worker.job_worker import JobWorker
|
17
|
+
from biolib.experiments.experiment import Experiment
|
18
|
+
from biolib.jobs import Job
|
19
|
+
from biolib.typing_utils import Optional
|
22
20
|
from biolib.utils.app_uri import parse_app_uri
|
23
21
|
|
24
22
|
|
25
23
|
class BioLibApp:
|
26
|
-
|
27
24
|
def __init__(self, uri: str):
|
28
25
|
app_response = BiolibAppApi.get_by_uri(uri)
|
29
26
|
self._app: App = app_response['app']
|
@@ -48,17 +45,18 @@ class BioLibApp:
|
|
48
45
|
return self._app_version
|
49
46
|
|
50
47
|
def cli(
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
48
|
+
self,
|
49
|
+
args=None,
|
50
|
+
stdin=None,
|
51
|
+
files=None,
|
52
|
+
override_command=False,
|
53
|
+
machine='',
|
54
|
+
blocking: bool = True,
|
55
|
+
experiment_id: Optional[str] = None,
|
56
|
+
result_prefix: Optional[str] = None,
|
57
|
+
timeout: Optional[int] = None,
|
58
|
+
notify: bool = False,
|
59
|
+
machine_count: Optional[int] = None,
|
62
60
|
) -> Job:
|
63
61
|
if not experiment_id:
|
64
62
|
experiment = Experiment.get_experiment_in_context()
|
@@ -78,7 +76,9 @@ class BioLibApp:
|
|
78
76
|
|
79
77
|
return self._run_locally(module_input_serialized)
|
80
78
|
|
81
|
-
job =
|
79
|
+
job = Job._start_job_in_cloud( # pylint: disable=protected-access
|
80
|
+
app_uri=self._app_uri,
|
81
|
+
app_version_uuid=self._app_version['public_id'],
|
82
82
|
experiment_id=experiment_id,
|
83
83
|
machine=machine,
|
84
84
|
module_input_serialized=module_input_serialized,
|
@@ -86,6 +86,7 @@ class BioLibApp:
|
|
86
86
|
override_command=override_command,
|
87
87
|
result_prefix=result_prefix,
|
88
88
|
timeout=timeout,
|
89
|
+
requested_machine_count=machine_count,
|
89
90
|
)
|
90
91
|
if blocking:
|
91
92
|
# TODO: Deprecate utils.STREAM_STDOUT and always stream logs by simply calling job.stream_logs()
|
@@ -93,8 +94,8 @@ class BioLibApp:
|
|
93
94
|
utils.STREAM_STDOUT = True
|
94
95
|
|
95
96
|
enable_print = bool(
|
96
|
-
utils.STREAM_STDOUT
|
97
|
-
(self._app_version.get('main_output_file') or self._app_version.get('stdout_render_type') == 'text')
|
97
|
+
utils.STREAM_STDOUT
|
98
|
+
and (self._app_version.get('main_output_file') or self._app_version.get('stdout_render_type') == 'text')
|
98
99
|
)
|
99
100
|
job._stream_logs(enable_print=enable_print) # pylint: disable=protected-access
|
100
101
|
|
@@ -108,11 +109,11 @@ class BioLibApp:
|
|
108
109
|
self.cli()
|
109
110
|
|
110
111
|
else:
|
111
|
-
raise BioLibError(
|
112
|
+
raise BioLibError("""
|
112
113
|
Calling an app directly with app() is currently being reworked.
|
113
114
|
To use the previous functionality, please call app.cli() instead.
|
114
115
|
Example: "app.cli('--help')"
|
115
|
-
|
116
|
+
""")
|
116
117
|
|
117
118
|
@staticmethod
|
118
119
|
def _get_serialized_module_input(args=None, stdin=None, files=None) -> bytes:
|
@@ -142,9 +143,9 @@ Example: "app.cli('--help')"
|
|
142
143
|
args[idx] = Path(arg).name
|
143
144
|
|
144
145
|
# support --myarg=file.txt
|
145
|
-
elif os.path.isfile(arg.split(
|
146
|
-
files.append(arg.split(
|
147
|
-
args[idx] = arg.split(
|
146
|
+
elif os.path.isfile(arg.split('=')[-1]) or os.path.isdir(arg.split('=')[-1]):
|
147
|
+
files.append(arg.split('=')[-1])
|
148
|
+
args[idx] = arg.split('=')[0] + '=' + Path(arg.split('=')[-1]).name
|
148
149
|
else:
|
149
150
|
pass # a normal string arg was given
|
150
151
|
else:
|
@@ -154,7 +155,7 @@ Example: "app.cli('--help')"
|
|
154
155
|
elif isinstance(arg, io.BytesIO):
|
155
156
|
file_data = arg.getvalue()
|
156
157
|
else:
|
157
|
-
raise Exception(f
|
158
|
+
raise Exception(f'Unexpected type of argument: {arg}')
|
158
159
|
files_dict[f'/{tmp_filename}'] = file_data
|
159
160
|
args[idx] = tmp_filename
|
160
161
|
|
@@ -192,48 +193,10 @@ Example: "app.cli('--help')"
|
|
192
193
|
)
|
193
194
|
return module_input_serialized
|
194
195
|
|
195
|
-
def _start_in_cloud(
|
196
|
-
self,
|
197
|
-
module_input_serialized: bytes,
|
198
|
-
override_command: bool = False,
|
199
|
-
machine: Optional[str] = None,
|
200
|
-
experiment_id: Optional[str] = None,
|
201
|
-
result_prefix: Optional[str] = None,
|
202
|
-
timeout: Optional[int] = None,
|
203
|
-
notify: bool = False,
|
204
|
-
) -> Job:
|
205
|
-
if len(module_input_serialized) < 500_000:
|
206
|
-
_job_dict = BiolibJobApi.create_job_with_data(
|
207
|
-
app_resource_name_prefix=parse_app_uri(self._app_uri)['resource_name_prefix'],
|
208
|
-
app_version_uuid=self._app_version['public_id'],
|
209
|
-
arguments_override_command=override_command,
|
210
|
-
experiment_uuid=experiment_id,
|
211
|
-
module_input_serialized=module_input_serialized,
|
212
|
-
notify=notify,
|
213
|
-
requested_machine=machine,
|
214
|
-
requested_timeout_seconds=timeout,
|
215
|
-
result_name_prefix=result_prefix,
|
216
|
-
)
|
217
|
-
return Job(cast(JobDict, _job_dict))
|
218
|
-
|
219
|
-
job_dict: CreatedJobDict = BiolibJobApi.create(
|
220
|
-
app_resource_name_prefix=parse_app_uri(self._app_uri)['resource_name_prefix'],
|
221
|
-
app_version_id=self._app_version['public_id'],
|
222
|
-
experiment_uuid=experiment_id,
|
223
|
-
machine=machine,
|
224
|
-
notify=notify,
|
225
|
-
override_command=override_command,
|
226
|
-
timeout=timeout,
|
227
|
-
)
|
228
|
-
JobStorage.upload_module_input(job=job_dict, module_input_serialized=module_input_serialized)
|
229
|
-
cloud_job = BiolibJobApi.create_cloud_job(job_id=job_dict['public_id'], result_name_prefix=result_prefix)
|
230
|
-
logger.debug(f"Cloud: Job created with id {cloud_job['public_id']}")
|
231
|
-
return Job(cast(JobDict, job_dict))
|
232
|
-
|
233
196
|
def _run_locally(self, module_input_serialized: bytes) -> Job:
|
234
197
|
job_dict = BiolibJobApi.create(
|
235
198
|
app_version_id=self._app_version['public_id'],
|
236
|
-
app_resource_name_prefix=parse_app_uri(self._app_uri)['resource_name_prefix']
|
199
|
+
app_resource_name_prefix=parse_app_uri(self._app_uri)['resource_name_prefix'],
|
237
200
|
)
|
238
201
|
job = Job(job_dict)
|
239
202
|
|
@@ -263,7 +226,12 @@ Example: "app.cli('--help')"
|
|
263
226
|
if not key.startswith('--'):
|
264
227
|
key = f'--{key}'
|
265
228
|
|
266
|
-
args.
|
229
|
+
args.append(key)
|
230
|
+
if isinstance(value, list):
|
231
|
+
# TODO: only do this if argument key is of type file list
|
232
|
+
args.extend(value)
|
233
|
+
else:
|
234
|
+
args.append(value)
|
267
235
|
|
268
236
|
return self.cli(args, **biolib_kwargs)
|
269
237
|
|