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
@@ -1,11 +1,34 @@
1
- from requests.auth import AuthBase # type: ignore
1
+ from biolib import api
2
+ from biolib.biolib_api_client.api_client import UserTokens
3
+ from biolib.typing_utils import TypedDict, Literal
2
4
 
3
5
 
4
- class BearerAuth(AuthBase):
5
- def __init__(self, access_token=None):
6
- self.access_token = access_token
6
+ class AuthChallengeCreate(TypedDict):
7
+ token: str
7
8
 
8
- def __call__(self, req):
9
- if self.access_token:
10
- req.headers['Authorization'] = 'Bearer ' + self.access_token
11
- return req
9
+
10
+ class _AuthChallengeStatus(TypedDict):
11
+ state: Literal['awaiting', 'completed']
12
+
13
+
14
+ class AuthChallengeStatus(_AuthChallengeStatus, total=False):
15
+ user_tokens: UserTokens
16
+
17
+
18
+ class BiolibAuthChallengeApi:
19
+
20
+ @staticmethod
21
+ def create_auth_challenge() -> AuthChallengeCreate:
22
+ response = api.client.post(path='/user/auth_challenges/')
23
+ response_dict: AuthChallengeCreate = response.json()
24
+ return response_dict
25
+
26
+ @staticmethod
27
+ def get_auth_challenge_status(token: str) -> AuthChallengeStatus:
28
+ response = api.client.get(
29
+ path='/user/auth_challenges/',
30
+ headers={'Auth-Challenge-Token': token},
31
+ )
32
+
33
+ response_dict: AuthChallengeStatus = response.json()
34
+ return response_dict
@@ -1,71 +1,166 @@
1
+ import mimetypes
2
+ import os
3
+ import random
1
4
  import re
5
+ import subprocess
6
+ import urllib.parse
2
7
 
3
- import requests
4
-
8
+ import biolib.api
5
9
  from biolib import biolib_errors
6
- from biolib.biolib_api_client.auth import BearerAuth
7
- from biolib.biolib_api_client import BiolibApiClient, AppGetResponse
8
- from biolib.biolib_errors import BioLibError
10
+ from biolib._internal.http_client import HttpError
11
+ from biolib.api.client import ApiClient
12
+ from biolib.biolib_api_client import AppGetResponse
9
13
  from biolib.biolib_logging import logger
14
+ from biolib.typing_utils import Optional
15
+ from biolib.utils import load_base_url_from_env
10
16
 
11
17
 
12
- class BiolibAppApi:
18
+ def encode_multipart(data, files):
19
+ boundary = f'----------{random.randint(0, 1000000000)}'
20
+ line_array = []
13
21
 
14
- @staticmethod
15
- def get_by_uri(uri: str) -> AppGetResponse:
16
- # Replace protocol with @ (if supplied)
17
- uri = re.sub(r'^http(s)?://', '@', uri)
18
- # Replace frontend version path with app_uri compatible version (if supplied)
19
- uri = re.sub(r'/version/(?P<version>(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*))(/)?$', r':\g<version>', uri)
22
+ for key, value in data.items():
23
+ if value is not None:
24
+ line_array.append(f'--{boundary}')
25
+ line_array.append(f'Content-Disposition: form-data; name="{key}"')
26
+ line_array.append('')
27
+ line_array.append(value)
28
+
29
+ for key, (filename, value) in files.items():
30
+ line_array.append(f'--{boundary}')
31
+ line_array.append(f'Content-Disposition: form-data; name="{key}"; filename="{filename}"')
32
+ line_array.append(f'Content-Type: {mimetypes.guess_type(filename)[0] or "application/octet-stream"}')
33
+ line_array.append('')
34
+ line_array.append('')
35
+ line_array.append(value)
36
+
37
+ line_array.append(f'--{boundary}--')
38
+ line_array.append('')
39
+
40
+ data_encoded = b'\r\n'.join([line.encode() if isinstance(line, str) else line for line in line_array])
41
+ return f'multipart/form-data; boundary={boundary}', data_encoded
42
+
43
+
44
+ def _get_git_branch_name() -> str:
45
+ try:
46
+ github_actions_branch_name = os.getenv('GITHUB_REF_NAME')
47
+ if github_actions_branch_name:
48
+ return github_actions_branch_name
49
+
50
+ gitlab_ci_branch_name = os.getenv('CI_COMMIT_REF_NAME')
51
+ if gitlab_ci_branch_name:
52
+ return gitlab_ci_branch_name
53
+
54
+ result = subprocess.run(['git', 'branch', '--show-current'], check=True, stdout=subprocess.PIPE, text=True)
55
+ return result.stdout.strip()
56
+ except BaseException:
57
+ return ''
58
+
59
+
60
+ def _get_git_commit_hash() -> str:
61
+ try:
62
+ github_actions_commit_hash = os.getenv('GITHUB_SHA')
63
+ if github_actions_commit_hash:
64
+ return github_actions_commit_hash
20
65
 
