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/app/app.py
CHANGED
|
@@ -1,29 +1,74 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
1
4
|
import os
|
|
5
|
+
import posixpath
|
|
6
|
+
import random
|
|
7
|
+
import string
|
|
8
|
+
from pathlib import Path
|
|
2
9
|
|
|
3
|
-
from biolib
|
|
10
|
+
from biolib import utils
|
|
11
|
+
from biolib._internal.file_utils import path_to_renamed_path
|
|
12
|
+
from biolib._runtime.runtime import Runtime
|
|
13
|
+
from biolib._shared.utils import parse_resource_uri
|
|
14
|
+
from biolib.api.client import ApiClient
|
|
4
15
|
from biolib.biolib_api_client import JobState
|
|
5
16
|
from biolib.biolib_api_client.app_types import App, AppVersion
|
|
6
|
-
from biolib.biolib_api_client.biolib_job_api import BiolibJobApi
|
|
7
|
-
from biolib.app.app_result import AppResult
|
|
8
17
|
from biolib.biolib_api_client.biolib_app_api import BiolibAppApi
|
|
18
|
+
from biolib.biolib_api_client.biolib_job_api import BiolibJobApi
|
|
9
19
|
from biolib.biolib_binary_format import ModuleInput
|
|
10
|
-
from biolib.biolib_errors import BioLibError
|
|
20
|
+
from biolib.biolib_errors import BioLibError, JobResultNonZeroExitCodeError
|
|
11
21
|
from biolib.biolib_logging import logger
|
|
22
|
+
from biolib.compute_node.job_worker.job_worker import JobWorker
|
|
23
|
+
from biolib.experiments.experiment import Experiment
|
|
24
|
+
from biolib.jobs.job import Result
|
|
25
|
+
from biolib.typing_utils import Dict, Optional
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class JsonStringIO(io.StringIO):
|
|
29
|
+
pass
|
|
12
30
|
|
|
13
31
|
|
|
14
32
|
class BioLibApp:
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
uri: str,
|
|
36
|
+
_api_client: Optional[ApiClient] = None,
|
|
37
|
+
suppress_version_warning: bool = False,
|
|
38
|
+
_experiment: Optional[str] = None,
|
|
39
|
+
):
|
|
40
|
+
self._api_client: Optional[ApiClient] = _api_client
|
|
41
|
+
self._experiment = _experiment
|
|
42
|
+
self._input_uri = uri
|
|
43
|
+
self._parsed_input_uri = parse_resource_uri(uri)
|
|
15
44
|
|
|
16
|
-
|
|
17
|
-
app_response = BiolibAppApi.get_by_uri(uri)
|
|
45
|
+
app_response = BiolibAppApi.get_by_uri(uri=uri, api_client=self._api_client)
|
|
18
46
|
self._app: App = app_response['app']
|
|
19
47
|
self._app_uri = app_response['app_uri']
|
|
20
48
|
self._app_version: AppVersion = app_response['app_version']
|
|
21
49
|
|
|
22
|
-
|
|
50
|
+
if not suppress_version_warning:
|
|
51
|
+
if self._parsed_input_uri['version'] is None:
|
|
52
|
+
if Runtime.check_is_environment_biolib_app():
|
|
53
|
+
logger.warning(
|
|
54
|
+
f"No version specified in URI '{uri}'. This will use the default version, "
|
|
55
|
+
f'which may change behaviour over time. Consider locking down the exact version, '
|
|
56
|
+
f"e.g. '{uri}:1.2.3'"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if self._parsed_input_uri['tag']:
|
|
60
|
+
semantic_version = f"{self._app_version['major']}.{self._app_version['minor']}.{self._app_version['patch']}"
|
|
61
|
+
logger.info(f'Loaded {self._input_uri} (resolved to {semantic_version})')
|
|
62
|
+
else:
|
|
63
|
+
logger.info(f'Loaded {self._app_uri}')
|
|
23
64
|
|
|
24
65
|
def __str__(self) -> str:
|
|
25
66
|
return self._app_uri
|
|
26
67
|
|
|
68
|
+
@property
|
|
69
|
+
def uri(self) -> str:
|
|
70
|
+
return self._app_uri
|
|
71
|
+
|
|
27
72
|
@property
|
|
28
73
|
def uuid(self) -> str:
|
|
29
74
|
return self._app['public_id']
|
|
@@ -32,50 +77,98 @@ class BioLibApp:
|
|
|
32
77
|
def version(self) -> AppVersion:
|
|
33
78
|
return self._app_version
|
|
34
79
|
|
|
35
|
-
def cli(
|
|
80
|
+
def cli(
|
|
81
|
+
self,
|
|
82
|
+
args=None,
|
|
83
|
+
stdin=None,
|
|
84
|
+
files=None,
|
|
85
|
+
override_command=False,
|
|
86
|
+
machine='',
|
|
87
|
+
blocking: bool = True,
|
|
88
|
+
experiment_id: Optional[str] = None,
|
|
89
|
+
result_prefix: Optional[str] = None,
|
|
90
|
+
timeout: Optional[int] = None,
|
|
91
|
+
notify: bool = False,
|
|
92
|
+
max_workers: Optional[int] = None,
|
|
93
|
+
experiment: Optional[str] = None,
|
|
94
|
+
temporary_client_secrets: Optional[Dict[str, str]] = None,
|
|
95
|
+
check: bool = False,
|
|
96
|
+
stream_logs: bool = False,
|
|
97
|
+
) -> Result:
|
|
98
|
+
if experiment_id and experiment:
|
|
99
|
+
raise ValueError('Only one of experiment_id and experiment can be specified')
|
|
100
|
+
|
|
101
|
+
if check and not blocking:
|
|
102
|
+
raise ValueError('The argument "check" cannot be True when blocking is False')
|
|
103
|
+
|
|
104
|
+
if not experiment_id:
|
|
105
|
+
experiment_to_use = experiment if experiment is not None else self._experiment
|
|
106
|
+
experiment_instance: Optional[Experiment]
|
|
107
|
+
if experiment_to_use:
|
|
108
|
+
experiment_instance = Experiment(experiment_to_use, _api_client=self._api_client)
|
|
109
|
+
else:
|
|
110
|
+
experiment_instance = Experiment.get_experiment_in_context()
|
|
111
|
+
experiment_id = experiment_instance.uuid if experiment_instance else None
|
|
112
|
+
|
|
36
113
|
module_input_serialized = self._get_serialized_module_input(args, stdin, files)
|
|
37
114
|
|
|
38
|
-
|
|
39
|
-
|
|
115
|
+
if machine == 'local':
|
|
116
|
+
raise BioLibError('Running applications locally with machine="local" is no longer supported.')
|
|
40
117
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
118
|
+
job = Result._start_job_in_cloud( # pylint: disable=protected-access
|
|
119
|
+
app_uri=self._app_uri,
|
|
120
|
+
app_version_uuid=self._app_version['public_id'],
|
|
121
|
+
experiment_id=experiment_id,
|
|
122
|
+
machine=machine,
|
|
123
|
+
module_input_serialized=module_input_serialized,
|
|
124
|
+
notify=notify,
|
|
125
|
+
override_command=override_command,
|
|
126
|
+
result_prefix=result_prefix,
|
|
127
|
+
timeout=timeout,
|
|
128
|
+
requested_machine_count=max_workers,
|
|
129
|
+
temporary_client_secrets=temporary_client_secrets,
|
|
130
|
+
api_client=self._api_client,
|
|
131
|
+
)
|
|
132
|
+
if utils.IS_RUNNING_IN_NOTEBOOK:
|
|
133
|
+
logger.info(f'View the result in your browser at: {utils.BIOLIB_BASE_URL}/results/{job.id}/')
|
|
134
|
+
if blocking:
|
|
135
|
+
# TODO: Deprecate utils.STREAM_STDOUT and always stream logs by simply calling job.stream_logs()
|
|
136
|
+
if utils.IS_RUNNING_IN_NOTEBOOK:
|
|
137
|
+
utils.STREAM_STDOUT = True
|
|
138
|
+
|
|
139
|
+
enable_print = bool(
|
|
140
|
+
(utils.STREAM_STDOUT or stream_logs)
|
|
141
|
+
and (self._app_version.get('main_output_file') or self._app_version.get('stdout_render_type') == 'text')
|
|
53
142
|
)
|
|
143
|
+
job._stream_logs(enable_print=enable_print) # pylint: disable=protected-access
|
|
144
|
+
|
|
145
|
+
if check:
|
|
146
|
+
exit_code = job.get_exit_code()
|
|
147
|
+
if exit_code != 0:
|
|
148
|
+
raise JobResultNonZeroExitCodeError(exit_code)
|
|
54
149
|
|
|
55
|
-
|
|
56
|
-
logger.error(f'Compute failed with: {exception.message}')
|
|
57
|
-
try:
|
|
58
|
-
BiolibJobApi.update_state(job_id=job['public_id'], state=JobState.FAILED.value)
|
|
59
|
-
except Exception as error: # pylint: disable=broad-except
|
|
60
|
-
logger.warning(f'Could not update job state to failed:\n{error}')
|
|
150
|
+
return job
|
|
61
151
|
|
|
62
|
-
|
|
152
|
+
def exec(self, args=None, stdin=None, files=None, machine=''):
|
|
153
|
+
return self.cli(args, stdin, files, override_command=True, machine=machine)
|
|
63
154
|
|
|
64
155
|
def __call__(self, *args, **kwargs):
|
|
65
156
|
if not args and not kwargs:
|
|
66
157
|
self.cli()
|
|
67
158
|
|
|
68
159
|
else:
|
|
69
|
-
raise BioLibError(
|
|
160
|
+
raise BioLibError("""
|
|
70
161
|
Calling an app directly with app() is currently being reworked.
|
|
71
|
-
To use the previous functionality, please call app.cli() instead.
|
|
162
|
+
To use the previous functionality, please call app.cli() instead.
|
|
72
163
|
Example: "app.cli('--help')"
|
|
73
|
-
|
|
164
|
+
""")
|
|
74
165
|
|
|
75
166
|
@staticmethod
|
|
76
167
|
def _get_serialized_module_input(args=None, stdin=None, files=None) -> bytes:
|
|
77
168
|
if args is None:
|
|
78
169
|
args = []
|
|
170
|
+
else:
|
|
171
|
+
args = copy.copy(args)
|
|
79
172
|
|
|
80
173
|
if stdin is None:
|
|
81
174
|
stdin = b''
|
|
@@ -91,26 +184,136 @@ Example: "app.cli('--help')"
|
|
|
91
184
|
|
|
92
185
|
if files is None:
|
|
93
186
|
files = []
|
|
94
|
-
for idx, arg in enumerate(args):
|
|
95
|
-
if os.path.isfile(arg):
|
|
96
|
-
files.append(arg)
|
|
97
|
-
args[idx] = arg.split('/')[-1]
|
|
98
187
|
|
|
99
|
-
cwd = os.getcwd()
|
|
100
188
|
files_dict = {}
|
|
189
|
+
if isinstance(files, list):
|
|
190
|
+
for file_path in files:
|
|
191
|
+
path = Path(file_path)
|
|
192
|
+
if path.is_dir():
|
|
193
|
+
renamed_dir = path_to_renamed_path(file_path)
|
|
194
|
+
for filename in path.rglob('*'):
|
|
195
|
+
if filename.is_dir():
|
|
196
|
+
continue
|
|
197
|
+
with open(filename, 'rb') as f:
|
|
198
|
+
relative_to_dir = filename.resolve().relative_to(path.resolve())
|
|
199
|
+
files_dict[posixpath.join(renamed_dir, relative_to_dir.as_posix())] = f.read()
|
|
200
|
+
else:
|
|
201
|
+
with open(path, 'rb') as f:
|
|
202
|
+
files_dict[path_to_renamed_path(str(path))] = f.read()
|
|
203
|
+
elif isinstance(files, dict):
|
|
204
|
+
files_dict = {}
|
|
205
|
+
for key, value in files.items():
|
|
206
|
+
if '//' in key:
|
|
207
|
+
raise BioLibError(f"File path '{key}' contains double slashes which are not allowed")
|
|
208
|
+
if not key.startswith('/'):
|
|
209
|
+
key = '/' + key
|
|
210
|
+
files_dict[key] = value
|
|
211
|
+
else:
|
|
212
|
+
raise Exception('The given files input must be list or dict or None')
|
|
101
213
|
|
|
102
|
-
for
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
214
|
+
for idx, arg in enumerate(args):
|
|
215
|
+
if isinstance(arg, str):
|
|
216
|
+
if os.path.isfile(arg) or os.path.isdir(arg):
|
|
217
|
+
if os.path.isfile(arg):
|
|
218
|
+
with open(arg, 'rb') as f:
|
|
219
|
+
files_dict[path_to_renamed_path(arg)] = f.read()
|
|
220
|
+
elif os.path.isdir(arg):
|
|
221
|
+
path = Path(arg)
|
|
222
|
+
renamed_dir = path_to_renamed_path(arg)
|
|
223
|
+
for filename in path.rglob('*'):
|
|
224
|
+
if filename.is_dir():
|
|
225
|
+
continue
|
|
226
|
+
with open(filename, 'rb') as f:
|
|
227
|
+
relative_to_dir = filename.resolve().relative_to(path.resolve())
|
|
228
|
+
files_dict[posixpath.join(renamed_dir, relative_to_dir.as_posix())] = f.read()
|
|
229
|
+
args[idx] = path_to_renamed_path(arg, prefix_with_slash=False)
|
|
111
230
|
|
|
112
|
-
|
|
113
|
-
|
|
231
|
+
# support --myarg=file.txt
|
|
232
|
+
elif os.path.isfile(arg.split('=')[-1]) or os.path.isdir(arg.split('=')[-1]):
|
|
233
|
+
file_path = arg.split('=')[-1]
|
|
234
|
+
if os.path.isfile(file_path):
|
|
235
|
+
with open(file_path, 'rb') as f:
|
|
236
|
+
files_dict[path_to_renamed_path(file_path)] = f.read()
|
|
237
|
+
elif os.path.isdir(file_path):
|
|
238
|
+
path = Path(file_path)
|
|
239
|
+
renamed_dir = path_to_renamed_path(file_path)
|
|
240
|
+
for filename in path.rglob('*'):
|
|
241
|
+
if filename.is_dir():
|
|
242
|
+
continue
|
|
243
|
+
with open(filename, 'rb') as f:
|
|
244
|
+
relative_to_dir = filename.resolve().relative_to(path.resolve())
|
|
245
|
+
files_dict[posixpath.join(renamed_dir, relative_to_dir.as_posix())] = f.read()
|
|
246
|
+
args[idx] = arg.split('=')[0] + '=' + path_to_renamed_path(file_path, prefix_with_slash=False)
|
|
247
|
+
else:
|
|
248
|
+
pass # a normal string arg was given
|
|
249
|
+
else:
|
|
250
|
+
tmp_filename = f'input_{"".join(random.choices(string.ascii_letters + string.digits, k=7))}'
|
|
251
|
+
if isinstance(arg, JsonStringIO):
|
|
252
|
+
file_data = arg.getvalue().encode()
|
|
253
|
+
tmp_filename += '.json'
|
|
254
|
+
elif isinstance(arg, io.StringIO):
|
|
255
|
+
file_data = arg.getvalue().encode()
|
|
256
|
+
elif isinstance(arg, io.BytesIO):
|
|
257
|
+
file_data = arg.getvalue()
|
|
258
|
+
else:
|
|
259
|
+
raise Exception(f'Unexpected type of argument: {arg}')
|
|
260
|
+
files_dict[f'/{tmp_filename}'] = file_data
|
|
261
|
+
args[idx] = tmp_filename
|
|
114
262
|
|
|
115
|
-
module_input_serialized: bytes = ModuleInput().serialize(
|
|
263
|
+
module_input_serialized: bytes = ModuleInput().serialize(
|
|
264
|
+
stdin=stdin,
|
|
265
|
+
arguments=args,
|
|
266
|
+
files=files_dict,
|
|
267
|
+
)
|
|
116
268
|
return module_input_serialized
|
|
269
|
+
|
|
270
|
+
def _run_locally(self, module_input_serialized: bytes) -> Result:
|
|
271
|
+
job_dict = BiolibJobApi.create(
|
|
272
|
+
app_version_id=self._app_version['public_id'],
|
|
273
|
+
app_resource_name_prefix=parse_resource_uri(self._app_uri)['resource_prefix'],
|
|
274
|
+
)
|
|
275
|
+
job = Result(job_dict)
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
BiolibJobApi.update_state(job.id, JobState.IN_PROGRESS)
|
|
279
|
+
module_output = JobWorker().run_job_locally(job_dict, module_input_serialized)
|
|
280
|
+
job._set_result_module_output(module_output) # pylint: disable=protected-access
|
|
281
|
+
BiolibJobApi.update_state(job.id, JobState.COMPLETED)
|
|
282
|
+
except BaseException as error:
|
|
283
|
+
BiolibJobApi.update_state(job.id, JobState.FAILED)
|
|
284
|
+
raise error
|
|
285
|
+
|
|
286
|
+
return job
|
|
287
|
+
|
|
288
|
+
def run(self, **kwargs) -> Result:
|
|
289
|
+
args = []
|
|
290
|
+
biolib_kwargs = {}
|
|
291
|
+
for key, value in kwargs.items():
|
|
292
|
+
if key.startswith('biolib_'):
|
|
293
|
+
biolib_kwarg_key = key.replace('biolib_', '')
|
|
294
|
+
biolib_kwargs[biolib_kwarg_key] = value
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
if isinstance(value, dict):
|
|
298
|
+
value = JsonStringIO(json.dumps(value))
|
|
299
|
+
elif isinstance(value, (int, float)): # Cast numeric values to strings
|
|
300
|
+
value = str(value)
|
|
301
|
+
|
|
302
|
+
if not key.startswith('--'):
|
|
303
|
+
key = f'--{key}'
|
|
304
|
+
|
|
305
|
+
args.append(key)
|
|
306
|
+
if isinstance(value, list):
|
|
307
|
+
# TODO: only do this if argument key is of type file list
|
|
308
|
+
args.extend(value)
|
|
309
|
+
else:
|
|
310
|
+
args.append(value)
|
|
311
|
+
|
|
312
|
+
# Set check=True by default if not explicitly provided and not in non-blocking mode
|
|
313
|
+
if 'check' not in biolib_kwargs and biolib_kwargs.get('blocking', True) is not False:
|
|
314
|
+
biolib_kwargs['check'] = True
|
|
315
|
+
|
|
316
|
+
return self.cli(args, **biolib_kwargs)
|
|
317
|
+
|
|
318
|
+
def start(self, **kwargs) -> Result:
|
|
319
|
+
return self.run(biolib_blocking=False, **kwargs)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# TODO: Fix ignore of type
|
|
2
|
+
# type: ignore
|
|
3
|
+
from biolib import api, utils
|
|
4
|
+
from biolib.typing_utils import Optional, List
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def search_apps(
|
|
8
|
+
search_query: Optional[str] = None,
|
|
9
|
+
team: Optional[str] = None,
|
|
10
|
+
count: int = 100,
|
|
11
|
+
) -> List[str]:
|
|
12
|
+
query_exceeded_page_size = False
|
|
13
|
+
params = {
|
|
14
|
+
'page_size': count,
|
|
15
|
+
}
|
|
16
|
+
if team:
|
|
17
|
+
if not team.startswith('@'):
|
|
18
|
+
team = '@biolib.com/' + team
|
|
19
|
+
params['account_handle'] = team
|
|
20
|
+
|
|
21
|
+
if search_query:
|
|
22
|
+
params['search'] = search_query
|
|
23
|
+
|
|
24
|
+
apps_json = api.client.get(path='/apps/', params=params).json()
|
|
25
|
+
if apps_json['count'] > count:
|
|
26
|
+
query_exceeded_page_size = True
|
|
27
|
+
|
|
28
|
+
apps = [app['resource_uri'] for app in apps_json['results']]
|
|
29
|
+
|
|
30
|
+
if not utils.BASE_URL_IS_PUBLIC_BIOLIB and (not team or team.lower().startswith('@biolib.com')):
|
|
31
|
+
# Also get federated apps if running on enterprise deployment
|
|
32
|
+
public_biolib_apps_json = api.client.get(
|
|
33
|
+
authenticate=False,
|
|
34
|
+
path='https://biolib.com/api/apps/',
|
|
35
|
+
params=params,
|
|
36
|
+
).json()
|
|
37
|
+
if public_biolib_apps_json['count'] > count:
|
|
38
|
+
query_exceeded_page_size = True
|
|
39
|
+
|
|
40
|
+
apps.extend([f"@biolib.com/{app['resource_uri']}" for app in public_biolib_apps_json['results']])
|
|
41
|
+
|
|
42
|
+
if query_exceeded_page_size:
|
|
43
|
+
print(f'Search results exceeded {count}, use the argument "count" to increase the amount of results returned')
|
|
44
|
+
|
|
45
|
+
return apps
|
|
@@ -1,46 +1,130 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from datetime import datetime, timezone
|
|
2
3
|
from json.decoder import JSONDecodeError
|
|
3
4
|
|
|
4
|
-
import
|
|
5
|
-
from biolib.
|
|
5
|
+
from biolib._internal.http_client import HttpClient
|
|
6
|
+
from biolib._internal.utils.auth import decode_jwt_without_checking_signature
|
|
7
|
+
from biolib._runtime.runtime import Runtime
|
|
6
8
|
from biolib.biolib_errors import BioLibError
|
|
7
|
-
from biolib.biolib_logging import logger
|
|
9
|
+
from biolib.biolib_logging import logger, logger_no_user_data
|
|
10
|
+
from biolib.typing_utils import Optional, TypedDict
|
|
11
|
+
|
|
12
|
+
from .user_state import UserState
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UserTokens(TypedDict):
|
|
16
|
+
access: str
|
|
17
|
+
refresh: str
|
|
8
18
|
|
|
9
19
|
|
|
10
20
|
class _ApiClient:
|
|
11
21
|
def __init__(self, base_url: str, access_token: Optional[str] = None):
|
|
12
22
|
self.base_url: str = base_url
|
|
23
|
+
self.access_token: Optional[str] = access_token # TODO: Deprecate passing access_token in constructor
|
|
13
24
|
self.refresh_token: Optional[str] = None
|
|
14
|
-
self.
|
|
25
|
+
self.resource_deploy_key: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
self._user_state = UserState()
|
|
28
|
+
self._sign_in_attempted: bool = False
|
|
15
29
|
|
|
16
30
|
@property
|
|
17
31
|
def is_signed_in(self) -> bool:
|
|
18
|
-
return self.refresh_token
|
|
32
|
+
return bool(self.refresh_token or self.resource_deploy_key)
|
|
19
33
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
def set_user_tokens(self, user_tokens: UserTokens) -> None:
|
|
35
|
+
with self._user_state as user_state:
|
|
36
|
+
user_state['refresh_token'] = user_tokens['refresh']
|
|
37
|
+
|
|
38
|
+
self.access_token = user_tokens['access']
|
|
39
|
+
self.refresh_token = user_tokens['refresh']
|
|
40
|
+
|
|
41
|
+
def sign_out(self) -> None:
|
|
42
|
+
api_token = os.getenv('BIOLIB_TOKEN', default=None)
|
|
43
|
+
if api_token:
|
|
44
|
+
print('To sign out unset the environment variable "BIOLIB_TOKEN"')
|
|
45
|
+
|
|
46
|
+
self.access_token = None
|
|
47
|
+
self.refresh_token = None
|
|
48
|
+
|
|
49
|
+
with self._user_state as user_state:
|
|
50
|
+
user_state['refresh_token'] = None
|
|
51
|
+
|
|
52
|
+
def refresh_access_token(self) -> None:
|
|
53
|
+
if not self.is_signed_in or self.resource_deploy_key:
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
if self.access_token:
|
|
57
|
+
decoded_token = decode_jwt_without_checking_signature(self.access_token)
|
|
58
|
+
if datetime.now(tz=timezone.utc).timestamp() < decoded_token['payload']['exp'] - 60: # 60 second buffer
|
|
59
|
+
# Token has not expired yet
|
|
26
60
|
return
|
|
27
61
|
|
|
28
|
-
|
|
29
|
-
f'{self.base_url}/api/user/api_tokens/exchange/',
|
|
30
|
-
json={'token': api_token},
|
|
31
|
-
)
|
|
62
|
+
# TODO: Implement nicer error handling
|
|
32
63
|
try:
|
|
33
|
-
|
|
64
|
+
response = HttpClient.request(
|
|
65
|
+
method='POST',
|
|
66
|
+
url=f'{self.base_url}/api/user/token/refresh/',
|
|
67
|
+
data={'refresh': self.refresh_token},
|
|
68
|
+
)
|
|
69
|
+
except Exception as exception:
|
|
70
|
+
logger.error('Sign in with refresh token failed')
|
|
71
|
+
raise exception
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
response_dict = response.json()
|
|
34
75
|
except JSONDecodeError as error:
|
|
35
76
|
logger.error('Could not decode response from server as JSON:')
|
|
36
77
|
raise BioLibError(response.text) from error
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
78
|
+
|
|
79
|
+
self.access_token = response_dict['access']
|
|
80
|
+
|
|
81
|
+
def attempt_sign_in(self) -> None:
|
|
82
|
+
if not self._sign_in_attempted:
|
|
83
|
+
self._attempt_sign_in()
|
|
84
|
+
self._sign_in_attempted = True
|
|
85
|
+
|
|
86
|
+
def _attempt_sign_in(self) -> None:
|
|
87
|
+
api_token = os.getenv('BIOLIB_TOKEN', default=None)
|
|
88
|
+
|
|
89
|
+
if api_token:
|
|
90
|
+
if api_token.startswith('bld_'):
|
|
91
|
+
self.resource_deploy_key = api_token
|
|
92
|
+
else:
|
|
93
|
+
self.sign_in_with_api_token(api_token)
|
|
40
94
|
else:
|
|
41
|
-
self.
|
|
42
|
-
|
|
43
|
-
|
|
95
|
+
with self._user_state as user_state:
|
|
96
|
+
refresh_token_from_state = user_state['refresh_token']
|
|
97
|
+
|
|
98
|
+
# TODO: Handle expired refresh token
|
|
99
|
+
if refresh_token_from_state:
|
|
100
|
+
logger_no_user_data.debug('ApiClient: Signing in with refresh token from user state...')
|
|
101
|
+
self.refresh_token = refresh_token_from_state
|
|
102
|
+
try:
|
|
103
|
+
self.refresh_access_token()
|
|
104
|
+
except Exception: # pylint: disable=broad-except
|
|
105
|
+
self.refresh_token = None
|
|
106
|
+
with self._user_state as user_state:
|
|
107
|
+
user_state['refresh_token'] = None
|
|
108
|
+
|
|
109
|
+
def sign_in_with_api_token(self, api_token: str) -> None:
|
|
110
|
+
logger_no_user_data.debug('ApiClient: Signing in with BIOLIB_TOKEN...')
|
|
111
|
+
try:
|
|
112
|
+
response = HttpClient.request(
|
|
113
|
+
method='POST',
|
|
114
|
+
url=f'{self.base_url}/api/user/api_tokens/exchange/',
|
|
115
|
+
data={'token': api_token},
|
|
116
|
+
)
|
|
117
|
+
except Exception as exception:
|
|
118
|
+
logger.error('Sign in with API token failed')
|
|
119
|
+
raise exception
|
|
120
|
+
try:
|
|
121
|
+
json_response = response.json()
|
|
122
|
+
except JSONDecodeError as error:
|
|
123
|
+
logger.error('Could not decode response from server as JSON')
|
|
124
|
+
raise BioLibError(response.text) from error
|
|
125
|
+
|
|
126
|
+
self.access_token = json_response['access_token']
|
|
127
|
+
self.refresh_token = json_response['refresh_token']
|
|
44
128
|
|
|
45
129
|
|
|
46
130
|
class BiolibApiClient:
|
|
@@ -48,23 +132,34 @@ class BiolibApiClient:
|
|
|
48
132
|
|
|
49
133
|
@staticmethod
|
|
50
134
|
def initialize(base_url: str, access_token: Optional[str] = None):
|
|
51
|
-
BiolibApiClient.api_client = _ApiClient(base_url, access_token)
|
|
135
|
+
BiolibApiClient.api_client = _ApiClient(base_url=base_url, access_token=access_token)
|
|
52
136
|
|
|
53
137
|
@staticmethod
|
|
54
|
-
def get() -> _ApiClient:
|
|
138
|
+
def get(attempt_sign_in: bool = True) -> _ApiClient:
|
|
55
139
|
api_client = BiolibApiClient.api_client
|
|
56
|
-
if api_client
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
api_client.login(api_token=biolib_token, exit_on_failure=True)
|
|
140
|
+
if api_client:
|
|
141
|
+
if attempt_sign_in:
|
|
142
|
+
api_client.attempt_sign_in()
|
|
60
143
|
return api_client
|
|
144
|
+
|
|
145
|
+
raise BioLibError('Attempted to use uninitialized API client')
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def refresh_auth_token():
|
|
149
|
+
api_client = BiolibApiClient.get()
|
|
150
|
+
api_client.refresh_access_token()
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def is_reauthentication_needed() -> bool:
|
|
154
|
+
api_client = BiolibApiClient.get()
|
|
155
|
+
if not api_client.is_signed_in and not Runtime.check_is_environment_biolib_app():
|
|
156
|
+
return True
|
|
61
157
|
else:
|
|
62
|
-
|
|
158
|
+
return False
|
|
63
159
|
|
|
64
160
|
@staticmethod
|
|
65
161
|
def assert_is_signed_in(authenticated_action_description: str) -> None:
|
|
66
|
-
|
|
67
|
-
if not api_client.is_signed_in:
|
|
162
|
+
if BiolibApiClient.is_reauthentication_needed():
|
|
68
163
|
raise BioLibError(
|
|
69
164
|
f'You must be signed in to {authenticated_action_description}. '
|
|
70
165
|
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):
|
|
@@ -14,6 +14,9 @@ class AppVersion(AppVersionSlim):
|
|
|
14
14
|
description: str
|
|
15
15
|
is_runnable_by_user: bool
|
|
16
16
|
source_code_license: str
|
|
17
|
+
stdout_render_type: Literal['text', 'markdown']
|
|
18
|
+
main_output_file: Optional[str]
|
|
19
|
+
app_uri: str
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
class App(TypedDict):
|
|
@@ -28,6 +31,8 @@ class App(TypedDict):
|
|
|
28
31
|
name: str
|
|
29
32
|
public_id: str
|
|
30
33
|
state: str
|
|
34
|
+
resource_uri: str
|
|
35
|
+
type: str
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
class AppGetResponse(TypedDict):
|
|
@@ -57,34 +62,49 @@ class FilesMapping(TypedDict):
|
|
|
57
62
|
|
|
58
63
|
|
|
59
64
|
class LargeFileSystemMapping(TypedDict):
|
|
60
|
-
|
|
65
|
+
presigned_download_url: str
|
|
61
66
|
size_bytes: int
|
|
62
67
|
to_path: str
|
|
68
|
+
uuid: str
|
|
63
69
|
|
|
64
70
|
|
|
65
|
-
class
|
|
71
|
+
class _Module(TypedDict):
|
|
66
72
|
command: str
|
|
67
73
|
environment: Literal['biolib-app', 'biolib-custom', 'biolib-ecr']
|
|
68
74
|
image_uri: str
|
|
75
|
+
absolute_image_uri: str
|
|
69
76
|
estimated_image_size_bytes: Optional[int]
|
|
70
77
|
input_files_mappings: List[FilesMapping]
|
|
71
78
|
large_file_systems: List[LargeFileSystemMapping]
|
|
72
79
|
name: str
|
|
73
80
|
output_files_mappings: List[FilesMapping]
|
|
81
|
+
ports: List[int]
|
|
74
82
|
source_files_mappings: List[FilesMapping]
|
|
75
83
|
working_directory: str
|
|
76
84
|
|
|
77
85
|
|
|
86
|
+
# type optional keys with total=False
|
|
87
|
+
class Module(_Module, total=False):
|
|
88
|
+
secrets: Dict[str, str]
|
|
89
|
+
|
|
90
|
+
|
|
78
91
|
class _AppVersionOnJob(TypedDict):
|
|
92
|
+
created_at: str
|
|
79
93
|
client_side_executable_zip: Optional[str]
|
|
80
|
-
consumes_stdin: bool
|
|
81
94
|
is_runnable_by_user: bool
|
|
82
95
|
public_id: str
|
|
83
96
|
remote_hosts: List[RemoteHost]
|
|
84
97
|
settings: List[Dict]
|
|
85
98
|
stdout_render_type: Literal['text', 'markdown']
|
|
99
|
+
main_output_file: Optional[str]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class AppOnJob(TypedDict):
|
|
103
|
+
allow_client_side_execution: bool
|
|
104
|
+
state: Literal['public', 'draft']
|
|
86
105
|
|
|
87
106
|
|
|
88
107
|
# type optional keys with total=False
|
|
89
108
|
class AppVersionOnJob(_AppVersionOnJob, total=False):
|
|
109
|
+
app: AppOnJob
|
|
90
110
|
modules: List[Module]
|