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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from urllib.parse import urlparse
|
|
3
|
+
|
|
4
|
+
import biolib.utils
|
|
5
|
+
from biolib.typing_utils import Optional, Tuple
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse_result_id_or_url(result_id_or_url: str, default_token: Optional[str] = None) -> Tuple[str, Optional[str]]:
|
|
9
|
+
result_id_or_url = result_id_or_url.strip()
|
|
10
|
+
|
|
11
|
+
if '/' not in result_id_or_url:
|
|
12
|
+
return (result_id_or_url, default_token)
|
|
13
|
+
|
|
14
|
+
if not result_id_or_url.startswith('http://') and not result_id_or_url.startswith('https://'):
|
|
15
|
+
result_id_or_url = 'https://' + result_id_or_url
|
|
16
|
+
|
|
17
|
+
parsed_url = urlparse(result_id_or_url)
|
|
18
|
+
|
|
19
|
+
if biolib.utils.BIOLIB_BASE_URL:
|
|
20
|
+
expected_base = urlparse(biolib.utils.BIOLIB_BASE_URL)
|
|
21
|
+
if parsed_url.scheme != expected_base.scheme or parsed_url.netloc != expected_base.netloc:
|
|
22
|
+
raise ValueError(f'URL must start with {biolib.utils.BIOLIB_BASE_URL}, got: {result_id_or_url}')
|
|
23
|
+
|
|
24
|
+
pattern = r'/results?/(?P<uuid>[a-f0-9-]+)/?(?:\?token=(?P<token>[^&]+))?'
|
|
25
|
+
match = re.search(pattern, result_id_or_url, re.IGNORECASE)
|
|
26
|
+
|
|
27
|
+
if not match:
|
|
28
|
+
raise ValueError(f'URL must be in format <base_url>/results/<UUID>/?token=<token>, got: {result_id_or_url}')
|
|
29
|
+
|
|
30
|
+
uuid = match.group('uuid')
|
|
31
|
+
token = match.group('token') or default_token
|
|
32
|
+
|
|
33
|
+
return (uuid, token)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
|
|
8
|
+
import biolib
|
|
9
|
+
from biolib.utils import SeqUtil
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def natsorted(lst):
|
|
13
|
+
"""Sort the list using the natural sort key."""
|
|
14
|
+
|
|
15
|
+
def _natural_sort_key(s):
|
|
16
|
+
"""A key function for natural sorting."""
|
|
17
|
+
return [int(text) if text.isdigit() else text.lower() for text in re.split('([0-9]+)', s)]
|
|
18
|
+
|
|
19
|
+
return sorted(lst, key=_natural_sort_key)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def fasta_above_threshold(fasta_file, work_threshold, work_per_residue=1, verbose=False):
|
|
23
|
+
"""True if total FASYA residue work above max_work"""
|
|
24
|
+
|
|
25
|
+
records = SeqUtil.parse_fasta(fasta_file)
|
|
26
|
+
|
|
27
|
+
# Calculate work units
|
|
28
|
+
total_work_units = 0
|
|
29
|
+
for i, record in enumerate(records):
|
|
30
|
+
sequence_work_units = len(record.sequence) * work_per_residue
|
|
31
|
+
total_work_units += sequence_work_units
|
|
32
|
+
|
|
33
|
+
if total_work_units >= work_threshold:
|
|
34
|
+
if verbose:
|
|
35
|
+
print(f'FASTA above threshold (stopped at {total_work_units}) >= {work_threshold}')
|
|
36
|
+
print(f'From from {i+1}/{len(records)} sequences in {fasta_file}')
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
if verbose:
|
|
40
|
+
print(f'FASTA below threshold ({total_work_units}) < {work_threshold}')
|
|
41
|
+
print(f'From {len(records)} sequences in {fasta_file}')
|
|
42
|
+
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def run_locally(command_list, args):
|
|
47
|
+
"""Run script locally (no multi-node processing)"""
|
|
48
|
+
|
|
49
|
+
# Prepare command
|
|
50
|
+
new_args = vars(args)
|
|
51
|
+
|
|
52
|
+
# Delete multinode-specific input arguments
|
|
53
|
+
for k in list(new_args.keys()):
|
|
54
|
+
if str(k).startswith('multinode'):
|
|
55
|
+
del new_args[k]
|
|
56
|
+
|
|
57
|
+
# Convert to list format
|
|
58
|
+
new_args_list = _args_dict_to_args_list(new_args)
|
|
59
|
+
|
|
60
|
+
# Prepare command, e.g. ["python3", "predict.py"] + new_args_list
|
|
61
|
+
command = command_list + new_args_list
|
|
62
|
+
|
|
63
|
+
if args.verbose >= 1:
|
|
64
|
+
print(f'Running {command}')
|
|
65
|
+
|
|
66
|
+
# Run command
|
|
67
|
+
result = subprocess.run(command, capture_output=True, text=True, check=False)
|
|
68
|
+
if result.returncode == 0:
|
|
69
|
+
print(f'{result.stdout}')
|
|
70
|
+
else:
|
|
71
|
+
print(f'Error: {result.stderr}')
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def fasta_batch_records(records, work_per_batch_min, work_per_residue=1, verbose=False):
|
|
75
|
+
"""Converts FASTA records to batches of records, based on thresholds"""
|
|
76
|
+
|
|
77
|
+
def log_batches(batches):
|
|
78
|
+
for i, batch in enumerate(batches):
|
|
79
|
+
batch_dict = {
|
|
80
|
+
'records': len(batch),
|
|
81
|
+
'residues': sum(len(record.sequence) for record in batch),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
n_seqs, n_res = batch_dict['records'], batch_dict['residues']
|
|
85
|
+
print(f'Batch {i+1}: {n_res} residues from {n_seqs} sequences')
|
|
86
|
+
|
|
87
|
+
batches = []
|
|
88
|
+
batch = []
|
|
89
|
+
current_longest_seq_len = 0
|
|
90
|
+
for record in records:
|
|
91
|
+
seq_len = len(record.sequence)
|
|
92
|
+
potential_longest_seq_len = max(current_longest_seq_len, seq_len)
|
|
93
|
+
|
|
94
|
+
# Calculate work units if we were to add this record
|
|
95
|
+
potential_work_units = potential_longest_seq_len * work_per_residue * (len(batch) + 1)
|
|
96
|
+
|
|
97
|
+
if potential_work_units >= work_per_batch_min and len(batch) > 0:
|
|
98
|
+
batches.append(batch)
|
|
99
|
+
batch = []
|
|
100
|
+
current_longest_seq_len = 0
|
|
101
|
+
potential_longest_seq_len = seq_len
|
|
102
|
+
|
|
103
|
+
# Add to batch
|
|
104
|
+
batch.append(record)
|
|
105
|
+
current_longest_seq_len = potential_longest_seq_len
|
|
106
|
+
|
|
107
|
+
# Append last batch if present
|
|
108
|
+
if batch:
|
|
109
|
+
batches.append(batch)
|
|
110
|
+
|
|
111
|
+
if verbose:
|
|
112
|
+
log_batches(batches)
|
|
113
|
+
|
|
114
|
+
return batches
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def fasta_send_batches_biolib(
|
|
118
|
+
app_url, batches, args, args_fasta='fasta', machine='cpu.large', stream_all_jobs=True, verbose=1
|
|
119
|
+
):
|
|
120
|
+
"""
|
|
121
|
+
Send jobs through pybiolib interface
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
if args.verbose >= 1:
|
|
125
|
+
print(f'Sending {len(batches)} batches to Biolib')
|
|
126
|
+
|
|
127
|
+
# Login to biolib, prepare app
|
|
128
|
+
# current_app = biolib.load(Runtime.get_app_uri())
|
|
129
|
+
current_app = biolib.load(app_url) # Nb: uses "_" not "-"
|
|
130
|
+
|
|
131
|
+
# Compute results
|
|
132
|
+
job_list = []
|
|
133
|
+
for i, batch_records in enumerate(batches): # MH
|
|
134
|
+
# Write FASTA, send to server
|
|
135
|
+
with tempfile.TemporaryDirectory() as tempdir:
|
|
136
|
+
# New arguments
|
|
137
|
+
new_args = vars(args)
|
|
138
|
+
|
|
139
|
+
# Write batched FASTA to send
|
|
140
|
+
fasta_path = f'{tempdir}/input.fasta'
|
|
141
|
+
SeqUtil.write_records_to_fasta(fasta_path, batch_records)
|
|
142
|
+
new_args[args_fasta] = fasta_path
|
|
143
|
+
new_args['multinode_only_local'] = True
|
|
144
|
+
|
|
145
|
+
# Convert to list
|
|
146
|
+
new_args_list = _args_dict_to_args_list(new_args)
|
|
147
|
+
|
|
148
|
+
# Send job
|
|
149
|
+
job = current_app.cli(args=new_args_list, blocking=False, machine=machine)
|
|
150
|
+
job_list.append(job)
|
|
151
|
+
|
|
152
|
+
# Job stats
|
|
153
|
+
if args.verbose:
|
|
154
|
+
batch_dict = _get_batch_stats(batch_records)
|
|
155
|
+
n_seqs, n_res = batch_dict['records'], batch_dict['residues']
|
|
156
|
+
print(f'Sending job {i+1}: {n_res} residues from {n_seqs} sequences -> arg_list = {new_args_list}')
|
|
157
|
+
|
|
158
|
+
# Stream job output at a time
|
|
159
|
+
print('Streaming job outputs ...')
|
|
160
|
+
for i, job in enumerate(job_list):
|
|
161
|
+
# Try to print if verbose. Always on first job, otherwise only if stream_all_jobs set
|
|
162
|
+
if (i == 0 and verbose) or (stream_all_jobs and verbose):
|
|
163
|
+
job.stream_logs()
|
|
164
|
+
|
|
165
|
+
# Check if job succeeded
|
|
166
|
+
assert job.get_exit_code() == 0, f'Job failed with exit code {job.get_exit_code()}'
|
|
167
|
+
|
|
168
|
+
# Write to disk
|
|
169
|
+
output_dir = f'job_output/job_{i+1}'
|
|
170
|
+
job.save_files(output_dir=output_dir)
|
|
171
|
+
|
|
172
|
+
if verbose:
|
|
173
|
+
print(f'Saving to {output_dir}')
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def merge_folder(folder_name, job_out_dir='job_output', out_dir='output', verbose=1):
|
|
177
|
+
"""Helper function for merging folders"""
|
|
178
|
+
|
|
179
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
180
|
+
|
|
181
|
+
job_dirs = glob.glob(f'{job_out_dir}/job_*')
|
|
182
|
+
job_dirs = natsorted(job_dirs)
|
|
183
|
+
|
|
184
|
+
# Move first file, prepare to merge
|
|
185
|
+
first_folder = f'{job_dirs[0]}/{folder_name}'
|
|
186
|
+
merged_folder = f'{out_dir}/{folder_name}'
|
|
187
|
+
shutil.move(first_folder, merged_folder)
|
|
188
|
+
|
|
189
|
+
if verbose:
|
|
190
|
+
print(f'Merging {folder_name} from {len(job_dirs)} directories to {merged_folder}')
|
|
191
|
+
|
|
192
|
+
# If more than one folder, merge to first
|
|
193
|
+
if len(job_dirs) >= 2:
|
|
194
|
+
# Find each job output file
|
|
195
|
+
for job_dir in job_dirs[1:]:
|
|
196
|
+
# Move over extra files
|
|
197
|
+
extra_folder = f'{job_dir}/{folder_name}'
|
|
198
|
+
extra_files = os.listdir(extra_folder)
|
|
199
|
+
for file_name in extra_files:
|
|
200
|
+
file_path = f'{extra_folder}/{file_name}'
|
|
201
|
+
shutil.move(file_path, merged_folder)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def merge_file(
|
|
205
|
+
file_name,
|
|
206
|
+
header_lines_int=1,
|
|
207
|
+
job_out_dir='job_output',
|
|
208
|
+
out_dir='output',
|
|
209
|
+
verbose=1,
|
|
210
|
+
):
|
|
211
|
+
"""Helper function for merging files with headers"""
|
|
212
|
+
|
|
213
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
214
|
+
|
|
215
|
+
job_dirs = glob.glob(f'{job_out_dir}/job_*')
|
|
216
|
+
job_dirs = natsorted(job_dirs)
|
|
217
|
+
|
|
218
|
+
# Move first file, prepare to merge
|
|
219
|
+
first_file = f'{job_dirs[0]}/{file_name}'
|
|
220
|
+
merged_file = f'{out_dir}/{file_name}'
|
|
221
|
+
shutil.move(first_file, merged_file)
|
|
222
|
+
|
|
223
|
+
if verbose:
|
|
224
|
+
print(f'Merging {file_name} from {len(job_dirs)} directories to {merged_file}')
|
|
225
|
+
|
|
226
|
+
# If more than one file, append to first
|
|
227
|
+
if len(job_dirs) >= 2:
|
|
228
|
+
# Open first file
|
|
229
|
+
with open(merged_file, 'a') as merged_file_handle:
|
|
230
|
+
# Find each job output file
|
|
231
|
+
for job_dir in job_dirs[1:]:
|
|
232
|
+
# Open extra file
|
|
233
|
+
extra_file = f'{job_dir}/{file_name}'
|
|
234
|
+
with open(extra_file) as extra_file_handle:
|
|
235
|
+
# Skip first n header lines
|
|
236
|
+
for _ in range(header_lines_int):
|
|
237
|
+
next(extra_file_handle)
|
|
238
|
+
|
|
239
|
+
# Append content to first file
|
|
240
|
+
contents = extra_file_handle.read()
|
|
241
|
+
merged_file_handle.write(contents)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _get_batch_stats(batch):
|
|
245
|
+
stats_dict = {
|
|
246
|
+
'records': len(batch),
|
|
247
|
+
'residues': sum(len(R.sequence) for R in batch),
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return stats_dict
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _args_dict_to_args_list(new_args):
|
|
254
|
+
"""Converts args dict to list of arguments for Biolib"""
|
|
255
|
+
|
|
256
|
+
nested_list = [[f'--{key}', f'{value}'] for key, value in new_args.items()]
|
|
257
|
+
|
|
258
|
+
arg_list = []
|
|
259
|
+
for lst in nested_list:
|
|
260
|
+
for item in lst:
|
|
261
|
+
arg_list.append(item)
|
|
262
|
+
|
|
263
|
+
return arg_list
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from biolib import api
|
|
7
|
+
from biolib._internal.runtime import BioLibRuntimeError, BioLibRuntimeNotRecognizedError, RuntimeJobDataDict
|
|
8
|
+
from biolib.biolib_logging import logger
|
|
9
|
+
from biolib.typing_utils import cast
|
|
10
|
+
from biolib.utils.seq_util import SeqUtil
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Runtime:
|
|
14
|
+
_job_data: Optional[RuntimeJobDataDict] = None
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def check_is_environment_biolib_app() -> bool:
|
|
18
|
+
return bool(Runtime._try_to_get_job_data())
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def check_is_environment_biolib_cloud() -> bool:
|
|
22
|
+
return Runtime._get_job_data().get('is_environment_biolib_cloud', False)
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def get_job_id() -> str:
|
|
26
|
+
return Runtime._get_job_data()['job_uuid']
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def get_job_auth_token() -> str:
|
|
30
|
+
return Runtime._get_job_data()['job_auth_token']
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def get_job_requested_machine() -> Optional[str]:
|
|
34
|
+
job_data = Runtime._get_job_data()
|
|
35
|
+
job_requested_machine = job_data.get('job_requested_machine')
|
|
36
|
+
if not job_requested_machine:
|
|
37
|
+
return None
|
|
38
|
+
return job_requested_machine
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def is_spot_machine_requested() -> bool:
|
|
42
|
+
job_data = Runtime._get_job_data()
|
|
43
|
+
return job_data.get('job_requested_machine_spot', False)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def get_app_uri() -> str:
|
|
47
|
+
return Runtime._get_job_data()['app_uri']
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def get_max_workers() -> int:
|
|
51
|
+
return Runtime._get_job_data()['job_reserved_machines']
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def get_secret(secret_name: str) -> bytes:
|
|
55
|
+
assert re.match(
|
|
56
|
+
'^[a-zA-Z0-9_-]*$', secret_name
|
|
57
|
+
), 'Secret name can only contain alphanumeric characters and dashes or underscores '
|
|
58
|
+
try:
|
|
59
|
+
with open(f'/biolib/secrets/{secret_name}', 'rb') as file:
|
|
60
|
+
return file.read()
|
|
61
|
+
except BaseException as error:
|
|
62
|
+
raise BioLibRuntimeError(f'Unable to get system secret: {secret_name}') from error
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def get_temporary_client_secret(secret_name: str) -> bytes:
|
|
66
|
+
assert re.match(
|
|
67
|
+
'^[a-zA-Z0-9_-]*$', secret_name
|
|
68
|
+
), 'Secret name can only contain alphanumeric characters and dashes or underscores '
|
|
69
|
+
try:
|
|
70
|
+
with open(f'/biolib/temporary-client-secrets/{secret_name}', 'rb') as file:
|
|
71
|
+
return file.read()
|
|
72
|
+
except BaseException as error:
|
|
73
|
+
raise BioLibRuntimeError(f'Unable to get secret: {secret_name}') from error
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def set_main_result_prefix(result_prefix: str) -> None:
|
|
77
|
+
job_data = Runtime._get_job_data()
|
|
78
|
+
api.client.patch(
|
|
79
|
+
data={'result_name_prefix': result_prefix},
|
|
80
|
+
headers={'Job-Auth-Token': job_data['job_auth_token']},
|
|
81
|
+
path=f"/jobs/{job_data['job_uuid']}/main_result/",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def set_result_name_prefix(result_prefix: str) -> None:
|
|
86
|
+
Runtime.set_main_result_prefix(result_prefix)
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def set_result_name_prefix_from_fasta(path_to_fasta: str) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Set BioLib result name to FASTA file name (excluding file extension) or,
|
|
92
|
+
if detecting a BioLib generated name, use ID of the first sequence in the fasta file
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
# Set job name to first header if FASTA text input (random BioLib file) detected
|
|
96
|
+
if re.match('^input_[A-Za-z0-9]+.*', path_to_fasta):
|
|
97
|
+
first_id = next(SeqUtil.parse_fasta(path_to_fasta)).id
|
|
98
|
+
result_name = first_id.replace(' ', '_')[:60]
|
|
99
|
+
else:
|
|
100
|
+
result_name = Path(path_to_fasta).name
|
|
101
|
+
|
|
102
|
+
logger.debug(f'Setting result name to "{result_name}" from {path_to_fasta}')
|
|
103
|
+
Runtime.set_result_name_prefix(result_name)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.warning(f'Failed to set result name from fasta file {path_to_fasta}: {str(e)}')
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def set_result_name_from_file(path_to_file: str) -> None:
|
|
109
|
+
try:
|
|
110
|
+
if path_to_file.lower().endswith('.fasta'):
|
|
111
|
+
return Runtime.set_result_name_prefix_from_fasta(path_to_file)
|
|
112
|
+
|
|
113
|
+
# Set job name only if not a BioLib default name
|
|
114
|
+
if not re.match('^input_[A-Za-z0-9]+.*', path_to_file):
|
|
115
|
+
result_name = Path(path_to_file).name[:60]
|
|
116
|
+
logger.debug(f'Setting result name to "{result_name}" from {path_to_file}')
|
|
117
|
+
Runtime.set_result_name_prefix(result_name)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.warning(f'Failed to set result name from file {path_to_file}: {str(e)}')
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def set_result_name_from_string(result_name: str) -> None:
|
|
123
|
+
try:
|
|
124
|
+
truncated_name = result_name[:60]
|
|
125
|
+
logger.debug(f'Setting result name to "{truncated_name}" from string')
|
|
126
|
+
Runtime.set_result_name_prefix(truncated_name)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.warning(f'Failed to set result name from string: {str(e)}')
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def create_result_note(note: str) -> None:
|
|
132
|
+
job_id = Runtime.get_job_id()
|
|
133
|
+
# Note: Authentication is added by app caller proxy in compute node
|
|
134
|
+
api.client.post(data={'note': note}, path=f'/jobs/{job_id}/notes/')
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def _try_to_get_job_data() -> Optional[RuntimeJobDataDict]:
|
|
138
|
+
if not Runtime._job_data:
|
|
139
|
+
try:
|
|
140
|
+
with open('/biolib/secrets/biolib_system_secret') as file:
|
|
141
|
+
job_data: RuntimeJobDataDict = json.load(file)
|
|
142
|
+
except BaseException:
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
if not job_data['version'].startswith('1.'):
|
|
146
|
+
raise BioLibRuntimeError(f"Unexpected system secret version {job_data['version']} expected 1.x.x")
|
|
147
|
+
|
|
148
|
+
Runtime._job_data = job_data
|
|
149
|
+
|
|
150
|
+
return cast(RuntimeJobDataDict, Runtime._job_data)
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def _get_job_data() -> RuntimeJobDataDict:
|
|
154
|
+
job_data = Runtime._try_to_get_job_data()
|
|
155
|
+
if not job_data:
|
|
156
|
+
raise BioLibRuntimeNotRecognizedError() from None
|
|
157
|
+
return job_data
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from biolib import utils
|
|
2
|
+
from biolib.typing_utils import Optional
|
|
3
|
+
from biolib.api.client import ApiClient, ApiClientInitDict
|
|
4
|
+
from biolib.app import BioLibApp
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Session:
|
|
8
|
+
def __init__(self, _init_dict: ApiClientInitDict, _experiment: Optional[str] = None) -> None:
|
|
9
|
+
self._api = ApiClient(_init_dict=_init_dict)
|
|
10
|
+
self._experiment = _experiment
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def get_session(refresh_token: str, base_url: Optional[str] = None, client_type: Optional[str] = None, experiment: Optional[str] = None) -> 'Session':
|
|
14
|
+
return Session(
|
|
15
|
+
_init_dict=ApiClientInitDict(
|
|
16
|
+
refresh_token=refresh_token,
|
|
17
|
+
base_url=base_url or utils.load_base_url_from_env(),
|
|
18
|
+
client_type=client_type,
|
|
19
|
+
),
|
|
20
|
+
_experiment=experiment,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def load(self, uri: str, suppress_version_warning: bool = False) -> BioLibApp:
|
|
24
|
+
r"""Load a BioLib application by its URI or website URL.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
uri (str): The URI or website URL of the application to load. Can be either:
|
|
28
|
+
- App URI (e.g., 'biolib/myapp:1.0.0')
|
|
29
|
+
- Website URL (e.g., 'https://biolib.com/biolib/myapp/')
|
|
30
|
+
suppress_version_warning (bool): If True, don't print a warning when no version is specified.
|
|
31
|
+
Defaults to False.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
BioLibApp: The loaded application object
|
|
35
|
+
|
|
36
|
+
Example::
|
|
37
|
+
|
|
38
|
+
>>> # Load by URI
|
|
39
|
+
>>> app = biolib.load('biolib/myapp:1.0.0')
|
|
40
|
+
>>> # Load by website URL
|
|
41
|
+
>>> app = biolib.load('https://biolib.com/biolib/myapp/')
|
|
42
|
+
>>> result = app.cli('--help')
|
|
43
|
+
"""
|
|
44
|
+
return BioLibApp(uri=uri, _api_client=self._api, suppress_version_warning=suppress_version_warning, _experiment=self._experiment)
|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from .account import AccountDict, AccountDetailedDict
|
|
2
|
+
from .account_member import AccountMemberDict
|
|
3
|
+
from .app import AppDetailedDict, AppSlimDict
|
|
4
|
+
from .data_record import (
|
|
5
|
+
DataRecordDetailedDict,
|
|
6
|
+
DataRecordSlimDict,
|
|
7
|
+
DataRecordTypeDict,
|
|
8
|
+
DataRecordValidationRuleDict,
|
|
9
|
+
SqliteV1Column,
|
|
10
|
+
SqliteV1DatabaseSchema,
|
|
11
|
+
SqliteV1ForeignKey,
|
|
12
|
+
SqliteV1Table,
|
|
13
|
+
)
|
|
14
|
+
from .experiment import (
|
|
15
|
+
DeprecatedExperimentDict,
|
|
16
|
+
ExperimentDetailedDict,
|
|
17
|
+
ExperimentDict,
|
|
18
|
+
ResultCounts,
|
|
19
|
+
)
|
|
20
|
+
from .file_node import FileNodeDict, FileZipMetadataDict, ZipFileNodeDict
|
|
21
|
+
from .push import PushResponseDict
|
|
22
|
+
from .resource import ResourceDetailedDict, ResourceDict, ResourceTypeLiteral, ResourceUriDict, SemanticVersionDict
|
|
23
|
+
from .resource_deploy_key import ResourceDeployKeyDict, ResourceDeployKeyWithSecretDict
|
|
24
|
+
from .resource_permission import ResourcePermissionDetailedDict, ResourcePermissionDict
|
|
25
|
+
from .resource_version import (
|
|
26
|
+
ResourceVersionAssetsDict,
|
|
27
|
+
ResourceVersionDetailedDict,
|
|
28
|
+
ResourceVersionDict,
|
|
29
|
+
)
|
|
30
|
+
from .result import ResultDetailedDict, ResultDict
|
|
31
|
+
from .typing import Optional
|
|
32
|
+
from .user import EnterpriseSettingsDict, UserDetailedDict, UserDict
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
'AccountDetailedDict',
|
|
36
|
+
'AccountDict',
|
|
37
|
+
'AccountMemberDict',
|
|
38
|
+
'AppDetailedDict',
|
|
39
|
+
'AppSlimDict',
|
|
40
|
+
'DataRecordDetailedDict',
|
|
41
|
+
'DataRecordSlimDict',
|
|
42
|
+
'DataRecordTypeDict',
|
|
43
|
+
'DataRecordValidationRuleDict',
|
|
44
|
+
'DeprecatedExperimentDict',
|
|
45
|
+
'EnterpriseSettingsDict',
|
|
46
|
+
'ExperimentDetailedDict',
|
|
47
|
+
'ExperimentDict',
|
|
48
|
+
'FileNodeDict',
|
|
49
|
+
'FileZipMetadataDict',
|
|
50
|
+
'Optional',
|
|
51
|
+
'PushResponseDict',
|
|
52
|
+
'ResourceDeployKeyDict',
|
|
53
|
+
'ResourceDeployKeyWithSecretDict',
|
|
54
|
+
'ResourceDetailedDict',
|
|
55
|
+
'ResourceDict',
|
|
56
|
+
'ResourceTypeLiteral',
|
|
57
|
+
'ResourcePermissionDetailedDict',
|
|
58
|
+
'ResourcePermissionDict',
|
|
59
|
+
'ResourceUriDict',
|
|
60
|
+
'ResourceVersionAssetsDict',
|
|
61
|
+
'ResourceVersionDetailedDict',
|
|
62
|
+
'ResourceVersionDict',
|
|
63
|
+
'ResultCounts',
|
|
64
|
+
'ResultDetailedDict',
|
|
65
|
+
'ResultDict',
|
|
66
|
+
'SemanticVersionDict',
|
|
67
|
+
'SqliteV1Column',
|
|
68
|
+
'SqliteV1DatabaseSchema',
|
|
69
|
+
'SqliteV1ForeignKey',
|
|
70
|
+
'SqliteV1Table',
|
|
71
|
+
'UserDetailedDict',
|
|
72
|
+
'UserDict',
|
|
73
|
+
'ZipFileNodeDict',
|
|
74
|
+
]
|
|
@@ -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']
|
|
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,32 @@
|
|
|
1
|
+
from .result import ResultDict
|
|
2
|
+
from .typing import Optional, TypedDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ResultCounts(TypedDict):
|
|
6
|
+
cancelled: int
|
|
7
|
+
completed: int
|
|
8
|
+
failed: int
|
|
9
|
+
in_progress: int
|
|
10
|
+
queued: int
|
|
11
|
+
total: int
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DeprecatedExperimentDict(TypedDict):
|
|
15
|
+
# Note: fields on this TypedDict are deprecated
|
|
16
|
+
job_count: int
|
|
17
|
+
job_running_count: int
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ExperimentDict(DeprecatedExperimentDict):
|
|
21
|
+
uuid: Optional[str]
|
|
22
|
+
name: Optional[str]
|
|
23
|
+
account_uuid: Optional[str]
|
|
24
|
+
created_at: Optional[str]
|
|
25
|
+
finished_at: Optional[str]
|
|
26
|
+
last_created_at: Optional[str]
|
|
27
|
+
last_created_result: Optional[ResultDict]
|
|
28
|
+
result_counts: ResultCounts
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ExperimentDetailedDict(ExperimentDict):
|
|
32
|
+
pass
|