21
- response = requests.get(
22
- f'{BiolibApiClient.get().base_url}/api/app/',
23
- auth=BearerAuth(BiolibApiClient.get().access_token),
24
- params={'uri': uri},
25
- timeout=5,
26
- )
66
+ gitlab_ci_commit_hash = os.getenv('CI_COMMIT_SHA')
67
+ if gitlab_ci_commit_hash:
68
+ return gitlab_ci_commit_hash
27
69
 
28
- if response.status_code == 404:
29
- raise biolib_errors.NotFound(f'Application {uri} not found.')
70
+ result = subprocess.run(['git', 'rev-parse', 'HEAD'], check=True, stdout=subprocess.PIPE, text=True)
71
+ return result.stdout.strip()
72
+ except BaseException:
73
+ return ''
30
74
 
31
- if response.status_code == 400:
32
- raise biolib_errors.BioLibError(response.content.decode())
33
75
 
34
- if not response.ok:
35
- raise Exception(response.content.decode())
76
+ def _get_git_repository_url() -> str:
77
+ try:
78
+ result = subprocess.run(['git', 'remote', 'get-url', 'origin'], check=True, stdout=subprocess.PIPE, text=True)
79
+ return result.stdout.strip()
80
+ except BaseException:
81
+ return ''
36
82
 
37
- app_response: AppGetResponse = response.json()
38
- return app_response
39
83
 
84
+ def _get_resource_uri_from_str(input_str: str) -> str:
85
+ parsed_base_url = urllib.parse.urlparse(load_base_url_from_env())
86
+ parsed_uri = urllib.parse.urlparse(input_str)
87
+ if parsed_uri.netloc != '' and parsed_base_url.netloc != parsed_uri.netloc:
88
+ raise biolib_errors.ValidationError(f'Invalid URI. The hostname "{parsed_base_url.netloc}" is not recognized.')
89
+ elif parsed_uri.netloc != '' and parsed_uri.path[1] != '@':
90
+ uri = f'@{parsed_uri.netloc}{parsed_uri.path}'
91
+ elif parsed_uri.netloc == '' and parsed_uri.path.startswith(parsed_base_url.netloc):
92
+ uri = f'@{parsed_uri.path}'
93
+ else:
94
+ uri = parsed_uri.path
95
+ uri = uri.strip('/')
96
+ # Replace frontend version path with app_uri compatible version (if supplied)
97
+ uri = re.sub(r'/version/(?P<version>(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*))(/)?$', r':\g<version>', uri)
98
+ return uri
99
+
100
+
101
+ class BiolibAppApi:
40
102
  @staticmethod
