pybiolib 0.2.951__py3-none-any.whl → 1.2.1890__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 +357 -11
- biolib/_data_record/data_record.py +380 -0
- biolib/_index/__init__.py +0 -0
- biolib/_index/index.py +55 -0
- biolib/_index/query_result.py +103 -0
- biolib/_internal/__init__.py +0 -0
- biolib/_internal/add_copilot_prompts.py +58 -0
- biolib/_internal/add_gui_files.py +81 -0
- biolib/_internal/data_record/__init__.py +1 -0
- biolib/_internal/data_record/data_record.py +85 -0
- biolib/_internal/data_record/push_data.py +116 -0
- biolib/_internal/data_record/remote_storage_endpoint.py +43 -0
- biolib/_internal/errors.py +5 -0
- biolib/_internal/file_utils.py +125 -0
- biolib/_internal/fuse_mount/__init__.py +1 -0
- biolib/_internal/fuse_mount/experiment_fuse_mount.py +209 -0
- biolib/_internal/http_client.py +159 -0
- biolib/_internal/lfs/__init__.py +1 -0
- biolib/_internal/lfs/cache.py +51 -0
- biolib/_internal/libs/__init__.py +1 -0
- biolib/_internal/libs/fusepy/__init__.py +1257 -0
- biolib/_internal/push_application.py +488 -0
- biolib/_internal/runtime.py +22 -0
- biolib/_internal/string_utils.py +13 -0
- biolib/_internal/templates/__init__.py +1 -0
- biolib/_internal/templates/copilot_template/.github/instructions/general-app-knowledge.instructions.md +10 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-general.instructions.md +20 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-python.instructions.md +16 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_app_inputs.prompt.md +11 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_run_apps.prompt.md +12 -0
- biolib/_internal/templates/dashboard_template/.biolib/config.yml +5 -0
- biolib/_internal/templates/github_workflow_template/.github/workflows/biolib.yml +21 -0
- biolib/_internal/templates/gitignore_template/.gitignore +10 -0
- biolib/_internal/templates/gui_template/.yarnrc.yml +1 -0
- biolib/_internal/templates/gui_template/App.tsx +53 -0
- biolib/_internal/templates/gui_template/Dockerfile +27 -0
- biolib/_internal/templates/gui_template/biolib-sdk.ts +82 -0
- biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
- biolib/_internal/templates/gui_template/index.css +5 -0
- biolib/_internal/templates/gui_template/index.html +13 -0
- biolib/_internal/templates/gui_template/index.tsx +10 -0
- biolib/_internal/templates/gui_template/package.json +27 -0
- biolib/_internal/templates/gui_template/tsconfig.json +24 -0
- biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +50 -0
- biolib/_internal/templates/gui_template/vite.config.mts +10 -0
- biolib/_internal/templates/init_template/.biolib/config.yml +19 -0
- biolib/_internal/templates/init_template/Dockerfile +14 -0
- biolib/_internal/templates/init_template/requirements.txt +1 -0
- biolib/_internal/templates/init_template/run.py +12 -0
- biolib/_internal/templates/init_template/run.sh +4 -0
- biolib/_internal/templates/templates.py +25 -0
- biolib/_internal/tree_utils.py +106 -0
- biolib/_internal/utils/__init__.py +65 -0
- biolib/_internal/utils/auth.py +46 -0
- biolib/_internal/utils/job_url.py +33 -0
- biolib/_internal/utils/multinode.py +263 -0
- biolib/_runtime/runtime.py +157 -0
- biolib/_session/session.py +44 -0
- biolib/_shared/__init__.py +0 -0
- biolib/_shared/types/__init__.py +74 -0
- biolib/_shared/types/account.py +12 -0
- biolib/_shared/types/account_member.py +8 -0
- biolib/_shared/types/app.py +9 -0
- biolib/_shared/types/data_record.py +40 -0
- biolib/_shared/types/experiment.py +32 -0
- biolib/_shared/types/file_node.py +17 -0
- biolib/_shared/types/push.py +6 -0
- biolib/_shared/types/resource.py +37 -0
- biolib/_shared/types/resource_deploy_key.py +11 -0
- biolib/_shared/types/resource_permission.py +14 -0
- biolib/_shared/types/resource_version.py +19 -0
- biolib/_shared/types/result.py +14 -0
- biolib/_shared/types/typing.py +10 -0
- biolib/_shared/types/user.py +19 -0
- biolib/_shared/utils/__init__.py +7 -0
- biolib/_shared/utils/resource_uri.py +75 -0
- biolib/api/__init__.py +6 -0
- biolib/api/client.py +168 -0
- biolib/app/app.py +252 -49
- biolib/app/search_apps.py +45 -0
- biolib/biolib_api_client/api_client.py +126 -31
- biolib/biolib_api_client/app_types.py +24 -4
- biolib/biolib_api_client/auth.py +31 -8
- biolib/biolib_api_client/biolib_app_api.py +147 -52
- biolib/biolib_api_client/biolib_job_api.py +161 -141
- biolib/biolib_api_client/job_types.py +21 -5
- biolib/biolib_api_client/lfs_types.py +7 -23
- biolib/biolib_api_client/user_state.py +56 -0
- biolib/biolib_binary_format/__init__.py +1 -4
- biolib/biolib_binary_format/file_in_container.py +105 -0
- biolib/biolib_binary_format/module_input.py +24 -7
- biolib/biolib_binary_format/module_output_v2.py +149 -0
- biolib/biolib_binary_format/remote_endpoints.py +34 -0
- biolib/biolib_binary_format/remote_stream_seeker.py +59 -0
- biolib/biolib_binary_format/saved_job.py +3 -2
- biolib/biolib_binary_format/{attestation_document.py → stdout_and_stderr.py} +8 -8
- biolib/biolib_binary_format/system_status_update.py +3 -2
- biolib/biolib_binary_format/utils.py +175 -0
- biolib/biolib_docker_client/__init__.py +11 -2
- biolib/biolib_errors.py +36 -0
- biolib/biolib_logging.py +27 -10
- biolib/cli/__init__.py +38 -0
- biolib/cli/auth.py +46 -0
- biolib/cli/data_record.py +164 -0
- biolib/cli/index.py +32 -0
- biolib/cli/init.py +421 -0
- biolib/cli/lfs.py +101 -0
- biolib/cli/push.py +50 -0
- biolib/cli/run.py +63 -0
- biolib/cli/runtime.py +14 -0
- biolib/cli/sdk.py +16 -0
- biolib/cli/start.py +56 -0
- biolib/compute_node/cloud_utils/cloud_utils.py +110 -161
- biolib/compute_node/job_worker/cache_state.py +66 -88
- biolib/compute_node/job_worker/cache_types.py +1 -6
- biolib/compute_node/job_worker/docker_image_cache.py +112 -37
- biolib/compute_node/job_worker/executors/__init__.py +0 -3
- biolib/compute_node/job_worker/executors/docker_executor.py +532 -199
- biolib/compute_node/job_worker/executors/docker_types.py +9 -1
- biolib/compute_node/job_worker/executors/types.py +19 -9
- biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +30 -0
- biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +3 -5
- biolib/compute_node/job_worker/job_storage.py +108 -0
- biolib/compute_node/job_worker/job_worker.py +397 -212
- biolib/compute_node/job_worker/large_file_system.py +87 -38
- biolib/compute_node/job_worker/network_alloc.py +99 -0
- biolib/compute_node/job_worker/network_buffer.py +240 -0
- biolib/compute_node/job_worker/utilization_reporter_thread.py +197 -0
- biolib/compute_node/job_worker/utils.py +9 -24
- biolib/compute_node/remote_host_proxy.py +400 -98
- biolib/compute_node/utils.py +31 -9
- biolib/compute_node/webserver/compute_node_results_proxy.py +189 -0
- biolib/compute_node/webserver/proxy_utils.py +28 -0
- biolib/compute_node/webserver/webserver.py +130 -44
- biolib/compute_node/webserver/webserver_types.py +2 -6
- biolib/compute_node/webserver/webserver_utils.py +77 -12
- biolib/compute_node/webserver/worker_thread.py +183 -42
- biolib/experiments/__init__.py +0 -0
- biolib/experiments/experiment.py +356 -0
- biolib/jobs/__init__.py +1 -0
- biolib/jobs/job.py +741 -0
- biolib/jobs/job_result.py +185 -0
- biolib/jobs/types.py +50 -0
- biolib/py.typed +0 -0
- biolib/runtime/__init__.py +14 -0
- biolib/sdk/__init__.py +91 -0
- biolib/tables.py +34 -0
- biolib/typing_utils.py +2 -7
- biolib/user/__init__.py +1 -0
- biolib/user/sign_in.py +54 -0
- biolib/utils/__init__.py +162 -0
- biolib/utils/cache_state.py +94 -0
- biolib/utils/multipart_uploader.py +194 -0
- biolib/utils/seq_util.py +150 -0
- biolib/utils/zip/remote_zip.py +640 -0
- pybiolib-1.2.1890.dist-info/METADATA +41 -0
- pybiolib-1.2.1890.dist-info/RECORD +177 -0
- {pybiolib-0.2.951.dist-info → pybiolib-1.2.1890.dist-info}/WHEEL +1 -1
- pybiolib-1.2.1890.dist-info/entry_points.txt +2 -0
- README.md +0 -17
- biolib/app/app_result.py +0 -68
- biolib/app/utils.py +0 -62
- biolib/biolib-js/0-biolib.worker.js +0 -1
- biolib/biolib-js/1-biolib.worker.js +0 -1
- biolib/biolib-js/2-biolib.worker.js +0 -1
- biolib/biolib-js/3-biolib.worker.js +0 -1
- biolib/biolib-js/4-biolib.worker.js +0 -1
- biolib/biolib-js/5-biolib.worker.js +0 -1
- biolib/biolib-js/6-biolib.worker.js +0 -1
- biolib/biolib-js/index.html +0 -10
- biolib/biolib-js/main-biolib.js +0 -1
- biolib/biolib_api_client/biolib_account_api.py +0 -21
- biolib/biolib_api_client/biolib_large_file_system_api.py +0 -108
- biolib/biolib_binary_format/aes_encrypted_package.py +0 -42
- biolib/biolib_binary_format/module_output.py +0 -58
- biolib/biolib_binary_format/rsa_encrypted_aes_package.py +0 -57
- biolib/biolib_push.py +0 -114
- biolib/cli.py +0 -203
- biolib/cli_utils.py +0 -273
- biolib/compute_node/cloud_utils/enclave_parent_types.py +0 -7
- biolib/compute_node/enclave/__init__.py +0 -2
- biolib/compute_node/enclave/enclave_remote_hosts.py +0 -53
- biolib/compute_node/enclave/nitro_secure_module_utils.py +0 -64
- biolib/compute_node/job_worker/executors/base_executor.py +0 -18
- biolib/compute_node/job_worker/executors/pyppeteer_executor.py +0 -173
- biolib/compute_node/job_worker/executors/remote/__init__.py +0 -1
- biolib/compute_node/job_worker/executors/remote/nitro_enclave_utils.py +0 -81
- biolib/compute_node/job_worker/executors/remote/remote_executor.py +0 -51
- biolib/lfs.py +0 -196
- biolib/pyppeteer/.circleci/config.yml +0 -100
- biolib/pyppeteer/.coveragerc +0 -3
- biolib/pyppeteer/.gitignore +0 -89
- biolib/pyppeteer/.pre-commit-config.yaml +0 -28
- biolib/pyppeteer/CHANGES.md +0 -253
- biolib/pyppeteer/CONTRIBUTING.md +0 -26
- biolib/pyppeteer/LICENSE +0 -12
- biolib/pyppeteer/README.md +0 -137
- biolib/pyppeteer/docs/Makefile +0 -177
- biolib/pyppeteer/docs/_static/custom.css +0 -28
- biolib/pyppeteer/docs/_templates/layout.html +0 -10
- biolib/pyppeteer/docs/changes.md +0 -1
- biolib/pyppeteer/docs/conf.py +0 -299
- biolib/pyppeteer/docs/index.md +0 -21
- biolib/pyppeteer/docs/make.bat +0 -242
- biolib/pyppeteer/docs/reference.md +0 -211
- biolib/pyppeteer/docs/server.py +0 -60
- biolib/pyppeteer/poetry.lock +0 -1699
- biolib/pyppeteer/pyppeteer/__init__.py +0 -135
- biolib/pyppeteer/pyppeteer/accessibility.py +0 -286
- biolib/pyppeteer/pyppeteer/browser.py +0 -401
- biolib/pyppeteer/pyppeteer/browser_fetcher.py +0 -194
- biolib/pyppeteer/pyppeteer/command.py +0 -22
- biolib/pyppeteer/pyppeteer/connection/__init__.py +0 -242
- biolib/pyppeteer/pyppeteer/connection/cdpsession.py +0 -101
- biolib/pyppeteer/pyppeteer/coverage.py +0 -346
- biolib/pyppeteer/pyppeteer/device_descriptors.py +0 -787
- biolib/pyppeteer/pyppeteer/dialog.py +0 -79
- biolib/pyppeteer/pyppeteer/domworld.py +0 -597
- biolib/pyppeteer/pyppeteer/emulation_manager.py +0 -53
- biolib/pyppeteer/pyppeteer/errors.py +0 -48
- biolib/pyppeteer/pyppeteer/events.py +0 -63
- biolib/pyppeteer/pyppeteer/execution_context.py +0 -156
- biolib/pyppeteer/pyppeteer/frame/__init__.py +0 -299
- biolib/pyppeteer/pyppeteer/frame/frame_manager.py +0 -306
- biolib/pyppeteer/pyppeteer/helpers.py +0 -245
- biolib/pyppeteer/pyppeteer/input.py +0 -371
- biolib/pyppeteer/pyppeteer/jshandle.py +0 -598
- biolib/pyppeteer/pyppeteer/launcher.py +0 -683
- biolib/pyppeteer/pyppeteer/lifecycle_watcher.py +0 -169
- biolib/pyppeteer/pyppeteer/models/__init__.py +0 -103
- biolib/pyppeteer/pyppeteer/models/_protocol.py +0 -12460
- biolib/pyppeteer/pyppeteer/multimap.py +0 -82
- biolib/pyppeteer/pyppeteer/network_manager.py +0 -678
- biolib/pyppeteer/pyppeteer/options.py +0 -8
- biolib/pyppeteer/pyppeteer/page.py +0 -1728
- biolib/pyppeteer/pyppeteer/pipe_transport.py +0 -59
- biolib/pyppeteer/pyppeteer/target.py +0 -147
- biolib/pyppeteer/pyppeteer/task_queue.py +0 -24
- biolib/pyppeteer/pyppeteer/timeout_settings.py +0 -36
- biolib/pyppeteer/pyppeteer/tracing.py +0 -93
- biolib/pyppeteer/pyppeteer/us_keyboard_layout.py +0 -305
- biolib/pyppeteer/pyppeteer/util.py +0 -18
- biolib/pyppeteer/pyppeteer/websocket_transport.py +0 -47
- biolib/pyppeteer/pyppeteer/worker.py +0 -101
- biolib/pyppeteer/pyproject.toml +0 -97
- biolib/pyppeteer/spell.txt +0 -137
- biolib/pyppeteer/tox.ini +0 -72
- biolib/pyppeteer/utils/generate_protocol_types.py +0 -603
- biolib/start_cli.py +0 -7
- biolib/utils.py +0 -47
- biolib/validators/validate_app_version.py +0 -183
- biolib/validators/validate_argument.py +0 -134
- biolib/validators/validate_module.py +0 -323
- biolib/validators/validate_zip_file.py +0 -40
- biolib/validators/validator_utils.py +0 -103
- pybiolib-0.2.951.dist-info/LICENSE +0 -21
- pybiolib-0.2.951.dist-info/METADATA +0 -61
- pybiolib-0.2.951.dist-info/RECORD +0 -153
- pybiolib-0.2.951.dist-info/entry_points.txt +0 -3
- /LICENSE → /pybiolib-1.2.1890.dist-info/licenses/LICENSE +0 -0
biolib/biolib_api_client/auth.py
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
|
-
from
|
|
1
|
+
from biolib import api
|
|
2
|
+
from biolib.biolib_api_client.api_client import UserTokens
|
|
3
|
+
from biolib.typing_utils import TypedDict, Literal
|
|
2
4
|
|
|
3
5
|
|
|
4
|
-
class
|
|
5
|
-
|
|
6
|
-
self.access_token = access_token
|
|
6
|
+
class AuthChallengeCreate(TypedDict):
|
|
7
|
+
token: str
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
|
|
10
|
+
class _AuthChallengeStatus(TypedDict):
|
|
11
|
+
state: Literal['awaiting', 'completed']
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuthChallengeStatus(_AuthChallengeStatus, total=False):
|
|
15
|
+
user_tokens: UserTokens
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BiolibAuthChallengeApi:
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def create_auth_challenge() -> AuthChallengeCreate:
|
|
22
|
+
response = api.client.post(path='/user/auth_challenges/')
|
|
23
|
+
response_dict: AuthChallengeCreate = response.json()
|
|
24
|
+
return response_dict
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def get_auth_challenge_status(token: str) -> AuthChallengeStatus:
|
|
28
|
+
response = api.client.get(
|
|
29
|
+
path='/user/auth_challenges/',
|
|
30
|
+
headers={'Auth-Challenge-Token': token},
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
response_dict: AuthChallengeStatus = response.json()
|
|
34
|
+
return response_dict
|
|
@@ -1,71 +1,166 @@
|
|
|
1
|
+
import mimetypes
|
|
2
|
+
import os
|
|
3
|
+
import random
|
|
1
4
|
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
import urllib.parse
|
|
2
7
|
|
|
3
|
-
import
|
|
4
|
-
|
|
8
|
+
import biolib.api
|
|
5
9
|
from biolib import biolib_errors
|
|
6
|
-
from biolib.
|
|
7
|
-
from biolib.
|
|
8
|
-
from biolib.
|
|
10
|
+
from biolib._internal.http_client import HttpError
|
|
11
|
+
from biolib.api.client import ApiClient
|
|
12
|
+
from biolib.biolib_api_client import AppGetResponse
|
|
9
13
|
from biolib.biolib_logging import logger
|
|
14
|
+
from biolib.typing_utils import Optional
|
|
15
|
+
from biolib.utils import load_base_url_from_env
|
|
10
16
|
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
def encode_multipart(data, files):
|
|
19
|
+
boundary = f'----------{random.randint(0, 1000000000)}'
|
|
20
|
+
line_array = []
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
for key, value in data.items():
|
|
23
|
+
if value is not None:
|
|
24
|
+
line_array.append(f'--{boundary}')
|
|
25
|
+
line_array.append(f'Content-Disposition: form-data; name="{key}"')
|
|
26
|
+
line_array.append('')
|
|
27
|
+
line_array.append(value)
|
|
28
|
+
|
|
29
|
+
for key, (filename, value) in files.items():
|
|
30
|
+
line_array.append(f'--{boundary}')
|
|
31
|
+
line_array.append(f'Content-Disposition: form-data; name="{key}"; filename="{filename}"')
|
|
32
|
+
line_array.append(f'Content-Type: {mimetypes.guess_type(filename)[0] or "application/octet-stream"}')
|
|
33
|
+
line_array.append('')
|
|
34
|
+
line_array.append('')
|
|
35
|
+
line_array.append(value)
|
|
36
|
+
|
|
37
|
+
line_array.append(f'--{boundary}--')
|
|
38
|
+
line_array.append('')
|
|
39
|
+
|
|
40
|
+
data_encoded = b'\r\n'.join([line.encode() if isinstance(line, str) else line for line in line_array])
|
|
41
|
+
return f'multipart/form-data; boundary={boundary}', data_encoded
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _get_git_branch_name() -> str:
|
|
45
|
+
try:
|
|
46
|
+
github_actions_branch_name = os.getenv('GITHUB_REF_NAME')
|
|
47
|
+
if github_actions_branch_name:
|
|
48
|
+
return github_actions_branch_name
|
|
49
|
+
|
|
50
|
+
gitlab_ci_branch_name = os.getenv('CI_COMMIT_REF_NAME')
|
|
51
|
+
if gitlab_ci_branch_name:
|
|
52
|
+
return gitlab_ci_branch_name
|
|
53
|
+
|
|
54
|
+
result = subprocess.run(['git', 'branch', '--show-current'], check=True, stdout=subprocess.PIPE, text=True)
|
|
55
|
+
return result.stdout.strip()
|
|
56
|
+
except BaseException:
|
|
57
|
+
return ''
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _get_git_commit_hash() -> str:
|
|
61
|
+
try:
|
|
62
|
+
github_actions_commit_hash = os.getenv('GITHUB_SHA')
|
|
63
|
+
if github_actions_commit_hash:
|
|
64
|
+
return github_actions_commit_hash
|
|
20
65
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
params={'uri': uri},
|
|
25
|
-
timeout=5,
|
|
26
|
-
)
|
|
66
|
+
gitlab_ci_commit_hash = os.getenv('CI_COMMIT_SHA')
|
|
67
|
+
if gitlab_ci_commit_hash:
|
|
68
|
+
return gitlab_ci_commit_hash
|
|
27
69
|
|
|
28
|
-
|
|
29
|
-
|
|
70
|
+
result = subprocess.run(['git', 'rev-parse', 'HEAD'], check=True, stdout=subprocess.PIPE, text=True)
|
|
71
|
+
return result.stdout.strip()
|
|
72
|
+
except BaseException:
|
|
73
|
+
return ''
|
|
30
74
|
|
|
31
|
-
if response.status_code == 400:
|
|
32
|
-
raise biolib_errors.BioLibError(response.content.decode())
|
|
33
75
|
|
|
34
|
-
|
|
35
|
-
|
|
76
|
+
def _get_git_repository_url() -> str:
|
|
77
|
+
try:
|
|
78
|
+
result = subprocess.run(['git', 'remote', 'get-url', 'origin'], check=True, stdout=subprocess.PIPE, text=True)
|
|
79
|
+
return result.stdout.strip()
|
|
80
|
+
except BaseException:
|
|
81
|
+
return ''
|
|
36
82
|
|
|
37
|
-
app_response: AppGetResponse = response.json()
|
|
38
|
-
return app_response
|
|
39
83
|
|
|
84
|
+
def _get_resource_uri_from_str(input_str: str) -> str:
|
|
85
|
+
parsed_base_url = urllib.parse.urlparse(load_base_url_from_env())
|
|
86
|
+
parsed_uri = urllib.parse.urlparse(input_str)
|
|
87
|
+
if parsed_uri.netloc != '' and parsed_base_url.netloc != parsed_uri.netloc:
|
|
88
|
+
raise biolib_errors.ValidationError(f'Invalid URI. The hostname "{parsed_base_url.netloc}" is not recognized.')
|
|
89
|
+
elif parsed_uri.netloc != '' and parsed_uri.path[1] != '@':
|
|
90
|
+
uri = f'@{parsed_uri.netloc}{parsed_uri.path}'
|
|
91
|
+
elif parsed_uri.netloc == '' and parsed_uri.path.startswith(parsed_base_url.netloc):
|
|
92
|
+
uri = f'@{parsed_uri.path}'
|
|
93
|
+
else:
|
|
94
|
+
uri = parsed_uri.path
|
|
95
|
+
uri = uri.strip('/')
|
|
96
|
+
# Replace frontend version path with app_uri compatible version (if supplied)
|
|
97
|
+
uri = re.sub(r'/version/(?P<version>(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*))(/)?$', r':\g<version>', uri)
|
|
98
|
+
return uri
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class BiolibAppApi:
|
|
40
102
|
@staticmethod
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
'source_files_zip': zip_binary
|
|
49
|
-
},
|
|
50
|
-
auth=BearerAuth(BiolibApiClient.get().access_token)
|
|
51
|
-
)
|
|
52
|
-
if not response.ok:
|
|
53
|
-
logger.error(f'Push failed for {author}/{app_name}:')
|
|
54
|
-
raise BioLibError(response.text)
|
|
103
|
+
def get_by_uri(uri: str, api_client: Optional[ApiClient] = None) -> AppGetResponse:
|
|
104
|
+
uri = _get_resource_uri_from_str(uri)
|
|
105
|
+
api = api_client or biolib.api.client
|
|
106
|
+
try:
|
|
107
|
+
response = api.get(path='/app/', params={'uri': uri})
|
|
108
|
+
app_response: AppGetResponse = response.json()
|
|
109
|
+
return app_response
|
|
55
110
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
111
|
+
except HttpError as error:
|
|
112
|
+
if error.code == 404:
|
|
113
|
+
raise biolib_errors.NotFound(f'Application {uri} not found.') from None
|
|
114
|
+
|
|
115
|
+
raise error
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def create_app(uri: str):
|
|
119
|
+
uri = _get_resource_uri_from_str(uri)
|
|
120
|
+
try:
|
|
121
|
+
response = biolib.api.client.post(path='/resources/apps/', data={'uri': uri})
|
|
122
|
+
return response.json()
|
|
123
|
+
except HttpError as error:
|
|
124
|
+
raise error
|
|
59
125
|
|
|
60
126
|
@staticmethod
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
127
|
+
def push_app_version(
|
|
128
|
+
app_id,
|
|
129
|
+
zip_binary,
|
|
130
|
+
author,
|
|
131
|
+
app_name,
|
|
132
|
+
set_as_active,
|
|
133
|
+
app_version_id_to_copy_images_from: Optional[str],
|
|
134
|
+
semantic_version: Optional[str],
|
|
135
|
+
):
|
|
136
|
+
try:
|
|
137
|
+
data = {
|
|
138
|
+
'app': app_id,
|
|
139
|
+
'set_as_active': 'true' if set_as_active else 'false',
|
|
140
|
+
'state': 'published',
|
|
141
|
+
'app_version_id_to_copy_images_from': app_version_id_to_copy_images_from,
|
|
142
|
+
'git_branch_name': _get_git_branch_name(),
|
|
143
|
+
'git_commit_hash': _get_git_commit_hash(),
|
|
144
|
+
'git_repository_url': _get_git_repository_url(),
|
|
145
|
+
}
|
|
146
|
+
if semantic_version:
|
|
147
|
+
data['semantic_version'] = semantic_version
|
|
70
148
|
|
|
149
|
+
content_type, data_encoded = encode_multipart(
|
|
150
|
+
data=data,
|
|
151
|
+
files={
|
|
152
|
+
'source_files_zip': ('source_files.zip', zip_binary),
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
response = biolib.api.client.post(
|
|
156
|
+
path='/app_versions/',
|
|
157
|
+
data=data_encoded,
|
|
158
|
+
headers={'Content-Type': content_type},
|
|
159
|
+
)
|
|
160
|
+
except Exception as error:
|
|
161
|
+
logger.error(f'Push failed for {author}/{app_name}:')
|
|
162
|
+
raise error
|
|
163
|
+
|
|
164
|
+
# TODO: When response includes the version number, print the URL for the new app version
|
|
165
|
+
logger.info(f'Initialized new app version for {author}/{app_name}.')
|
|
71
166
|
return response.json()
|
|
@@ -1,172 +1,192 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import os
|
|
2
|
+
from urllib.parse import urlparse
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from biolib
|
|
7
|
-
from biolib.
|
|
4
|
+
import biolib.api
|
|
5
|
+
|
|
6
|
+
from biolib import utils
|
|
7
|
+
from biolib._internal.http_client import HttpError
|
|
8
|
+
from biolib.api.client import ApiClient
|
|
9
|
+
from biolib.biolib_api_client import CloudJob, JobState
|
|
10
|
+
from biolib.biolib_errors import JobResultPermissionError, JobResultError, JobResultNotFound, StorageDownloadFailed
|
|
8
11
|
from biolib.biolib_logging import logger
|
|
9
12
|
from biolib.utils import BIOLIB_PACKAGE_VERSION
|
|
13
|
+
from biolib.typing_utils import TypedDict, Optional, Literal, Dict
|
|
10
14
|
|
|
11
15
|
|
|
12
|
-
class
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class BiolibJobApi:
|
|
17
|
-
|
|
18
|
-
@staticmethod
|
|
19
|
-
def create(app_version_id, caller_job=None):
|
|
20
|
-
data = {
|
|
21
|
-
'app_version_id': app_version_id,
|
|
22
|
-
'client_type': 'biolib-python',
|
|
23
|
-
'client_version': BIOLIB_PACKAGE_VERSION,
|
|
24
|
-
}
|
|
25
|
-
if caller_job:
|
|
26
|
-
data['caller_job'] = caller_job
|
|
27
|
-
|
|
28
|
-
response = requests.post(
|
|
29
|
-
f'{BiolibApiClient.get().base_url}/api/jobs/',
|
|
30
|
-
auth=BearerAuth(BiolibApiClient.get().access_token),
|
|
31
|
-
json=data,
|
|
32
|
-
)
|
|
16
|
+
class PresignedS3UploadLinkResponse(TypedDict):
|
|
17
|
+
presigned_upload_url: str
|
|
33
18
|
|
|
34
|
-
# TODO: Error handling with response object
|
|
35
|
-
if not response.ok:
|
|
36
|
-
raise BioLibError(response.content)
|
|
37
19
|
|
|
38
|
-
|
|
20
|
+
class PresignedS3DownloadLinkResponse(TypedDict):
|
|
21
|
+
presigned_download_url: str
|
|
39
22
|
|
|
40
|
-
@staticmethod
|
|
41
|
-
def update_state(job_id, state):
|
|
42
|
-
response = requests.patch(
|
|
43
|
-
f'{BiolibApiClient.get().base_url}/api/jobs/{job_id}/',
|
|
44
|
-
json={'state': state},
|
|
45
|
-
auth=BearerAuth(BiolibApiClient.get().access_token)
|
|
46
|
-
)
|
|
47
23
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
24
|
+
def _get_user_info() -> Optional[str]:
|
|
25
|
+
if utils.BASE_URL_IS_PUBLIC_BIOLIB:
|
|
26
|
+
return None
|
|
51
27
|
|
|
52
|
-
|
|
28
|
+
enterprise_agent_info_opt_env_vars = ['BIOLIB_OPT_USER', 'DOMINO_STARTING_USERNAME', 'USER']
|
|
53
29
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
response = requests.post(
|
|
60
|
-
f'{BiolibApiClient.get().base_url}/api/jobs/cloud/',
|
|
61
|
-
json={'module_name': module_name, 'job_id': job_id},
|
|
62
|
-
auth=BearerAuth(BiolibApiClient.get().access_token)
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
if response.status_code == 503:
|
|
66
|
-
raise RetryException(response.content)
|
|
67
|
-
# Handle possible validation errors from backend
|
|
68
|
-
elif not response.ok:
|
|
69
|
-
raise BioLibError(response.text)
|
|
70
|
-
|
|
71
|
-
break
|
|
72
|
-
|
|
73
|
-
except RetryException as retry_exception: # pylint: disable=broad-except
|
|
74
|
-
if retry > 3:
|
|
75
|
-
raise BioLibError('Reached retry limit for cloud job creation') from retry_exception
|
|
76
|
-
time.sleep(1)
|
|
77
|
-
|
|
78
|
-
if not response:
|
|
79
|
-
raise BioLibError('Could not create new cloud job')
|
|
80
|
-
|
|
81
|
-
cloud_job = response.json()
|
|
82
|
-
if cloud_job.get('is_compute_node_ready', False):
|
|
83
|
-
return cloud_job
|
|
84
|
-
|
|
85
|
-
max_retry_attempts = 25
|
|
86
|
-
retry_interval_seconds = 10
|
|
87
|
-
|
|
88
|
-
for _ in range(max_retry_attempts):
|
|
89
|
-
response = requests.get(
|
|
90
|
-
f'{BiolibApiClient.get().base_url}/api/jobs/cloud/{cloud_job["public_id"]}/status/',
|
|
91
|
-
auth=BearerAuth(BiolibApiClient.get().access_token)
|
|
92
|
-
)
|
|
93
|
-
cloud_job = response.json()
|
|
94
|
-
if cloud_job.get('is_compute_node_ready', False):
|
|
95
|
-
return cloud_job
|
|
30
|
+
for env_var in enterprise_agent_info_opt_env_vars:
|
|
31
|
+
env_var_value = os.getenv(env_var)
|
|
32
|
+
if env_var_value:
|
|
33
|
+
return env_var_value
|
|
96
34
|
|
|
97
|
-
|
|
98
|
-
time.sleep(retry_interval_seconds)
|
|
35
|
+
return None
|
|
99
36
|
|
|
100
|
-
raise BioLibError('Cloud: The reserved compute node was not ready in time')
|
|
101
|
-
|
|
102
|
-
@staticmethod
|
|
103
|
-
def save_compute_node_job(job, module_name, access_token, node_url):
|
|
104
|
-
response = requests.post(
|
|
105
|
-
f'{node_url}/v1/job/',
|
|
106
|
-
json={'module_name': module_name, 'job': job, 'access_token': access_token}
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
if not response.ok:
|
|
110
|
-
raise BioLibError(response.content)
|
|
111
|
-
|
|
112
|
-
return response.json
|
|
113
37
|
|
|
38
|
+
class BiolibJobApi:
|
|
114
39
|
@staticmethod
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
40
|
+
def create(
|
|
41
|
+
app_version_id,
|
|
42
|
+
app_resource_name_prefix=None,
|
|
43
|
+
override_command=False,
|
|
44
|
+
caller_job=None,
|
|
45
|
+
machine='',
|
|
46
|
+
experiment_uuid: Optional[str] = None,
|
|
47
|
+
timeout: Optional[int] = None,
|
|
48
|
+
notify: bool = False,
|
|
49
|
+
requested_machine_count: Optional[int] = None,
|
|
50
|
+
temporary_client_secrets: Optional[Dict[str, str]] = None,
|
|
51
|
+
api_client: Optional[ApiClient] = None,
|
|
52
|
+
):
|
|
53
|
+
data = {
|
|
54
|
+
'app_version_id': app_version_id,
|
|
55
|
+
'client_type': 'biolib-python',
|
|
56
|
+
'notify': notify,
|
|
57
|
+
'client_version': BIOLIB_PACKAGE_VERSION,
|
|
58
|
+
'client_opt_user_info': _get_user_info(),
|
|
59
|
+
}
|
|
120
60
|
|
|
121
|
-
if
|
|
122
|
-
|
|
61
|
+
if app_resource_name_prefix:
|
|
62
|
+
data.update({'app_resource_name_prefix': app_resource_name_prefix})
|
|
123
63
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
compute_type, job_id, node_url):
|
|
127
|
-
status_max_retry_attempts = int(retry_limit_minutes * 60 / retry_interval_seconds)
|
|
128
|
-
status_reached = False
|
|
129
|
-
for _ in range(status_max_retry_attempts):
|
|
130
|
-
response = requests.get(f'{node_url}/v1/job/{job_id}/status/')
|
|
131
|
-
if not response.ok:
|
|
132
|
-
raise Exception(response.content)
|
|
64
|
+
if override_command:
|
|
65
|
+
data.update({'arguments_override_command': override_command})
|
|
133
66
|
|
|
134
|
-
|
|
67
|
+
if caller_job:
|
|
68
|
+
data['caller_job'] = caller_job
|
|
135
69
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
logger.info(f'{compute_type}: {status_update["log_message"]}')
|
|
70
|
+
if machine:
|
|
71
|
+
data.update({'requested_machine': machine})
|
|
139
72
|
|
|
140
|
-
|
|
141
|
-
|
|
73
|
+
if requested_machine_count:
|
|
74
|
+
data.update({'requested_machine_count': requested_machine_count})
|
|
142
75
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
error_message = SystemExceptionCodeMap.get(error_code, f'Unknown error code {error_code}')
|
|
76
|
+
if experiment_uuid:
|
|
77
|
+
data['experiment_uuid'] = experiment_uuid
|
|
146
78
|
|
|
147
|
-
|
|
79
|
+
if timeout:
|
|
80
|
+
data['requested_timeout_seconds'] = timeout
|
|
148
81
|
|
|
149
|
-
|
|
150
|
-
|
|
82
|
+
if temporary_client_secrets:
|
|
83
|
+
data['temporary_client_secrets'] = temporary_client_secrets
|
|
151
84
|
|
|
152
|
-
|
|
85
|
+
api = api_client or biolib.api.client
|
|
86
|
+
response = api.post(path='/jobs/', data=data)
|
|
153
87
|
|
|
154
|
-
|
|
88
|
+
return response.json()
|
|
155
89
|
|
|
156
90
|
@staticmethod
|
|
157
|
-
def
|
|
158
|
-
|
|
159
|
-
f'
|
|
160
|
-
|
|
91
|
+
def update_state(job_uuid: str, state: JobState) -> None:
|
|
92
|
+
try:
|
|
93
|
+
biolib.api.client.patch(path=f'/jobs/{job_uuid}/', data={'state': state.value})
|
|
94
|
+
except BaseException as error:
|
|
95
|
+
logger.error(f'Failed to update job "{job_uuid}" to state "{state.value}" due to {error}')
|
|
161
96
|
|
|
162
|
-
|
|
163
|
-
|
|
97
|
+
@staticmethod
|
|
98
|
+
def create_cloud_job(
|
|
99
|
+
job_id: str,
|
|
100
|
+
result_name_prefix: Optional[str],
|
|
101
|
+
api_client: Optional[ApiClient] = None,
|
|
102
|
+
) -> CloudJob:
|
|
103
|
+
data = {'job_id': job_id}
|
|
104
|
+
if result_name_prefix:
|
|
105
|
+
data['result_name_prefix'] = result_name_prefix
|
|
106
|
+
|
|
107
|
+
api = api_client or biolib.api.client
|
|
108
|
+
response = api.post(path='/jobs/cloud/', data=data)
|
|
109
|
+
cloud_job: CloudJob = response.json()
|
|
110
|
+
return cloud_job
|
|
164
111
|
|
|
165
|
-
|
|
112
|
+
@staticmethod
|
|
113
|
+
def get_job_storage_download_url(
|
|
114
|
+
job_uuid: str,
|
|
115
|
+
job_auth_token: str,
|
|
116
|
+
storage_type: Literal['input', 'results'],
|
|
117
|
+
) -> str:
|
|
118
|
+
try:
|
|
119
|
+
response = biolib.api.client.get(
|
|
120
|
+
path=f'/jobs/{job_uuid}/storage/{storage_type}/download/',
|
|
121
|
+
authenticate=True,
|
|
122
|
+
headers={'Job-Auth-Token': job_auth_token},
|
|
123
|
+
)
|
|
124
|
+
presigned_s3_download_link_response: PresignedS3DownloadLinkResponse = response.json()
|
|
125
|
+
presigned_download_url = presigned_s3_download_link_response['presigned_download_url']
|
|
126
|
+
|
|
127
|
+
app_caller_proxy_job_storage_base_url = os.getenv('BIOLIB_CLOUD_JOB_STORAGE_BASE_URL', '')
|
|
128
|
+
if app_caller_proxy_job_storage_base_url:
|
|
129
|
+
# Done to hit App Caller Proxy when downloading result from inside an app
|
|
130
|
+
parsed_url = urlparse(presigned_download_url)
|
|
131
|
+
presigned_download_url = f'{app_caller_proxy_job_storage_base_url}{parsed_url.path}?{parsed_url.query}'
|
|
132
|
+
|
|
133
|
+
return presigned_download_url
|
|
134
|
+
|
|
135
|
+
except HttpError as error:
|
|
136
|
+
if storage_type == 'results':
|
|
137
|
+
if error.code == 401:
|
|
138
|
+
raise JobResultPermissionError('You must be signed in to get result of the job') from None
|
|
139
|
+
elif error.code == 403:
|
|
140
|
+
raise JobResultPermissionError(
|
|
141
|
+
'Cannot get result of job. Maybe the job was created without being signed in?'
|
|
142
|
+
) from None
|
|
143
|
+
elif error.code == 404:
|
|
144
|
+
raise JobResultNotFound('Job result not found') from None
|
|
145
|
+
else:
|
|
146
|
+
raise JobResultError('Failed to get result of job') from error
|
|
147
|
+
else:
|
|
148
|
+
raise StorageDownloadFailed(f'Failed to download result of job got error: {error}') from error
|
|
149
|
+
|
|
150
|
+
except Exception as error: # pylint: disable=broad-except
|
|
151
|
+
if storage_type == 'results':
|
|
152
|
+
raise JobResultError('Failed to get result of job') from error
|
|
153
|
+
else:
|
|
154
|
+
raise StorageDownloadFailed('Failed to download from Job Storage') from error
|
|
166
155
|
|
|
167
156
|
@staticmethod
|
|
168
|
-
def
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
157
|
+
def create_job_with_data(
|
|
158
|
+
app_version_uuid: str,
|
|
159
|
+
app_resource_name_prefix: Optional[str],
|
|
160
|
+
module_input_serialized: bytes,
|
|
161
|
+
arguments_override_command: bool,
|
|
162
|
+
experiment_uuid: Optional[str],
|
|
163
|
+
requested_machine: Optional[str],
|
|
164
|
+
result_name_prefix: Optional[str],
|
|
165
|
+
caller_job_uuid: Optional[str] = None,
|
|
166
|
+
requested_timeout_seconds: Optional[int] = None,
|
|
167
|
+
notify: bool = False,
|
|
168
|
+
requested_machine_count: Optional[int] = None,
|
|
169
|
+
api_client: Optional[ApiClient] = None,
|
|
170
|
+
) -> Dict:
|
|
171
|
+
api = api_client or biolib.api.client
|
|
172
|
+
job_dict: Dict = api.post(
|
|
173
|
+
path='/jobs/create_job_with_data/',
|
|
174
|
+
data=module_input_serialized,
|
|
175
|
+
headers={
|
|
176
|
+
'Content-Type': 'application/octet-stream',
|
|
177
|
+
'app-version-uuid': app_version_uuid,
|
|
178
|
+
'app-resource-name-prefix': app_resource_name_prefix,
|
|
179
|
+
'arguments-override-command': str(arguments_override_command),
|
|
180
|
+
'caller-job-uuid': caller_job_uuid,
|
|
181
|
+
'client-opt-user-info': _get_user_info(),
|
|
182
|
+
'client-type': 'biolib-python',
|
|
183
|
+
'client-version': BIOLIB_PACKAGE_VERSION,
|
|
184
|
+
'experiment-uuid': experiment_uuid,
|
|
185
|
+
'requested-machine': requested_machine,
|
|
186
|
+
'requested-machine-count': str(requested_machine_count) if requested_machine_count else None,
|
|
187
|
+
'result-name-prefix': result_name_prefix,
|
|
188
|
+
'requested-timeout-seconds': str(requested_timeout_seconds) if requested_timeout_seconds else None,
|
|
189
|
+
'notify': 'true' if notify else 'false',
|
|
190
|
+
},
|
|
191
|
+
).json()
|
|
192
|
+
return job_dict
|
|
@@ -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 Dict, List, Optional, TypedDict
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
class JobState(Enum):
|
|
@@ -15,21 +14,38 @@ class JobState(Enum):
|
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
class _Job(TypedDict):
|
|
17
|
+
app_uri: str
|
|
18
18
|
app_version: AppVersionOnJob
|
|
19
|
+
arguments_override_command: bool
|
|
20
|
+
auth_token: str
|
|
19
21
|
caller_job: Optional[str]
|
|
20
22
|
created_at: str
|
|
23
|
+
federated_job_uuid: Optional[str]
|
|
21
24
|
public_id: str
|
|
22
25
|
remote_hosts_with_warning: List[RemoteHost]
|
|
26
|
+
state: str
|
|
23
27
|
user_id: Optional[str]
|
|
28
|
+
uuid: str
|
|
24
29
|
|
|
25
30
|
|
|
26
31
|
# type optional keys with total=False
|
|
27
|
-
class
|
|
32
|
+
class CreatedJobDict(_Job, total=False):
|
|
28
33
|
custom_compute_node_url: str
|
|
34
|
+
temporary_client_secrets: Dict[str, str]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CloudJob(TypedDict):
|
|
38
|
+
public_id: str
|
|
39
|
+
reserved_cpu_in_nano_shares: int
|
|
40
|
+
reserved_gpu_count: int
|
|
41
|
+
reserved_memory_in_bytes: int
|
|
42
|
+
max_runtime_in_seconds: int
|
|
29
43
|
|
|
30
44
|
|
|
31
45
|
class JobWrapper(TypedDict):
|
|
32
46
|
access_token: str
|
|
33
47
|
BASE_URL: str # TODO: refactor this to lower case
|
|
34
48
|
compute_node_info: Optional[ComputeNodeInfo]
|
|
35
|
-
job:
|
|
49
|
+
job: CreatedJobDict
|
|
50
|
+
cloud_job: Optional[CloudJob]
|
|
51
|
+
job_temporary_dir: Optional[str]
|