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
biolib/cli/init.py ADDED
@@ -0,0 +1,421 @@
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+ import sys
5
+
6
+ import click
7
+
8
+ from biolib import (
9
+ biolib_errors,
10
+ utils, # Import like this to let BASE_URL_IS_PUBLIC_BIOLIB be set correctly
11
+ )
12
+ from biolib._internal.add_copilot_prompts import add_copilot_prompts
13
+ from biolib._internal.add_gui_files import add_gui_files
14
+ from biolib._internal.http_client import HttpClient, HttpError
15
+ from biolib._internal.string_utils import normalize_for_docker_tag
16
+ from biolib._internal.templates import templates
17
+ from biolib._internal.utils import get_pip_command
18
+ from biolib.api import client as api_client
19
+ from biolib.biolib_api_client.api_client import BiolibApiClient
20
+ from biolib.biolib_api_client.biolib_app_api import BiolibAppApi
21
+ from biolib.biolib_logging import logger_no_user_data
22
+ from biolib.typing_utils import Dict, List, Optional, Set
23
+ from biolib.user.sign_in import sign_in
24
+ from biolib.utils import BIOLIB_PACKAGE_VERSION
25
+
26
+
27
+ def _get_latest_pypi_version() -> Optional[str]:
28
+ try:
29
+ response = HttpClient.request(
30
+ url='https://pypi.org/pypi/pybiolib/json',
31
+ timeout_in_seconds=5,
32
+ retries=1,
33
+ )
34
+ data = response.json()
35
+ version = data.get('info', {}).get('version')
36
+ if isinstance(version, str):
37
+ return version
38
+ return None
39
+ except Exception as error:
40
+ logger_no_user_data.debug(f'Failed to fetch latest version from PyPI: {error}')
41
+ return None
42
+
43
+
44
+ def _is_current_version_outdated(current: str, latest: str) -> bool:
45
+ try:
46
+ current_parts = [int(x) for x in current.split('.')]
47
+ latest_parts = [int(x) for x in latest.split('.')]
48
+ return current_parts < latest_parts
49
+ except (ValueError, AttributeError):
50
+ return False
51
+
52
+
53
+ def _check_version_and_prompt_upgrade() -> bool:
54
+ latest_version = _get_latest_pypi_version()
55
+ if latest_version and _is_current_version_outdated(BIOLIB_PACKAGE_VERSION, latest_version):
56
+ print(f'A newer version of pybiolib is available: {latest_version} (current: {BIOLIB_PACKAGE_VERSION})')
57
+ pip_command = get_pip_command()
58
+ print(f'To upgrade, run: {pip_command} install --upgrade pybiolib')
59
+ print()
60
+ continue_input = input('Do you want to continue with the current version? [y/N]: ')
61
+ if continue_input.lower() not in ['y', 'yes']:
62
+ print('Please upgrade pybiolib and run `biolib init` again.')
63
+ return False
64
+ return True
65
+
66
+
67
+ def _prompt_for_app_uri(prompt_text: str) -> Optional[str]:
68
+ app_uri = input(prompt_text)
69
+
70
+ if app_uri and not app_uri.startswith('@'):
71
+ try:
72
+ response = api_client.get('system/enterprise/config/', authenticate=False)
73
+ config = response.json()
74
+ prefix = config.get('resource_hostname_prefix')
75
+ if prefix:
76
+ app_uri = f'@{prefix}/{app_uri}'
77
+ print(f'Detected enterprise deployment, using URI: {app_uri}')
78
+ except HttpError as error:
79
+ if error.code not in [404, 501]:
80
+ print(f'Warning: Could not detect enterprise configuration: {error}')
81
+ except Exception as error:
82
+ print(f'Warning: Could not detect enterprise configuration: {error}')
83
+
84
+ return app_uri if app_uri else None
85
+
86
+
87
+ def _validate_or_create_app(app_uri: str) -> bool:
88
+ try:
89
+ if BiolibApiClient.is_reauthentication_needed():
90
+ sign_in_input = input('You need to sign in to validate/create apps. Would you like to sign in? [y/N]: ')
91
+ if sign_in_input.lower() in ['y', 'yes']:
92
+ sign_in()
93
+ else:
94
+ print('Skipping app validation and creation. You can set the URI in .biolib/config.yml later.')
95
+ return False
96
+
97
+ BiolibAppApi.get_by_uri(app_uri)
98
+ print(f'App {app_uri} already exists.')
99
+ except biolib_errors.NotFound:
100
+ create_app_input = input(f'App {app_uri} does not exist. Would you like to create it? [y/N]: ')
101
+ if create_app_input.lower() in ['y', 'yes']:
102
+ try:
103
+ BiolibAppApi.create_app(app_uri)
104
+ print(f'Successfully created app {app_uri}')
105
+ except Exception as error:
106
+ print(f'Failed to create app {app_uri}: {str(error)}')
107
+ print('You can create the app manually later or set the URI in .biolib/config.yml')
108
+ else:
109
+ print('App creation skipped. You can create the app manually later or set the URI in .biolib/config.yml')
110
+ except Exception as error:
111
+ print(f'Failed to validate app {app_uri}: {str(error)}')
112
+ print('Continuing with initialization...')
113
+
114
+ return True
115
+
116
+
117
+ def _find_conflicting_files(template_dir: str, cwd: str) -> List[str]:
118
+ conflicting_files: List[str] = []
119
+ for root, dirs, filenames in os.walk(template_dir):
120
+ dirs[:] = [d for d in dirs if '__pycache__' not in d]
121
+ relative_dir = os.path.relpath(root, template_dir)
122
+ destination_dir = cwd if relative_dir == '.' else os.path.join(cwd, relative_dir)
123
+
124
+ for filename in filenames:
125
+ source_file = os.path.join(root, filename)
126
+ destination_file = os.path.join(destination_dir, filename)
127
+ if os.path.exists(destination_file):
128
+ with open(source_file, 'rb') as fsrc, open(destination_file, 'rb') as fdest:
129
+ if fsrc.read() != fdest.read():
130
+ conflicting_files.append(os.path.relpath(destination_file, cwd))
131
+ return conflicting_files
132
+
133
+
134
+ def _prompt_for_overwrites(conflicting_files: List[str]) -> Set[str]:
135
+ files_to_overwrite: Set[str] = set()
136
+ if conflicting_files:
137
+ print('The following files already exist and would be overwritten:')
138
+ for conflicting_file in conflicting_files:
139
+ print(f' {conflicting_file}')
140
+ print()
141
+
142
+ for conflicting_file in conflicting_files:
143
+ choice = input(f'Overwrite {conflicting_file}? [y/N]: ').lower().strip()
144
+ if choice in ['y', 'yes']:
145
+ files_to_overwrite.add(conflicting_file)
146
+ return files_to_overwrite
147
+
148
+
149
+ def _copy_template_files(
150
+ template_dir: str,
151
+ cwd: str,
152
+ files_to_overwrite: Set[str],
153
+ replacements: Dict[str, str],
154
+ ) -> None:
155
+ for root, dirs, filenames in os.walk(template_dir):
156
+ dirs[:] = [d for d in dirs if '__pycache__' not in d]
157
+ relative_dir = os.path.relpath(root, template_dir)
158
+ destination_dir = os.path.join(cwd, relative_dir)
159
+
160
+ os.makedirs(destination_dir, exist_ok=True)
161
+
162
+ for filename in filenames:
163
+ if utils.BASE_URL_IS_PUBLIC_BIOLIB and filename == 'biolib.yml':
164
+ continue
165
+
166
+ source_file = os.path.join(root, filename)
167
+ destination_file = os.path.join(destination_dir, filename)
168
+ relative_file_path = os.path.relpath(destination_file, cwd)
169
+
170
+ if not os.path.exists(destination_file) or relative_file_path in files_to_overwrite:
171
+ try:
172
+ with open(source_file) as f:
173
+ content = f.read()
174
+
175
+ new_content = content
176
+ for old_value, new_value in replacements.items():
177
+ new_content = new_content.replace(old_value, new_value)
178
+
179
+ with open(destination_file, 'w') as f:
180
+ f.write(new_content)
181
+ except UnicodeDecodeError:
182
+ shutil.copy2(source_file, destination_file)
183
+
184
+
185
+ def _create_readme_if_needed(cwd: str, app_name: Optional[str]) -> None:
186
+ readme_path = os.path.join(cwd, 'README.md')
187
+ if not os.path.exists(readme_path) and app_name:
188
+ with open(readme_path, 'w') as readme_file:
189
+ readme_file.write(f'# {app_name}\n')
190
+
191
+
192
+ def _copy_gitignore(cwd: str) -> None:
193
+ gitignore_template_dir = templates.gitignore_template()
194
+ source_file = os.path.join(gitignore_template_dir, '.gitignore')
195
+ destination_file = os.path.join(cwd, '.gitignore')
196
+ if not os.path.exists(destination_file):
197
+ shutil.copy2(source_file, destination_file)
198
+
199
+
200
+ def _copy_github_workflow(cwd: str, replacements: Dict[str, str]) -> None:
201
+ workflow_template_dir = templates.github_workflow_template()
202
+ source_file = os.path.join(workflow_template_dir, '.github', 'workflows', 'biolib.yml')
203
+ destination_dir = os.path.join(cwd, '.github', 'workflows')
204
+ destination_file = os.path.join(destination_dir, 'biolib.yml')
205
+
206
+ if not os.path.exists(destination_file):
207
+ os.makedirs(destination_dir, exist_ok=True)
208
+ with open(source_file) as f:
209
+ content = f.read()
210
+ for old_value, new_value in replacements.items():
211
+ content = content.replace(old_value, new_value)
212
+ with open(destination_file, 'w') as f:
213
+ f.write(content)
214
+
215
+
216
+ def _prompt_and_run_yarn_install() -> None:
217
+ yarn_install_input = input('Do you want to run yarn install? [Y/n]: ')
218
+ if yarn_install_input.lower() not in ['n', 'no']:
219
+ print('Running yarn install...')
220
+ try:
221
+ subprocess.run(['yarn', 'install'], check=True)
222
+ print('yarn install completed successfully.')
223
+ except FileNotFoundError:
224
+ print(
225
+ 'Error: yarn is not installed or not found in PATH. Please install yarn and run yarn install manually.'
226
+ )
227
+ except subprocess.CalledProcessError as error:
228
+ print(f'yarn install failed with exit code {error.returncode}. Please run yarn install manually.')
229
+ except Exception as error:
230
+ print(f'yarn install failed: {error}. Please run yarn install manually.')
231
+
232
+
233
+ def _create_dashboard_dockerfile(cwd: str) -> None:
234
+ gui_template_dir = templates.gui_template()
235
+ gui_dockerfile = os.path.join(gui_template_dir, 'Dockerfile')
236
+ destination_file = os.path.join(cwd, 'Dockerfile')
237
+
238
+ if not os.path.exists(destination_file):
239
+ with open(gui_dockerfile) as f:
240
+ lines = f.readlines()
241
+
242
+ gui_builder_lines = []
243
+ for line in lines:
244
+ gui_builder_lines.append(line)
245
+ if line.strip() == 'RUN yarn build':
246
+ break
247
+
248
+ dist_export_stage = '\nFROM scratch AS dist_export\nCOPY --from=gui_builder /home/biolib/gui/dist /dist\n'
249
+ with open(destination_file, 'w') as f:
250
+ f.writelines(gui_builder_lines)
251
+ f.write(dist_export_stage)
252
+
253
+
254
+ def _copy_gui_files_for_dashboard(cwd: str, app_name: str) -> None:
255
+ gui_template_dir = templates.gui_template()
256
+ gui_root_files = ['package.json', 'vite.config.mts', '.yarnrc.yml']
257
+
258
+ for root, _, filenames in os.walk(gui_template_dir):
259
+ relative_dir = os.path.relpath(root, gui_template_dir)
260
+
261
+ for filename in filenames:
262
+ if filename == 'Dockerfile':
263
+ continue
264
+
265
+ if filename in gui_root_files:
266
+ destination_dir = cwd
267
+ else:
268
+ if relative_dir == '.':
269
+ destination_dir = os.path.join(cwd, 'gui')
270
+ else:
271
+ destination_dir = os.path.join(cwd, 'gui', relative_dir)
272
+
273
+ source_file = os.path.join(root, filename)
274
+ destination_file = os.path.join(destination_dir, filename)
275
+
276
+ if not os.path.exists(destination_file):
277
+ os.makedirs(destination_dir, exist_ok=True)
278
+ try:
279
+ with open(source_file) as f:
280
+ content = f.read()
281
+ new_content = content.replace('BIOLIB_REPLACE_APP_NAME', app_name)
282
+ with open(destination_file, 'w') as f:
283
+ f.write(new_content)
284
+ except UnicodeDecodeError:
285
+ shutil.copy2(source_file, destination_file)
286
+
287
+
288
+ def _init_dashboard() -> None:
289
+ if not _check_version_and_prompt_upgrade():
290
+ return
291
+
292
+ cwd = os.getcwd()
293
+ app_uri = _prompt_for_app_uri('What URI do you want to create the dashboard under? (leave blank to skip): ')
294
+ app_name = app_uri.split('/')[-1] if app_uri else None
295
+
296
+ if app_uri:
297
+ if not _validate_or_create_app(app_uri):
298
+ return
299
+ else:
300
+ print(
301
+ 'Remember to set the app URI in the .biolib/config.yml file later, '
302
+ 'and update the .github/workflows/biolib.yml file.'
303
+ )
304
+
305
+ dashboard_template_dir = templates.dashboard_template()
306
+
307
+ try:
308
+ conflicting_files = _find_conflicting_files(dashboard_template_dir, cwd)
309
+ files_to_overwrite = _prompt_for_overwrites(conflicting_files)
310
+
311
+ replace_app_uri = app_uri if app_uri else 'PUT_APP_URI_HERE'
312
+ replace_app_name = app_name if app_name else 'biolib-dashboard'
313
+
314
+ replacements = {
315
+ 'BIOLIB_REPLACE_APP_URI': replace_app_uri,
316
+ 'BIOLIB_REPLACE_APP_NAME': replace_app_name,
317
+ }
318
+
319
+ _copy_template_files(dashboard_template_dir, cwd, files_to_overwrite, replacements)
320
+ _create_readme_if_needed(cwd, app_name)
321
+ _copy_gitignore(cwd)
322
+ _copy_github_workflow(
323
+ cwd,
324
+ {
325
+ 'BIOLIB_REPLACE_APP_URI': replace_app_uri,
326
+ 'BIOLIB_REPLACE_BUILD_COMMAND': 'docker build --target dist_export -o type=local,dest=. .',
327
+ },
328
+ )
329
+ _create_dashboard_dockerfile(cwd)
330
+ _copy_gui_files_for_dashboard(cwd, replace_app_name)
331
+ _prompt_and_run_yarn_install()
332
+
333
+ print('Dashboard template initialized successfully.')
334
+
335
+ except KeyboardInterrupt:
336
+ print('\nInit command cancelled.', file=sys.stderr)
337
+ exit(1)
338
+
339
+
340
+ @click.command(help='Initialize a BioLib project', hidden=True)
341
+ @click.argument('template_type', required=False, default=None)
342
+ def init(template_type: Optional[str]) -> None:
343
+ if template_type == 'dashboard':
344
+ _init_dashboard()
345
+ return
346
+
347
+ if template_type is not None:
348
+ print(f"Unknown template type: '{template_type}'. Available templates: dashboard")
349
+ print('Run `biolib init` without arguments for the default Python application template.')
350
+ return
351
+
352
+ if not _check_version_and_prompt_upgrade():
353
+ return
354
+
355
+ cwd = os.getcwd()
356
+ app_uri = _prompt_for_app_uri('What URI do you want to create the application under? (leave blank to skip): ')
357
+ app_name = app_uri.split('/')[-1] if app_uri else None
358
+ docker_tag = normalize_for_docker_tag(app_name) if app_name else None
359
+
360
+ if app_uri:
361
+ if not _validate_or_create_app(app_uri):
362
+ return
363
+ else:
364
+ print(
365
+ 'Remember to set the app URI in the .biolib/config.yml file later, '
366
+ 'and docker image name in the .biolib/config.yml and .github/workflows/biolib.yml files.'
367
+ )
368
+
369
+ advanced_setup_input = input('Do you want to set up advanced features like Copilot and GUI? [y/N]: ')
370
+ advanced_setup = advanced_setup_input.lower() == 'y'
371
+ include_copilot = False
372
+ include_gui = False
373
+ if advanced_setup:
374
+ copilot_enabled_input = input('Do you want to include Copilot instructions and prompts? [y/N]: ')
375
+ include_copilot = copilot_enabled_input.lower() == 'y'
376
+ include_gui_input = input('Do you want to include GUI setup? [y/N]: ')
377
+ include_gui = include_gui_input.lower() == 'y'
378
+
379
+ init_template_dir = templates.init_template()
380
+
381
+ try:
382
+ conflicting_files = _find_conflicting_files(init_template_dir, cwd)
383
+ files_to_overwrite = _prompt_for_overwrites(conflicting_files)
384
+
385
+ replace_app_uri = app_uri if app_uri else 'PUT_APP_URI_HERE'
386
+ replace_app_name = app_name if app_name else 'biolib-app'
387
+
388
+ gui_config = "main_output_file: '/result.html'\n" if include_gui else ''
389
+ gui_mv_command = 'mv result.html output/result.html\n' if include_gui else ''
390
+
391
+ replacements = {
392
+ 'BIOLIB_REPLACE_PYBIOLIB_VERSION': BIOLIB_PACKAGE_VERSION,
393
+ 'BIOLIB_REPLACE_APP_URI': replace_app_uri,
394
+ 'BIOLIB_REPLACE_DOCKER_TAG': docker_tag if docker_tag else 'PUT_DOCKER_TAG_HERE',
395
+ 'BIOLIB_REPLACE_APP_NAME': replace_app_name,
396
+ 'BIOLIB_REPLACE_GUI_CONFIG\n': gui_config,
397
+ 'BIOLIB_REPLACE_GUI_MV_COMMAND\n': gui_mv_command,
398
+ }
399
+
400
+ _copy_template_files(init_template_dir, cwd, files_to_overwrite, replacements)
401
+ _create_readme_if_needed(cwd, app_name)
402
+ _copy_gitignore(cwd)
403
+ build_tag = docker_tag if docker_tag else 'PUT_DOCKER_TAG_HERE'
404
+ _copy_github_workflow(
405
+ cwd,
406
+ {
407
+ 'BIOLIB_REPLACE_APP_URI': replace_app_uri,
408
+ 'BIOLIB_REPLACE_BUILD_COMMAND': f'docker build -t {build_tag}:latest .',
409
+ },
410
+ )
411
+
412
+ if include_copilot:
413
+ add_copilot_prompts(force=False, silent=True)
414
+
415
+ if include_gui:
416
+ add_gui_files(force=False, silent=True)
417
+ _prompt_and_run_yarn_install()
418
+
419
+ except KeyboardInterrupt:
420
+ print('\nInit command cancelled.', file=sys.stderr)
421
+ exit(1)
biolib/cli/lfs.py ADDED
@@ -0,0 +1,101 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ import sys
5
+ from typing import Dict, List
6
+
7
+ import click
8
+
9
+ from biolib import biolib_errors
10
+ from biolib._data_record.data_record import DataRecord
11
+ from biolib._internal.lfs import prune_lfs_cache
12
+ from biolib.biolib_logging import logger, logger_no_user_data
13
+ from biolib.typing_utils import Optional
14
+
15
+
16
+ @click.group(help='Manage Large File Systems', hidden=True)
17
+ def lfs() -> None:
18
+ pass
19
+
20
+
21
+ @lfs.command(help='Create a Large File System')
22
+ @click.argument('uri', required=True)
23
+ def create(uri: str) -> None:
24
+ logger.warning('This is command deprecated, please use "biolib data-record create" instead.')
25
+ logger.configure(default_log_level=logging.INFO)
26
+ logger_no_user_data.configure(default_log_level=logging.INFO)
27
+ DataRecord.create(destination=uri)
28
+
29
+
30
+ @lfs.command(help='Push a new version of a Large File System')
31
+ @click.argument('uri', required=True)
32
+ @click.option('--path', required=True, type=click.Path(exists=True))
33
+ @click.option('--chunk-size', default=None, required=False, type=click.INT, help='The size of each chunk (In MB)')
34
+ def push(uri: str, path: str, chunk_size: Optional[int]) -> None:
35
+ logger.warning('This is command deprecated, please use "biolib data-record update" instead.')
36
+ logger.configure(default_log_level=logging.INFO)
37
+ logger_no_user_data.configure(default_log_level=logging.INFO)
38
+ try:
39
+ DataRecord.get_by_uri(uri=uri).update(data_path=path, chunk_size_in_mb=chunk_size)
40
+ except biolib_errors.BioLibError as error:
41
+ print(f'An error occurred:\n{error.message}', file=sys.stderr)
42
+ exit(1)
43
+
44
+
45
+ @lfs.command(help='Download a file from a Large File System')
46
+ @click.argument('uri', required=True)
47
+ @click.option('--file-path', required=True, type=str)
48
+ def download_file(uri: str, file_path: str) -> None:
49
+ logger.warning('This is command deprecated, please use "biolib data-record download" instead.')
50
+ logger.configure(default_log_level=logging.INFO)
51
+ logger_no_user_data.configure(default_log_level=logging.INFO)
52
+ try:
53
+ record = DataRecord.get_by_uri(uri=uri)
54
+ try:
55
+ file_obj = [file_obj for file_obj in record.list_files() if file_obj.path == file_path][0]
56
+ except IndexError:
57
+ raise Exception('File not found in data record') from None
58
+
59
+ assert not os.path.exists(file_obj.name), 'File already exists in current directory'
60
+ with open(file_obj.name, 'wb') as file_handle:
61
+ file_handle.write(file_obj.get_data())
62
+
63
+ except biolib_errors.BioLibError as error:
64
+ print(f'An error occurred:\n{error.message}', file=sys.stderr)
65
+ exit(1)
66
+
67
+
68
+ @lfs.command(help='Describe a Large File System')
69
+ @click.argument('uri', required=True)
70
+ @click.option('--json', 'output_as_json', is_flag=True, default=False, required=False, help='Format output as JSON')
71
+ def describe(uri: str, output_as_json: bool) -> None:
72
+ logger.warning('This is command deprecated, please use "biolib data-record describe" instead.')
73
+ data_record = DataRecord.get_by_uri(uri)
74
+ files_info: List[Dict] = []
75
+ total_size_in_bytes = 0
76
+ for file in data_record.list_files():
77
+ files_info.append({'path': file.path, 'size_bytes': file.length})
78
+ total_size_in_bytes += file.length
79
+
80
+ if output_as_json:
81
+ print(
82
+ json.dumps(
83
+ obj={'uri': data_record.uri, 'size_bytes': total_size_in_bytes, 'files': files_info},
84
+ indent=4,
85
+ )
86
+ )
87
+ else:
88
+ print(f'Large File System {data_record.uri}\ntotal {total_size_in_bytes} bytes\n')
89
+ print('size bytes path')
90
+ for file_info in files_info:
91
+ size_string = str(file_info['size_bytes'])
92
+ leading_space_string = ' ' * (10 - len(size_string))
93
+ print(f"{leading_space_string}{size_string} {file_info['path']}")
94
+
95
+
96
+ @lfs.command(help='Prune LFS cache', hidden=True)
97
+ @click.option('--dry-run', type=click.BOOL, default=True, required=False)
98
+ def prune_cache(dry_run: bool) -> None:
99
+ logger.configure(default_log_level=logging.INFO)
100
+ logger_no_user_data.configure(default_log_level=logging.INFO)
101
+ prune_lfs_cache(dry_run)
biolib/cli/push.py ADDED
@@ -0,0 +1,50 @@
1
+ import logging
2
+ import sys
3
+ from typing import Optional
4
+
5
+ import click
6
+
7
+ from biolib._internal.errors import AuthenticationError
8
+ from biolib._internal.push_application import push_application
9
+ from biolib.biolib_logging import logger, logger_no_user_data
10
+
11
+
12
+ @click.command(help='Push an application to BioLib')
13
+ @click.argument('uri')
14
+ @click.option('--path', default='.', required=False)
15
+ @click.option('--copy-images-from-version', required=False)
16
+ @click.option('--dev', is_flag=True, default=False, required=False)
17
+ @click.option('--pre-release', is_flag=True, default=False, required=False)
18
+ @click.option(
19
+ '--dry-run',
20
+ is_flag=True,
21
+ default=False,
22
+ required=False,
23
+ help='Perform validation without pushing',
24
+ )
25
+ def push(uri, path: str, copy_images_from_version: Optional[str], dev: bool, pre_release: bool, dry_run: bool) -> None:
26
+ logger.configure(default_log_level=logging.INFO)
27
+ logger_no_user_data.configure(default_log_level=logging.INFO)
28
+ set_as_active = True
29
+ set_as_published = True
30
+ if dev and pre_release:
31
+ print('Error: you cannot set both --dev and --pre-release, please select one.')
32
+ exit(1)
33
+ elif dev:
34
+ set_as_active = False
35
+ set_as_published = False
36
+ elif pre_release:
37
+ set_as_active = False
38
+ set_as_published = True
39
+ try:
40
+ push_application(
41
+ app_path=path,
42
+ app_uri=uri,
43
+ app_version_to_copy_images_from=copy_images_from_version,
44
+ set_as_active=set_as_active,
45
+ set_as_published=set_as_published,
46
+ dry_run=dry_run,
47
+ )
48
+ except AuthenticationError as error:
49
+ print(error.message, file=sys.stderr)
50
+ exit(1)
biolib/cli/run.py ADDED
@@ -0,0 +1,63 @@
1
+ import sys
2
+
3
+ import click
4
+
5
+ from biolib import biolib_errors, utils
6
+ from biolib.app import BioLibApp
7
+ from biolib.experiments.experiment import Experiment
8
+ from biolib.typing_utils import Optional, Tuple
9
+
10
+
11
+ @click.command(
12
+ context_settings=dict(ignore_unknown_options=True, allow_interspersed_args=False),
13
+ help='Run an application on BioLib.',
14
+ )
15
+ @click.option('--experiment', type=str, required=False, help='Experiment name or URI to add the run to.')
16
+ @click.option('--local', is_flag=True, required=False, hidden=True)
17
+ @click.option('--non-blocking', is_flag=True, required=False, help='Run the application non blocking.')
18
+ @click.argument('uri', required=True)
19
+ @click.argument('args', nargs=-1, type=click.UNPROCESSED)
20
+ def run(experiment: Optional[str], local: bool, non_blocking: bool, uri: str, args: Tuple[str]) -> None:
21
+ if local:
22
+ print('Error: Running applications locally with --local is no longer supported.', file=sys.stderr)
23
+ sys.exit(1)
24
+
25
+ if experiment:
26
+ with Experiment(uri=experiment):
27
+ _run(non_blocking=non_blocking, uri=uri, args=args)
28
+ else:
29
+ _run(non_blocking=non_blocking, uri=uri, args=args)
30
+
31
+
32
+ def _run(non_blocking: bool, uri: str, args: Tuple[str]) -> None:
33
+ try:
34
+ app = BioLibApp(uri=uri)
35
+ except biolib_errors.BioLibError as error:
36
+ print(f'An error occurred:\n {error.message}', file=sys.stderr)
37
+ exit(1)
38
+
39
+ def _get_stdin():
40
+ stdin = None
41
+ if not sys.stdin.isatty() and not utils.IS_DEV:
42
+ stdin = sys.stdin.read()
43
+ return stdin
44
+
45
+ blocking = not non_blocking
46
+ job = app.cli(
47
+ args=list(args),
48
+ stdin=_get_stdin(),
49
+ files=None,
50
+ blocking=blocking,
51
+ )
52
+
53
+ if blocking:
54
+ job.save_files('biolib_results', overwrite=True)
55
+
56
+ # Write stdout and stderr if it has not been streamed (Markdown is not streamed)
57
+ if app.version.get('stdout_render_type') == 'markdown' or not sys.stdout.isatty():
58
+ sys.stdout.buffer.write(job.get_stdout())
59
+ sys.stderr.buffer.write(job.get_stderr())
60
+
61
+ exit(job.get_exit_code())
62
+ else:
63
+ print('{"job_id": "' + str(job.id) + '"}')
biolib/cli/runtime.py ADDED
@@ -0,0 +1,14 @@
1
+ import click
2
+
3
+ import biolib.runtime
4
+
5
+
6
+ @click.group(help='Commands available within a BioLib runtime')
7
+ def runtime() -> None:
8
+ pass
9
+
10
+
11
+ @runtime.command(help='Set the name prefix of the main result')
12
+ @click.argument('result-prefix', required=True)
13
+ def set_main_result_prefix(result_prefix: str) -> None:
14
+ biolib.runtime.set_main_result_prefix(result_prefix)
biolib/cli/sdk.py ADDED
@@ -0,0 +1,16 @@
1
+ import click
2
+
3
+ from biolib._internal.add_copilot_prompts import add_copilot_prompts
4
+
5
+
6
+ @click.group(name='sdk', help='Advanced commands for developers')
7
+ def sdk():
8
+ pass
9
+
10
+
11
+ @sdk.command(
12
+ name='add-copilot-prompts', help='Add BioLib-specific GitHub Copilot prompts and instructions to your repository'
13
+ )
14
+ @click.option('--force', is_flag=True, help='Force overwrite existing files.')
15
+ def add_copilot_prompts_command(force: bool) -> None:
16
+ add_copilot_prompts(force)