41
- def push_app_version(app_id, zip_binary, author, app_name, set_as_active):
42
- response = requests.post(
43
- f'{BiolibApiClient.get().base_url}/api/app_versions/',
44
- files={
45
- 'app': (None, app_id),
46
- 'set_as_active': (None, 'true' if set_as_active else 'false'),
47
- 'state': (None, 'published'),
48
- 'source_files_zip': zip_binary
49
- },
50
- auth=BearerAuth(BiolibApiClient.get().access_token)
51
- )
52
- if not response.ok:
53
- logger.error(f'Push failed for {author}/{app_name}:')
54
- raise BioLibError(response.text)
103
+ def get_by_uri(uri: str, api_client: Optional[ApiClient] = None) -> AppGetResponse:
104
+ uri = _get_resource_uri_from_str(uri)
105
+ api = api_client or biolib.api.client
106
+ try:
107
+ response = api.get(path='/app/', params={'uri': uri})
108
+ app_response: AppGetResponse = response.json()
109
+ return app_response
55
110
 
56
- # TODO: When response includes the version number, print the URL for the new app version
57
- logger.info(f'Successfully pushed app version for {author}/{app_name}.')
58
- return response.json()
111
+ except HttpError as error:
112
+ if error.code == 404:
113
+ raise biolib_errors.NotFound(f'Application {uri} not found.') from None
114
+
115
+ raise error
116
+
117
+ @staticmethod
118
+ def create_app(uri: str):
119
+ uri = _get_resource_uri_from_str(uri)
120
+ try:
121
+ response = biolib.api.client.post(path='/resources/apps/', data={'uri': uri})
122
+ return response.json()
123
+ except HttpError as error:
124
+ raise error
59
125
 
60
126
  @staticmethod
61
- def update_app_version(app_version_id, data):
62
- response = requests.patch(
63
- f'{BiolibApiClient.get().base_url}/api/app_versions/{app_version_id}/',
64
- json=data,
65
- auth=BearerAuth(BiolibApiClient.get().access_token)
66
- )
67
- if not response.ok:
68
- logger.error(f'Failed to update app version {app_version_id}')
69
- raise BioLibError(response.text)
127
+ def push_app_version(
128
+ app_id,
129
+ zip_binary,
130
+ author,
131
+ app_name,
132
+ set_as_active,
133
+ app_version_id_to_copy_images_from: Optional[str],
134
+ semantic_version: Optional[str],
135
+ ):
136
+ try:
137
+ data = {
138
+ 'app': app_id,
139
+ 'set_as_active': 'true' if set_as_active else 'false',
140
+ 'state': 'published',
141
+ 'app_version_id_to_copy_images_from': app_version_id_to_copy_images_from,
142
+ 'git_branch_name': _get_git_branch_name(),
143
+ 'git_commit_hash': _get_git_commit_hash(),
144
+ 'git_repository_url': _get_git_repository_url(),
145
+ }
146
+ if semantic_version:
147
+ data['semantic_version'] = semantic_version
70
148
 
149
+ content_type, data_encoded = encode_multipart(
150
+ data=data,
151
+ files={
152
+ 'source_files_zip': ('source_files.zip', zip_binary),
153
+ },
154
+ )
155
+ response = biolib.api.client.post(
156
+ path='/app_versions/',
157
+ data=data_encoded,
158
+ headers={'Content-Type': content_type},
159
+ )
160
+ except Exception as error:
161
+ logger.error(f'Push failed for {author}/{app_name}:')
162
+ raise error
163
+
164
+ # TODO: When response includes the version number, print the URL for the new app version
165
+ logger.info(f'Initialized new app version for {author}/{app_name}.')
71
166
  return response.json()
@@ -1,172 +1,192 @@
1
- import time
2
- import requests
1
+ import os
2
+ from urllib.parse import urlparse
3
3
 
4
- from biolib.biolib_api_client.auth import BearerAuth
5
- from biolib.biolib_api_client import BiolibApiClient
6
- from biolib.biolib_errors import BioLibError
7
- from biolib.compute_node.utils import SystemExceptionCodeMap
4
+ import biolib.api
5
+
6
+ from biolib import utils
7
+ from biolib._internal.http_client import HttpError
8
+ from biolib.api.client import ApiClient
9
+ from biolib.biolib_api_client import CloudJob, JobState
10
+ from biolib.biolib_errors import JobResultPermissionError, JobResultError, JobResultNotFound, StorageDownloadFailed
8
11
  from biolib.biolib_logging import logger
