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.
Files changed (262) hide show
  1. biolib/__init__.py +357 -11
  2. biolib/_data_record/data_record.py +380 -0
  3. biolib/_index/__init__.py +0 -0
  4. biolib/_index/index.py +55 -0
  5. biolib/_index/query_result.py +103 -0
  6. biolib/_internal/__init__.py +0 -0
  7. biolib/_internal/add_copilot_prompts.py +58 -0
  8. biolib/_internal/add_gui_files.py +81 -0
  9. biolib/_internal/data_record/__init__.py +1 -0
  10. biolib/_internal/data_record/data_record.py +85 -0
  11. biolib/_internal/data_record/push_data.py +116 -0
  12. biolib/_internal/data_record/remote_storage_endpoint.py +43 -0
  13. biolib/_internal/errors.py +5 -0
  14. biolib/_internal/file_utils.py +125 -0
  15. biolib/_internal/fuse_mount/__init__.py +1 -0
  16. biolib/_internal/fuse_mount/experiment_fuse_mount.py +209 -0
  17. biolib/_internal/http_client.py +159 -0
  18. biolib/_internal/lfs/__init__.py +1 -0
  19. biolib/_internal/lfs/cache.py +51 -0
  20. biolib/_internal/libs/__init__.py +1 -0
  21. biolib/_internal/libs/fusepy/__init__.py +1257 -0
  22. biolib/_internal/push_application.py +488 -0
  23. biolib/_internal/runtime.py +22 -0
  24. biolib/_internal/string_utils.py +13 -0
  25. biolib/_internal/templates/__init__.py +1 -0
  26. biolib/_internal/templates/copilot_template/.github/instructions/general-app-knowledge.instructions.md +10 -0
  27. biolib/_internal/templates/copilot_template/.github/instructions/style-general.instructions.md +20 -0
  28. biolib/_internal/templates/copilot_template/.github/instructions/style-python.instructions.md +16 -0
  29. biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
  30. biolib/_internal/templates/copilot_template/.github/prompts/biolib_app_inputs.prompt.md +11 -0
  31. biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
  32. biolib/_internal/templates/copilot_template/.github/prompts/biolib_run_apps.prompt.md +12 -0
  33. biolib/_internal/templates/dashboard_template/.biolib/config.yml +5 -0
  34. biolib/_internal/templates/github_workflow_template/.github/workflows/biolib.yml +21 -0
  35. biolib/_internal/templates/gitignore_template/.gitignore +10 -0
  36. biolib/_internal/templates/gui_template/.yarnrc.yml +1 -0
  37. biolib/_internal/templates/gui_template/App.tsx +53 -0
  38. biolib/_internal/templates/gui_template/Dockerfile +27 -0
  39. biolib/_internal/templates/gui_template/biolib-sdk.ts +82 -0
  40. biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
  41. biolib/_internal/templates/gui_template/index.css +5 -0
  42. biolib/_internal/templates/gui_template/index.html +13 -0
  43. biolib/_internal/templates/gui_template/index.tsx +10 -0
  44. biolib/_internal/templates/gui_template/package.json +27 -0
  45. biolib/_internal/templates/gui_template/tsconfig.json +24 -0
  46. biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +50 -0
  47. biolib/_internal/templates/gui_template/vite.config.mts +10 -0
  48. biolib/_internal/templates/init_template/.biolib/config.yml +19 -0
  49. biolib/_internal/templates/init_template/Dockerfile +14 -0
  50. biolib/_internal/templates/init_template/requirements.txt +1 -0
  51. biolib/_internal/templates/init_template/run.py +12 -0
  52. biolib/_internal/templates/init_template/run.sh +4 -0
  53. biolib/_internal/templates/templates.py +25 -0
  54. biolib/_internal/tree_utils.py +106 -0
  55. biolib/_internal/utils/__init__.py +65 -0
  56. biolib/_internal/utils/auth.py +46 -0
  57. biolib/_internal/utils/job_url.py +33 -0
  58. biolib/_internal/utils/multinode.py +263 -0
  59. biolib/_runtime/runtime.py +157 -0
  60. biolib/_session/session.py +44 -0
  61. biolib/_shared/__init__.py +0 -0
  62. biolib/_shared/types/__init__.py +74 -0
  63. biolib/_shared/types/account.py +12 -0
  64. biolib/_shared/types/account_member.py +8 -0
  65. biolib/_shared/types/app.py +9 -0
  66. biolib/_shared/types/data_record.py +40 -0
  67. biolib/_shared/types/experiment.py +32 -0
  68. biolib/_shared/types/file_node.py +17 -0
  69. biolib/_shared/types/push.py +6 -0
  70. biolib/_shared/types/resource.py +37 -0
  71. biolib/_shared/types/resource_deploy_key.py +11 -0
  72. biolib/_shared/types/resource_permission.py +14 -0
  73. biolib/_shared/types/resource_version.py +19 -0
  74. biolib/_shared/types/result.py +14 -0
  75. biolib/_shared/types/typing.py +10 -0
  76. biolib/_shared/types/user.py +19 -0
  77. biolib/_shared/utils/__init__.py +7 -0
  78. biolib/_shared/utils/resource_uri.py +75 -0
  79. biolib/api/__init__.py +6 -0
  80. biolib/api/client.py +168 -0
  81. biolib/app/app.py +252 -49
  82. biolib/app/search_apps.py +45 -0
  83. biolib/biolib_api_client/api_client.py +126 -31
  84. biolib/biolib_api_client/app_types.py +24 -4
  85. biolib/biolib_api_client/auth.py +31 -8
  86. biolib/biolib_api_client/biolib_app_api.py +147 -52
  87. biolib/biolib_api_client/biolib_job_api.py +161 -141
  88. biolib/biolib_api_client/job_types.py +21 -5
  89. biolib/biolib_api_client/lfs_types.py +7 -23
  90. biolib/biolib_api_client/user_state.py +56 -0
  91. biolib/biolib_binary_format/__init__.py +1 -4
  92. biolib/biolib_binary_format/file_in_container.py +105 -0
  93. biolib/biolib_binary_format/module_input.py +24 -7
  94. biolib/biolib_binary_format/module_output_v2.py +149 -0
  95. biolib/biolib_binary_format/remote_endpoints.py +34 -0
  96. biolib/biolib_binary_format/remote_stream_seeker.py +59 -0
  97. biolib/biolib_binary_format/saved_job.py +3 -2
  98. biolib/biolib_binary_format/{attestation_document.py → stdout_and_stderr.py} +8 -8
  99. biolib/biolib_binary_format/system_status_update.py +3 -2
  100. biolib/biolib_binary_format/utils.py +175 -0
  101. biolib/biolib_docker_client/__init__.py +11 -2
  102. biolib/biolib_errors.py +36 -0
  103. biolib/biolib_logging.py +27 -10
  104. biolib/cli/__init__.py +38 -0
  105. biolib/cli/auth.py +46 -0
  106. biolib/cli/data_record.py +164 -0
  107. biolib/cli/index.py +32 -0
  108. biolib/cli/init.py +421 -0
  109. biolib/cli/lfs.py +101 -0
  110. biolib/cli/push.py +50 -0
  111. biolib/cli/run.py +63 -0
  112. biolib/cli/runtime.py +14 -0
  113. biolib/cli/sdk.py +16 -0
  114. biolib/cli/start.py +56 -0
  115. biolib/compute_node/cloud_utils/cloud_utils.py +110 -161
  116. biolib/compute_node/job_worker/cache_state.py +66 -88
  117. biolib/compute_node/job_worker/cache_types.py +1 -6
  118. biolib/compute_node/job_worker/docker_image_cache.py +112 -37
  119. biolib/compute_node/job_worker/executors/__init__.py +0 -3
  120. biolib/compute_node/job_worker/executors/docker_executor.py +532 -199
  121. biolib/compute_node/job_worker/executors/docker_types.py +9 -1
  122. biolib/compute_node/job_worker/executors/types.py +19 -9
  123. biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +30 -0
  124. biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +3 -5
  125. biolib/compute_node/job_worker/job_storage.py +108 -0
  126. biolib/compute_node/job_worker/job_worker.py +397 -212
  127. biolib/compute_node/job_worker/large_file_system.py +87 -38
  128. biolib/compute_node/job_worker/network_alloc.py +99 -0
  129. biolib/compute_node/job_worker/network_buffer.py +240 -0
  130. biolib/compute_node/job_worker/utilization_reporter_thread.py +197 -0
  131. biolib/compute_node/job_worker/utils.py +9 -24
  132. biolib/compute_node/remote_host_proxy.py +400 -98
  133. biolib/compute_node/utils.py +31 -9
  134. biolib/compute_node/webserver/compute_node_results_proxy.py +189 -0
  135. biolib/compute_node/webserver/proxy_utils.py +28 -0
  136. biolib/compute_node/webserver/webserver.py +130 -44
  137. biolib/compute_node/webserver/webserver_types.py +2 -6
  138. biolib/compute_node/webserver/webserver_utils.py +77 -12
  139. biolib/compute_node/webserver/worker_thread.py +183 -42
  140. biolib/experiments/__init__.py +0 -0
  141. biolib/experiments/experiment.py +356 -0
  142. biolib/jobs/__init__.py +1 -0
  143. biolib/jobs/job.py +741 -0
  144. biolib/jobs/job_result.py +185 -0
  145. biolib/jobs/types.py +50 -0
  146. biolib/py.typed +0 -0
  147. biolib/runtime/__init__.py +14 -0
  148. biolib/sdk/__init__.py +91 -0
  149. biolib/tables.py +34 -0
  150. biolib/typing_utils.py +2 -7
  151. biolib/user/__init__.py +1 -0
  152. biolib/user/sign_in.py +54 -0
  153. biolib/utils/__init__.py +162 -0
  154. biolib/utils/cache_state.py +94 -0
  155. biolib/utils/multipart_uploader.py +194 -0
  156. biolib/utils/seq_util.py +150 -0
  157. biolib/utils/zip/remote_zip.py +640 -0
  158. pybiolib-1.2.1890.dist-info/METADATA +41 -0
  159. pybiolib-1.2.1890.dist-info/RECORD +177 -0
  160. {pybiolib-0.2.951.dist-info → pybiolib-1.2.1890.dist-info}/WHEEL +1 -1
  161. pybiolib-1.2.1890.dist-info/entry_points.txt +2 -0
  162. README.md +0 -17
  163. biolib/app/app_result.py +0 -68
  164. biolib/app/utils.py +0 -62
  165. biolib/biolib-js/0-biolib.worker.js +0 -1
  166. biolib/biolib-js/1-biolib.worker.js +0 -1
  167. biolib/biolib-js/2-biolib.worker.js +0 -1
  168. biolib/biolib-js/3-biolib.worker.js +0 -1
  169. biolib/biolib-js/4-biolib.worker.js +0 -1
  170. biolib/biolib-js/5-biolib.worker.js +0 -1
  171. biolib/biolib-js/6-biolib.worker.js +0 -1
  172. biolib/biolib-js/index.html +0 -10
  173. biolib/biolib-js/main-biolib.js +0 -1
  174. biolib/biolib_api_client/biolib_account_api.py +0 -21
  175. biolib/biolib_api_client/biolib_large_file_system_api.py +0 -108
  176. biolib/biolib_binary_format/aes_encrypted_package.py +0 -42
  177. biolib/biolib_binary_format/module_output.py +0 -58
  178. biolib/biolib_binary_format/rsa_encrypted_aes_package.py +0 -57
  179. biolib/biolib_push.py +0 -114
  180. biolib/cli.py +0 -203
  181. biolib/cli_utils.py +0 -273
  182. biolib/compute_node/cloud_utils/enclave_parent_types.py +0 -7
  183. biolib/compute_node/enclave/__init__.py +0 -2
  184. biolib/compute_node/enclave/enclave_remote_hosts.py +0 -53
  185. biolib/compute_node/enclave/nitro_secure_module_utils.py +0 -64
  186. biolib/compute_node/job_worker/executors/base_executor.py +0 -18
  187. biolib/compute_node/job_worker/executors/pyppeteer_executor.py +0 -173
  188. biolib/compute_node/job_worker/executors/remote/__init__.py +0 -1
  189. biolib/compute_node/job_worker/executors/remote/nitro_enclave_utils.py +0 -81
  190. biolib/compute_node/job_worker/executors/remote/remote_executor.py +0 -51
  191. biolib/lfs.py +0 -196
  192. biolib/pyppeteer/.circleci/config.yml +0 -100
  193. biolib/pyppeteer/.coveragerc +0 -3
  194. biolib/pyppeteer/.gitignore +0 -89
  195. biolib/pyppeteer/.pre-commit-config.yaml +0 -28
  196. biolib/pyppeteer/CHANGES.md +0 -253
  197. biolib/pyppeteer/CONTRIBUTING.md +0 -26
  198. biolib/pyppeteer/LICENSE +0 -12
  199. biolib/pyppeteer/README.md +0 -137
  200. biolib/pyppeteer/docs/Makefile +0 -177
  201. biolib/pyppeteer/docs/_static/custom.css +0 -28
  202. biolib/pyppeteer/docs/_templates/layout.html +0 -10
  203. biolib/pyppeteer/docs/changes.md +0 -1
  204. biolib/pyppeteer/docs/conf.py +0 -299
  205. biolib/pyppeteer/docs/index.md +0 -21
  206. biolib/pyppeteer/docs/make.bat +0 -242
  207. biolib/pyppeteer/docs/reference.md +0 -211
  208. biolib/pyppeteer/docs/server.py +0 -60
  209. biolib/pyppeteer/poetry.lock +0 -1699
  210. biolib/pyppeteer/pyppeteer/__init__.py +0 -135
  211. biolib/pyppeteer/pyppeteer/accessibility.py +0 -286
  212. biolib/pyppeteer/pyppeteer/browser.py +0 -401
  213. biolib/pyppeteer/pyppeteer/browser_fetcher.py +0 -194
  214. biolib/pyppeteer/pyppeteer/command.py +0 -22
  215. biolib/pyppeteer/pyppeteer/connection/__init__.py +0 -242
  216. biolib/pyppeteer/pyppeteer/connection/cdpsession.py +0 -101
  217. biolib/pyppeteer/pyppeteer/coverage.py +0 -346
  218. biolib/pyppeteer/pyppeteer/device_descriptors.py +0 -787
  219. biolib/pyppeteer/pyppeteer/dialog.py +0 -79
  220. biolib/pyppeteer/pyppeteer/domworld.py +0 -597
  221. biolib/pyppeteer/pyppeteer/emulation_manager.py +0 -53
  222. biolib/pyppeteer/pyppeteer/errors.py +0 -48
  223. biolib/pyppeteer/pyppeteer/events.py +0 -63
  224. biolib/pyppeteer/pyppeteer/execution_context.py +0 -156
  225. biolib/pyppeteer/pyppeteer/frame/__init__.py +0 -299
  226. biolib/pyppeteer/pyppeteer/frame/frame_manager.py +0 -306
  227. biolib/pyppeteer/pyppeteer/helpers.py +0 -245
  228. biolib/pyppeteer/pyppeteer/input.py +0 -371
  229. biolib/pyppeteer/pyppeteer/jshandle.py +0 -598
  230. biolib/pyppeteer/pyppeteer/launcher.py +0 -683
  231. biolib/pyppeteer/pyppeteer/lifecycle_watcher.py +0 -169
  232. biolib/pyppeteer/pyppeteer/models/__init__.py +0 -103
  233. biolib/pyppeteer/pyppeteer/models/_protocol.py +0 -12460
  234. biolib/pyppeteer/pyppeteer/multimap.py +0 -82
  235. biolib/pyppeteer/pyppeteer/network_manager.py +0 -678
  236. biolib/pyppeteer/pyppeteer/options.py +0 -8
  237. biolib/pyppeteer/pyppeteer/page.py +0 -1728
  238. biolib/pyppeteer/pyppeteer/pipe_transport.py +0 -59
  239. biolib/pyppeteer/pyppeteer/target.py +0 -147
  240. biolib/pyppeteer/pyppeteer/task_queue.py +0 -24
  241. biolib/pyppeteer/pyppeteer/timeout_settings.py +0 -36
  242. biolib/pyppeteer/pyppeteer/tracing.py +0 -93
  243. biolib/pyppeteer/pyppeteer/us_keyboard_layout.py +0 -305
  244. biolib/pyppeteer/pyppeteer/util.py +0 -18
  245. biolib/pyppeteer/pyppeteer/websocket_transport.py +0 -47
  246. biolib/pyppeteer/pyppeteer/worker.py +0 -101
  247. biolib/pyppeteer/pyproject.toml +0 -97
  248. biolib/pyppeteer/spell.txt +0 -137
  249. biolib/pyppeteer/tox.ini +0 -72
  250. biolib/pyppeteer/utils/generate_protocol_types.py +0 -603
  251. biolib/start_cli.py +0 -7
  252. biolib/utils.py +0 -47
  253. biolib/validators/validate_app_version.py +0 -183
  254. biolib/validators/validate_argument.py +0 -134
  255. biolib/validators/validate_module.py +0 -323
  256. biolib/validators/validate_zip_file.py +0 -40
  257. biolib/validators/validator_utils.py +0 -103
  258. pybiolib-0.2.951.dist-info/LICENSE +0 -21
  259. pybiolib-0.2.951.dist-info/METADATA +0 -61
  260. pybiolib-0.2.951.dist-info/RECORD +0 -153
  261. pybiolib-0.2.951.dist-info/entry_points.txt +0 -3
  262. /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,12 @@
1
+ from .typing import TypedDict
2
+
3
+
4
+ class AccountDict(TypedDict):
5
+ uuid: str
6
+ handle: str
7
+ display_name: str
8
+ description: str
9
+
10
+
11
+ class AccountDetailedDict(AccountDict):
12
+ bio: str
@@ -0,0 +1,8 @@
1
+ from .typing import Literal, TypedDict
2
+ from .user import UserDict
3
+
4
+
5
+ class AccountMemberDict(TypedDict):
6
+ user: UserDict
7
+ role: Literal['member', 'admin']
8
+ added_at: str
@@ -0,0 +1,9 @@
1
+ from .typing import TypedDict
2
+
3
+
4
+ class AppSlimDict(TypedDict):
5
+ pass
6
+
7
+
8
+ class AppDetailedDict(AppSlimDict):
9
+ pass
@@ -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