pybiolib 1.1.1881__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 +11 -4
- biolib/_data_record/data_record.py +278 -0
- biolib/_internal/data_record/__init__.py +1 -1
- biolib/_internal/data_record/data_record.py +95 -151
- biolib/_internal/data_record/remote_storage_endpoint.py +18 -7
- 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 +29 -9
- 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 +1 -1
- biolib/_internal/runtime.py +2 -56
- 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/_runtime/runtime.py +80 -0
- biolib/api/__init__.py +1 -0
- biolib/api/client.py +39 -17
- biolib/app/app.py +34 -71
- biolib/biolib_api_client/api_client.py +9 -2
- biolib/biolib_api_client/app_types.py +2 -2
- 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 +23 -3
- biolib/cli/auth.py +1 -1
- biolib/cli/data_record.py +43 -6
- biolib/cli/lfs.py +10 -6
- biolib/compute_node/cloud_utils/cloud_utils.py +13 -16
- biolib/compute_node/job_worker/executors/docker_executor.py +126 -108
- biolib/compute_node/job_worker/job_storage.py +3 -4
- biolib/compute_node/job_worker/job_worker.py +25 -15
- biolib/compute_node/remote_host_proxy.py +61 -84
- biolib/compute_node/webserver/webserver_types.py +0 -1
- biolib/experiments/experiment.py +75 -44
- biolib/jobs/job.py +98 -19
- biolib/jobs/job_result.py +46 -21
- biolib/jobs/types.py +1 -1
- biolib/runtime/__init__.py +2 -1
- biolib/sdk/__init__.py +18 -7
- biolib/typing_utils.py +2 -7
- biolib/user/sign_in.py +2 -2
- biolib/utils/seq_util.py +38 -35
- {pybiolib-1.1.1881.dist-info → pybiolib-1.1.2193.dist-info}/METADATA +1 -1
- {pybiolib-1.1.1881.dist-info → pybiolib-1.1.2193.dist-info}/RECORD +55 -44
- biolib/experiments/types.py +0 -9
- biolib/lfs/__init__.py +0 -4
- biolib/lfs/utils.py +0 -153
- /biolib/{lfs → _internal/lfs}/cache.py +0 -0
- {pybiolib-1.1.1881.dist-info → pybiolib-1.1.2193.dist-info}/LICENSE +0 -0
- {pybiolib-1.1.1881.dist-info → pybiolib-1.1.2193.dist-info}/WHEEL +0 -0
- {pybiolib-1.1.1881.dist-info → pybiolib-1.1.2193.dist-info}/entry_points.txt +0 -0
@@ -6,12 +6,12 @@ import rich.progress
|
|
6
6
|
import yaml
|
7
7
|
|
8
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
11
|
from biolib.biolib_api_client.biolib_app_api import BiolibAppApi
|
11
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.lfs.utils import get_files_and_size_of_directory, get_iterable_zip_stream
|
15
15
|
from biolib.typing_utils import Iterable, Optional, Set, TypedDict
|
16
16
|
|
17
17
|
REGEX_MARKDOWN_INLINE_IMAGE = re.compile(r'!\[(?P<alt>.*)\]\((?P<src>.*)\)')
|
biolib/_internal/runtime.py
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
import
|
2
|
-
|
3
|
-
from biolib import api
|
4
|
-
from biolib.typing_utils import Optional, TypedDict, cast
|
1
|
+
from biolib.typing_utils import TypedDict
|
5
2
|
|
6
3
|
|
7
4
|
class RuntimeJobDataDict(TypedDict):
|
@@ -9,6 +6,7 @@ class RuntimeJobDataDict(TypedDict):
|
|
9
6
|
job_requested_machine: str
|
10
7
|
job_uuid: str
|
11
8
|
job_auth_token: str
|
9
|
+
app_uri: str
|
12
10
|
|
13
11
|
|
14
12
|
class BioLibRuntimeError(Exception):
|
@@ -19,55 +17,3 @@ class BioLibRuntimeNotRecognizedError(BioLibRuntimeError):
|
|
19
17
|
def __init__(self, message='The runtime is not recognized as a BioLib app'):
|
20
18
|
self.message = message
|
21
19
|
super().__init__(self.message)
|
22
|
-
|
23
|
-
|
24
|
-
class Runtime:
|
25
|
-
_job_data: Optional[RuntimeJobDataDict] = None
|
26
|
-
|
27
|
-
@staticmethod
|
28
|
-
def check_is_environment_biolib_app() -> bool:
|
29
|
-
return bool(Runtime._try_to_get_job_data())
|
30
|
-
|
31
|
-
@staticmethod
|
32
|
-
def get_job_id() -> str:
|
33
|
-
return Runtime._get_job_data()['job_uuid']
|
34
|
-
|
35
|
-
@staticmethod
|
36
|
-
def get_job_auth_token() -> str:
|
37
|
-
return Runtime._get_job_data()['job_auth_token']
|
38
|
-
|
39
|
-
@staticmethod
|
40
|
-
def get_job_requested_machine() -> str:
|
41
|
-
return Runtime._get_job_data()['job_requested_machine']
|
42
|
-
|
43
|
-
@staticmethod
|
44
|
-
def set_main_result_prefix(result_prefix: str) -> None:
|
45
|
-
job_data = Runtime._get_job_data()
|
46
|
-
api.client.patch(
|
47
|
-
data={'result_name_prefix': result_prefix},
|
48
|
-
headers={'Job-Auth-Token': job_data['job_auth_token']},
|
49
|
-
path=f"/jobs/{job_data['job_uuid']}/main_result/",
|
50
|
-
)
|
51
|
-
|
52
|
-
@staticmethod
|
53
|
-
def _try_to_get_job_data() -> Optional[RuntimeJobDataDict]:
|
54
|
-
if not Runtime._job_data:
|
55
|
-
try:
|
56
|
-
with open('/biolib/secrets/biolib_system_secret') as file:
|
57
|
-
job_data: RuntimeJobDataDict = json.load(file)
|
58
|
-
except BaseException:
|
59
|
-
return None
|
60
|
-
|
61
|
-
if not job_data['version'].startswith('1.'):
|
62
|
-
raise BioLibRuntimeError(f"Unexpected system secret version {job_data['version']} expected 1.x.x")
|
63
|
-
|
64
|
-
Runtime._job_data = job_data
|
65
|
-
|
66
|
-
return cast(RuntimeJobDataDict, Runtime._job_data)
|
67
|
-
|
68
|
-
@staticmethod
|
69
|
-
def _get_job_data() -> RuntimeJobDataDict:
|
70
|
-
job_data = Runtime._try_to_get_job_data()
|
71
|
-
if not job_data:
|
72
|
-
raise BioLibRuntimeNotRecognizedError() from None
|
73
|
-
return job_data
|
@@ -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,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
|
|
@@ -6,7 +6,7 @@ import os
|
|
6
6
|
from datetime import datetime, timezone
|
7
7
|
from json.decoder import JSONDecodeError
|
8
8
|
|
9
|
-
from biolib.
|
9
|
+
from biolib._runtime.runtime import Runtime
|
10
10
|
from biolib._internal.http_client import HttpClient
|
11
11
|
from biolib.typing_utils import Optional
|
12
12
|
from biolib.biolib_errors import BioLibError
|
@@ -192,9 +192,16 @@ class BiolibApiClient:
|
|
192
192
|
api_client.refresh_access_token()
|
193
193
|
|
194
194
|
@staticmethod
|
195
|
-
def
|
195
|
+
def is_reauthentication_needed() -> bool:
|
196
196
|
api_client = BiolibApiClient.get()
|
197
197
|
if not api_client.is_signed_in and not Runtime.check_is_environment_biolib_app():
|
198
|
+
return True
|
199
|
+
else:
|
200
|
+
return False
|
201
|
+
|
202
|
+
@staticmethod
|
203
|
+
def assert_is_signed_in(authenticated_action_description: str) -> None:
|
204
|
+
if BiolibApiClient.is_reauthentication_needed():
|
198
205
|
raise BioLibError(
|
199
206
|
f'You must be signed in to {authenticated_action_description}. '
|
200
207
|
f'Please set the environment variable "BIOLIB_TOKEN"'
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
|
3
|
-
from biolib.typing_utils import TypedDict, List, Optional, Dict, Literal
|
4
3
|
from biolib.biolib_api_client.common_types import SemanticVersion
|
4
|
+
from biolib.typing_utils import Dict, List, Literal, Optional, TypedDict
|
5
5
|
|
6
6
|
|
7
7
|
class AppVersionSlim(SemanticVersion):
|
@@ -16,6 +16,7 @@ class AppVersion(AppVersionSlim):
|
|
16
16
|
source_code_license: str
|
17
17
|
stdout_render_type: Literal['text', 'markdown']
|
18
18
|
main_output_file: Optional[str]
|
19
|
+
app_uri: str
|
19
20
|
|
20
21
|
|
21
22
|
class App(TypedDict):
|
@@ -99,7 +100,6 @@ class _AppVersionOnJob(TypedDict):
|
|
99
100
|
|
100
101
|
class AppOnJob(TypedDict):
|
101
102
|
allow_client_side_execution: bool
|
102
|
-
can_push_data_record_for_user: bool
|
103
103
|
state: Literal['public', 'draft']
|
104
104
|
|
105
105
|
|
@@ -46,6 +46,7 @@ class BiolibJobApi:
|
|
46
46
|
experiment_uuid: Optional[str] = None,
|
47
47
|
timeout: Optional[int] = None,
|
48
48
|
notify: bool = False,
|
49
|
+
requested_machine_count: Optional[int] = None,
|
49
50
|
):
|
50
51
|
data = {
|
51
52
|
'app_version_id': app_version_id,
|
@@ -73,6 +74,9 @@ class BiolibJobApi:
|
|
73
74
|
'requested_machine': machine
|
74
75
|
})
|
75
76
|
|
77
|
+
if requested_machine_count:
|
78
|
+
data.update({'requested_machine_count': requested_machine_count})
|
79
|
+
|
76
80
|
if experiment_uuid:
|
77
81
|
data['experiment_uuid'] = experiment_uuid
|
78
82
|
|
@@ -156,6 +160,7 @@ class BiolibJobApi:
|
|
156
160
|
caller_job_uuid: Optional[str] = None,
|
157
161
|
requested_timeout_seconds: Optional[int] = None,
|
158
162
|
notify: bool = False,
|
163
|
+
requested_machine_count: Optional[int] = None,
|
159
164
|
) -> Dict:
|
160
165
|
job_dict: Dict = biolib.api.client.post(
|
161
166
|
path='/jobs/create_job_with_data/',
|
@@ -171,6 +176,7 @@ class BiolibJobApi:
|
|
171
176
|
'client-version': BIOLIB_PACKAGE_VERSION,
|
172
177
|
'experiment-uuid': experiment_uuid,
|
173
178
|
'requested-machine': requested_machine,
|
179
|
+
'requested-machine-count': str(requested_machine_count) if requested_machine_count else None,
|
174
180
|
'result-name-prefix': result_name_prefix,
|
175
181
|
'requested-timeout-seconds': str(requested_timeout_seconds) if requested_timeout_seconds else None,
|
176
182
|
'notify': 'true' if notify else 'false',
|
@@ -1,9 +1,8 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
|
3
|
-
from biolib.compute_node.webserver.webserver_types import ComputeNodeInfo
|
4
|
-
from biolib.typing_utils import TypedDict, Optional, List
|
5
|
-
|
6
3
|
from biolib.biolib_api_client.app_types import AppVersionOnJob, RemoteHost
|
4
|
+
from biolib.compute_node.webserver.webserver_types import ComputeNodeInfo
|
5
|
+
from biolib.typing_utils import List, Optional, TypedDict
|
7
6
|
|
8
7
|
|
9
8
|
class JobState(Enum):
|
@@ -15,6 +14,7 @@ class JobState(Enum):
|
|
15
14
|
|
16
15
|
|
17
16
|
class _Job(TypedDict):
|
17
|
+
app_uri: str
|
18
18
|
app_version: AppVersionOnJob
|
19
19
|
arguments_override_command: bool
|
20
20
|
auth_token: str
|
@@ -22,10 +22,10 @@ class _Job(TypedDict):
|
|
22
22
|
created_at: str
|
23
23
|
federated_job_uuid: Optional[str]
|
24
24
|
public_id: str
|
25
|
-
uuid: str
|
26
25
|
remote_hosts_with_warning: List[RemoteHost]
|
27
26
|
state: str
|
28
27
|
user_id: Optional[str]
|
28
|
+
uuid: str
|
29
29
|
|
30
30
|
|
31
31
|
# type optional keys with total=False
|