9
12
  from biolib.utils import BIOLIB_PACKAGE_VERSION
13
+ from biolib.typing_utils import TypedDict, Optional, Literal, Dict
10
14
 
11
15
 
12
- class RetryException(Exception):
13
- pass
14
-
15
-
16
- class BiolibJobApi:
17
-
18
- @staticmethod
19
- def create(app_version_id, caller_job=None):
20
- data = {
21
- 'app_version_id': app_version_id,
22
- 'client_type': 'biolib-python',
23
- 'client_version': BIOLIB_PACKAGE_VERSION,
24
- }
25
- if caller_job:
26
- data['caller_job'] = caller_job
27
-
28
- response = requests.post(
29
- f'{BiolibApiClient.get().base_url}/api/jobs/',
30
- auth=BearerAuth(BiolibApiClient.get().access_token),
31
- json=data,
32
- )
16
+ class PresignedS3UploadLinkResponse(TypedDict):
17
+ presigned_upload_url: str
33
18
 
34
- # TODO: Error handling with response object
35
- if not response.ok:
36
- raise BioLibError(response.content)
37
19
 
38
- return response.json()
20
+ class PresignedS3DownloadLinkResponse(TypedDict):
21
+ presigned_download_url: str
39
22
 
40
- @staticmethod
41
- def update_state(job_id, state):
42
- response = requests.patch(
43
- f'{BiolibApiClient.get().base_url}/api/jobs/{job_id}/',
44
- json={'state': state},
45
- auth=BearerAuth(BiolibApiClient.get().access_token)
46
- )
47
23
 
48
- # TODO: Error handling with response object
49
- if not response.ok:
50
- raise BioLibError(response.content)
24
+ def _get_user_info() -> Optional[str]:
25
+ if utils.BASE_URL_IS_PUBLIC_BIOLIB:
26
+ return None
51
27
 
52
- return response.json()
28
+ enterprise_agent_info_opt_env_vars = ['BIOLIB_OPT_USER', 'DOMINO_STARTING_USERNAME', 'USER']
53
29
 
54
- @staticmethod
55
- def create_cloud_job(module_name, job_id):
56
- response = None
57
- for retry in range(4):
58
- try:
59
- response = requests.post(
60
- f'{BiolibApiClient.get().base_url}/api/jobs/cloud/',
61
- json={'module_name': module_name, 'job_id': job_id},
62
- auth=BearerAuth(BiolibApiClient.get().access_token)
63
- )
64
-
65
- if response.status_code == 503:
66
- raise RetryException(response.content)
67
- # Handle possible validation errors from backend
68
- elif not response.ok:
69
- raise BioLibError(response.text)
70
-
71
- break
72
-
73
- except RetryException as retry_exception: # pylint: disable=broad-except
74
- if retry > 3:
75
- raise BioLibError('Reached retry limit for cloud job creation') from retry_exception
76
- time.sleep(1)
77
-
78
- if not response:
79
- raise BioLibError('Could not create new cloud job')
80
-
81
- cloud_job = response.json()
82
- if cloud_job.get('is_compute_node_ready', False):
83
- return cloud_job
84
-
85
- max_retry_attempts = 25
86
- retry_interval_seconds = 10
87
-
88
- for _ in range(max_retry_attempts):
89
- response = requests.get(
90
- f'{BiolibApiClient.get().base_url}/api/jobs/cloud/{cloud_job["public_id"]}/status/',
91
- auth=BearerAuth(BiolibApiClient.get().access_token)
92
- )
93
- cloud_job = response.json()
94
- if cloud_job.get('is_compute_node_ready', False):
95
- return cloud_job
30
+ for env_var in enterprise_agent_info_opt_env_vars:
31
+ env_var_value = os.getenv(env_var)
32
+ if env_var_value:
33
+ return env_var_value
96
34
 
97
- logger.info('Cloud: Reserved compute node not ready, retrying...')
98
- time.sleep(retry_interval_seconds)
35
+ return None
99
36
 
100
- raise BioLibError('Cloud: The reserved compute node was not ready in time')
101
-
102
- @staticmethod
103
- def save_compute_node_job(job, module_name, access_token, node_url):
104
- response = requests.post(
105
- f'{node_url}/v1/job/',
106
- json={'module_name': module_name, 'job': job, 'access_token': access_token}
107
- )
108
-
109
- if not response.ok:
110
- raise BioLibError(response.content)
111
-
112
- return response.json
113
37
 
38
+ class BiolibJobApi:
114
39
  @staticmethod
115
- def start_cloud_job(job_id, module_input_serialized, node_url):
116
- response = requests.post(
117
- f'{node_url}/v1/job/{job_id}/start/',
118
- data=module_input_serialized
119
- )
40
+ def create(
41
+ app_version_id,
42
+ app_resource_name_prefix=None,
43
+ override_command=False,
44
+ caller_job=None,
45
+ machine='',
46
+ experiment_uuid: Optional[str] = None,
47
+ timeout: Optional[int] = None,
48
+ notify: bool = False,
49
+ requested_machine_count: Optional[int] = None,
50
+ temporary_client_secrets: Optional[Dict[str, str]] = None,
51
+ api_client: Optional[ApiClient] = None,
52
+ ):
53
+ data = {
54
+ 'app_version_id': app_version_id,
55
+ 'client_type': 'biolib-python',
56
+ 'notify': notify,
57
+ 'client_version': BIOLIB_PACKAGE_VERSION,
58
+ 'client_opt_user_info': _get_user_info(),
59
+ }
120
60
 
121
- if not response.ok:
122
- raise BioLibError(response.content)
61
+ if app_resource_name_prefix:
62
+ data.update({'app_resource_name_prefix': app_resource_name_prefix})
123
63
 
124
- @staticmethod
125
- def await_compute_node_status(retry_interval_seconds, retry_limit_minutes, status_to_await,
126
- compute_type, job_id, node_url):
127
- status_max_retry_attempts = int(retry_limit_minutes * 60 / retry_interval_seconds)
128
- status_reached = False
129
- for _ in range(status_max_retry_attempts):
130
- response = requests.get(f'{node_url}/v1/job/{job_id}/status/')
131
- if not response.ok:
132
- raise Exception(response.content)
64
+ if override_command:
65
+ data.update({'arguments_override_command': override_command})
133
66
 
134
- status_json = response.json()
67
+ if caller_job:
68
+ data['caller_job'] = caller_job
135
69
 
136
- for status_update in status_json['status_updates']:
137
- if 'log_message' in status_update:
138
- logger.info(f'{compute_type}: {status_update["log_message"]}')
70
+ if machine:
71
+ data.update({'requested_machine': machine})
139
72
 
140
- if status_update.get('log_message') == status_to_await:
141
- status_reached = True
73
+ if requested_machine_count:
74
+ data.update({'requested_machine_count': requested_machine_count})
142
75
 
143
- if 'error_code' in status_json:
144
- error_code = status_json['error_code']
145
- error_message = SystemExceptionCodeMap.get(error_code, f'Unknown error code {error_code}')
76
+ if experiment_uuid:
77
+ data['experiment_uuid'] = experiment_uuid
146
78
 
147
- raise BioLibError(f'{compute_type}: {error_message}')
79
+ if timeout:
80
+ data['requested_timeout_seconds'] = timeout
148
81
 
149
- if status_reached:
150
- return
82
+ if temporary_client_secrets:
83
+ data['temporary_client_secrets'] = temporary_client_secrets
151
84
 
152
- time.sleep(retry_interval_seconds)
85
+ api = api_client or biolib.api.client
86
+ response = api.post(path='/jobs/', data=data)
153
87
 
154
- raise BioLibError(f'{compute_type}: Failed to get results: Retry limit exceeded')
88
+ return response.json()
155
89
 
156
90
  @staticmethod
157
- def get_cloud_result(job_id, node_url):
158
- response = requests.get(
159
- f'{node_url}/v1/job/{job_id}/result/',
160
- )
91
+ def update_state(job_uuid: str, state: JobState) -> None:
92
+ try:
93
+ biolib.api.client.patch(path=f'/jobs/{job_uuid}/', data={'state': state.value})
94
+ except BaseException as error:
95
+ logger.error(f'Failed to update job "{job_uuid}" to state "{state.value}" due to {error}')
161
96
 
162
- if not response.ok:
163
- raise Exception(response.content)
97
+ @staticmethod
98
+ def create_cloud_job(
99
+ job_id: str,
100
+ result_name_prefix: Optional[str],
101
+ api_client: Optional[ApiClient] = None,
102
+ ) -> CloudJob:
103
+ data = {'job_id': job_id}
104
+ if result_name_prefix:
105
+ data['result_name_prefix'] = result_name_prefix
106
+
107
+ api = api_client or biolib.api.client
108
+ response = api.post(path='/jobs/cloud/', data=data)
109
+ cloud_job: CloudJob = response.json()
110
+ return cloud_job
164
111
 
165
- return response.content
112
+ @staticmethod
113
+ def get_job_storage_download_url(
114
+ job_uuid: str,
115
+ job_auth_token: str,
116
+ storage_type: Literal['input', 'results'],
117
+ ) -> str:
118
+ try:
119
+ response = biolib.api.client.get(
120
+ path=f'/jobs/{job_uuid}/storage/{storage_type}/download/',
121
+ authenticate=True,
122
+ headers={'Job-Auth-Token': job_auth_token},
123
+ )
124
+ presigned_s3_download_link_response: PresignedS3DownloadLinkResponse = response.json()
125
+ presigned_download_url = presigned_s3_download_link_response['presigned_download_url']
126
+
127
+ app_caller_proxy_job_storage_base_url = os.getenv('BIOLIB_CLOUD_JOB_STORAGE_BASE_URL', '')
128
+ if app_caller_proxy_job_storage_base_url:
129
+ # Done to hit App Caller Proxy when downloading result from inside an app
130
+ parsed_url = urlparse(presigned_download_url)
131
+ presigned_download_url = f'{app_caller_proxy_job_storage_base_url}{parsed_url.path}?{parsed_url.query}'
132
+
133
+ return presigned_download_url
134
+
135
+ except HttpError as error:
136
+ if storage_type == 'results':
137
+ if error.code == 401:
138
+ raise JobResultPermissionError('You must be signed in to get result of the job') from None
139
+ elif error.code == 403:
140
+ raise JobResultPermissionError(
141
+ 'Cannot get result of job. Maybe the job was created without being signed in?'
142
+ ) from None
143
+ elif error.code == 404:
144
+ raise JobResultNotFound('Job result not found') from None
145
+ else:
146
+ raise JobResultError('Failed to get result of job') from error
147
+ else:
148
+ raise StorageDownloadFailed(f'Failed to download result of job got error: {error}') from error
149
+
150
+ except Exception as error: # pylint: disable=broad-except
151
+ if storage_type == 'results':
152
+ raise JobResultError('Failed to get result of job') from error
153
+ else:
154
+ raise StorageDownloadFailed('Failed to download from Job Storage') from error
166
155
 
167
156
  @staticmethod
168
- def get_enclave_json(biolib_base_url):
169
- response = requests.get(
170
- f'{biolib_base_url}/info-files/biolib-enclave.json',
171
- )
172
- return response.json()
157
+ def create_job_with_data(
158
+ app_version_uuid: str,
159
+ app_resource_name_prefix: Optional[str],
160
+ module_input_serialized: bytes,
161
+ arguments_override_command: bool,
162
+ experiment_uuid: Optional[str],
163
+ requested_machine: Optional[str],
164
+ result_name_prefix: Optional[str],
165
+ caller_job_uuid: Optional[str] = None,
166
+ requested_timeout_seconds: Optional[int] = None,
167
+ notify: bool = False,
168
+ requested_machine_count: Optional[int] = None,
169
+ api_client: Optional[ApiClient] = None,
170
+ ) -> Dict:
171
+ api = api_client or biolib.api.client
172
+ job_dict: Dict = api.post(
173
+ path='/jobs/create_job_with_data/',
174
+ data=module_input_serialized,
175
+ headers={
176
+ 'Content-Type': 'application/octet-stream',
177
+ 'app-version-uuid': app_version_uuid,
178
+ 'app-resource-name-prefix': app_resource_name_prefix,
179
+ 'arguments-override-command': str(arguments_override_command),
180
+ 'caller-job-uuid': caller_job_uuid,
181
+ 'client-opt-user-info': _get_user_info(),
182
+ 'client-type': 'biolib-python',
183
+ 'client-version': BIOLIB_PACKAGE_VERSION,
184
+ 'experiment-uuid': experiment_uuid,
185
+ 'requested-machine': requested_machine,
186
+ 'requested-machine-count': str(requested_machine_count) if requested_machine_count else None,
187
+ 'result-name-prefix': result_name_prefix,
188
+ 'requested-timeout-seconds': str(requested_timeout_seconds) if requested_timeout_seconds else None,
189
+ 'notify': 'true' if notify else 'false',
190
+ },
191
+ ).json()
192
+ return job_dict
@@ -1,9 +1,8 @@
1
1
  from enum import Enum
2
2
 
3
- from biolib.compute_node.webserver.webserver_types import ComputeNodeInfo
4
- from biolib.typing_utils import TypedDict, Optional, List
5
-
6
3
  from biolib.biolib_api_client.app_types import AppVersionOnJob, RemoteHost
4
+ from biolib.compute_node.webserver.webserver_types import ComputeNodeInfo
5
+ from biolib.typing_utils import Dict, List, Optional, TypedDict
7
6
 
8
7
 
9
8
  class JobState(Enum):
@@ -15,21 +14,38 @@ class JobState(Enum):
15
14
 
16
15
 
17
16
  class _Job(TypedDict):
17
+ app_uri: str
18
18
  app_version: AppVersionOnJob
19
+ arguments_override_command: bool
20
+ auth_token: str
19
21
  caller_job: Optional[str]
20
22
  created_at: str
23
+ federated_job_uuid: Optional[str]
21
24
  public_id: str
22
25
  remote_hosts_with_warning: List[RemoteHost]
26
+ state: str
23
27
  user_id: Optional[str]
28
+ uuid: str
24
29
 
25
30
 
26
31
  # type optional keys with total=False
27
- class Job(_Job, total=False):
32
+ class CreatedJobDict(_Job, total=False):
28
33
  custom_compute_node_url: str
34
+ temporary_client_secrets: Dict[str, str]
35
+
36
+
37
+ class CloudJob(TypedDict):
38
+ public_id: str
39
+ reserved_cpu_in_nano_shares: int
40
+ reserved_gpu_count: int
41
+ reserved_memory_in_bytes: int
42
+ max_runtime_in_seconds: int
29
43
 
30
44
 
31
45
  class JobWrapper(TypedDict):
32
46
  access_token: str
33
47
  BASE_URL: str # TODO: refactor this to lower case
34
48
  compute_node_info: Optional[ComputeNodeInfo]
35
- job: Job
49
+ job: CreatedJobDict
50
+ cloud_job: Optional[CloudJob]
51
+ job_temporary_dir: Optional[str]