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,17 @@
1
+ from .typing import TypedDict
2
+
3
+
4
+ class FileZipMetadataDict(TypedDict):
5
+ header_start: int
6
+ size_on_disk: int
7
+
8
+
9
+ class FileNodeDict(TypedDict):
10
+ dir_path: str
11
+ is_dir: bool
12
+ name: str
13
+ size: int
14
+
15
+
16
+ class ZipFileNodeDict(FileNodeDict):
17
+ zip_meta: FileZipMetadataDict
@@ -0,0 +1,6 @@
1
+ from typing import TypedDict
2
+
3
+
4
+ class PushResponseDict(TypedDict):
5
+ app_uri: str
6
+ sematic_version: str
@@ -0,0 +1,37 @@
1
+ from .experiment import DeprecatedExperimentDict
2
+ from .resource_version import ResourceVersionDetailedDict
3
+ from .typing import Literal, NotRequired, Optional, TypedDict
4
+
5
+
6
+ class SemanticVersionDict(TypedDict):
7
+ major: int
8
+ minor: int
9
+ patch: int
10
+
11
+
12
+ class ResourceUriDict(TypedDict):
13
+ account_handle_normalized: str
14
+ account_handle: str
15
+ resource_name_normalized: Optional[str]
16
+ resource_name: Optional[str]
17
+ resource_prefix: Optional[str]
18
+ version: Optional[SemanticVersionDict]
19
+ tag: Optional[str]
20
+
21
+
22
+ ResourceTypeLiteral = Literal['app', 'data-record', 'experiment', 'index']
23
+
24
+
25
+ class ResourceDict(TypedDict):
26
+ uuid: str
27
+ uri: str
28
+ name: str
29
+ created_at: str
30
+ description: str
31
+ account_uuid: str
32
+
33
+
34
+ class ResourceDetailedDict(ResourceDict):
35
+ type: ResourceTypeLiteral
36
+ version: NotRequired[ResourceVersionDetailedDict]
37
+ experiment: Optional[DeprecatedExperimentDict]
@@ -0,0 +1,11 @@
1
+ from .typing import TypedDict
2
+
3
+
4
+ class ResourceDeployKeyDict(TypedDict):
5
+ uuid: str
6
+ created_at: str
7
+ name: str
8
+
9
+
10
+ class ResourceDeployKeyWithSecretDict(ResourceDeployKeyDict):
11
+ secret_key: str
@@ -0,0 +1,14 @@
1
+ from .resource import ResourceDict
2
+ from .typing import TypedDict
3
+
4
+
5
+ class ResourcePermissionDict(TypedDict):
6
+ uuid: str
7
+ created_at: str
8
+ action: str
9
+ resource: ResourceDict
10
+ target_resource: ResourceDict
11
+
12
+
13
+ class ResourcePermissionDetailedDict(ResourcePermissionDict):
14
+ pass
@@ -0,0 +1,19 @@
1
+ from .typing import Literal, NotRequired, Optional, TypedDict
2
+
3
+
4
+ class ResourceVersionAssetsDict(TypedDict):
5
+ download_url: str
6
+ size_bytes: int
7
+
8
+
9
+ class ResourceVersionDict(TypedDict):
10
+ uuid: str
11
+ semantic_version: str
12
+ state: Literal['published', 'unpublished']
13
+ created_at: str
14
+ git_branch_name: NotRequired[str]
15
+ git_commit_hash: NotRequired[str]
16
+
17
+
18
+ class ResourceVersionDetailedDict(ResourceVersionDict):
19
+ assets: Optional[ResourceVersionAssetsDict]
@@ -0,0 +1,14 @@
1
+ from .typing import Optional, TypedDict
2
+
3
+
4
+ class ResultDict(TypedDict):
5
+ uuid: str
6
+ name: str
7
+ app_uri: str
8
+ created_at: str
9
+ state: str
10
+ finished_at: Optional[str]
11
+
12
+
13
+ class ResultDetailedDict(ResultDict):
14
+ pass
@@ -0,0 +1,10 @@
1
+ import sys
2
+
3
+ # import and expose everything from the typing module
4
+ from typing import * # noqa:F403 pylint: disable=wildcard-import, unused-wildcard-import
5
+
6
+ if sys.version_info < (3, 8): # noqa: UP036
7
+ from typing_extensions import Literal, TypedDict # pylint: disable=unused-import
8
+
9
+ if sys.version_info < (3, 11): # noqa: UP036
10
+ from typing_extensions import NotRequired # pylint: disable=unused-import
@@ -0,0 +1,19 @@
1
+ from .account import AccountDict
2
+ from .typing import Optional, TypedDict
3
+
4
+
5
+ class EnterpriseSettingsDict(TypedDict):
6
+ account_uuid: str
7
+ dashboard_message: Optional[str]
8
+ docs_message: Optional[str]
9
+ featured_dashboard_app_version_uuid: Optional[str]
10
+
11
+
12
+ class UserDict(TypedDict):
13
+ uuid: str
14
+ account: AccountDict
15
+
16
+
17
+ class UserDetailedDict(UserDict):
18
+ email: str
19
+ enterprise_settings: Optional[EnterpriseSettingsDict]
@@ -0,0 +1,7 @@
1
+ from biolib._shared.utils.resource_uri import normalize_resource_name, parse_resource_uri, parse_semantic_version
2
+
3
+ __all__ = [
4
+ 'normalize_resource_name',
5
+ 'parse_resource_uri',
6
+ 'parse_semantic_version',
7
+ ]
@@ -0,0 +1,75 @@
1
+ import re
2
+
3
+ from biolib._shared.types import Optional
4
+ from biolib._shared.types.resource import ResourceUriDict, SemanticVersionDict
5
+ from biolib.biolib_errors import BioLibError
6
+
7
+ URI_REGEX = re.compile(
8
+ r'^(@(?P<resource_prefix>[\w._-]+)/)?'
9
+ r'(?P<account_handle>[\w-]+)'
10
+ r'(/(?P<resource_name>[\w-]+))?'
11
+ r'(?::(?P<suffix>[^:]+))?$'
12
+ )
13
+ SEMVER_REGEX = re.compile(r'^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)$')
14
+ TAG_REGEX = re.compile(r'^[a-z0-9-]{1,128}$')
15
+
16
+
17
+ def normalize_resource_name(string: str) -> str:
18
+ return string.replace('-', '_').lower()
19
+
20
+
21
+ def parse_semantic_version(semantic_version: str) -> SemanticVersionDict:
22
+ if match := SEMVER_REGEX.fullmatch(semantic_version):
23
+ return SemanticVersionDict(
24
+ major=int(match.group('major')),
25
+ minor=int(match.group('minor')),
26
+ patch=int(match.group('patch')),
27
+ )
28
+
29
+ raise ValueError('The version must be a valid semantic version in the format of major.minor.patch (1.2.3).')
30
+
31
+
32
+ def parse_resource_uri(uri: str, use_account_as_name_default: bool = True) -> ResourceUriDict:
33
+ matches = URI_REGEX.match(uri)
34
+ if matches is None:
35
+ raise BioLibError(f"Could not parse resource uri '{uri}', uri did not match regex")
36
+
37
+ version: Optional[SemanticVersionDict] = None
38
+ tag: Optional[str] = None
39
+
40
+ suffix = matches.group('suffix')
41
+ if suffix and suffix != '*':
42
+ try:
43
+ version = parse_semantic_version(suffix)
44
+ except ValueError:
45
+ if TAG_REGEX.fullmatch(suffix):
46
+ tag = suffix
47
+ else:
48
+ raise BioLibError(
49
+ f'Invalid version or tag "{suffix}". '
50
+ 'Versions must be semantic versions like "1.2.3". '
51
+ 'Tags must be lowercase alphanumeric or dashes and at most 128 characters.'
52
+ ) from None
53
+
54
+ resource_prefix_raw: Optional[str] = matches.group('resource_prefix')
55
+ resource_prefix = resource_prefix_raw.lower() if resource_prefix_raw is not None else None
56
+ account_handle: str = matches.group('account_handle')
57
+ account_handle_normalized: str = normalize_resource_name(account_handle)
58
+ resource_name: Optional[str] = matches.group('resource_name')
59
+
60
+ if resource_name:
61
+ resource_name_normalized = normalize_resource_name(resource_name)
62
+ elif use_account_as_name_default:
63
+ resource_name_normalized = account_handle_normalized
64
+ else:
65
+ resource_name_normalized = None
66
+
67
+ return ResourceUriDict(
68
+ resource_prefix=resource_prefix,
69
+ account_handle=account_handle,
70
+ account_handle_normalized=account_handle_normalized,
71
+ resource_name_normalized=resource_name_normalized,
72
+ resource_name=resource_name if resource_name is not None or not use_account_as_name_default else account_handle,
73
+ version=version,
74
+ tag=tag,
75
+ )
biolib/api/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from biolib._internal.http_client import HttpClient
2
+
3
+ from .client import ApiClient as _ApiClient
4
+
5
+ _client = _ApiClient()
6
+ client = _client
biolib/api/client.py ADDED
@@ -0,0 +1,168 @@
1
+ from datetime import datetime, timezone
2
+ from json.decoder import JSONDecodeError
3
+ from urllib.parse import urlencode, urljoin
4
+
5
+ import importlib_metadata
6
+
7
+ from biolib._internal.http_client import HttpClient, HttpResponse
8
+ from biolib._internal.utils.auth import decode_jwt_without_checking_signature
9
+ from biolib._shared.types.typing import Dict, Optional, TypedDict, Union, cast
10
+ from biolib.biolib_api_client import BiolibApiClient as DeprecatedApiClient
11
+ from biolib.biolib_errors import BioLibError
12
+ from biolib.biolib_logging import logger
13
+
14
+ OptionalHeaders = Union[
15
+ Optional[Dict[str, str]],
16
+ Optional[Dict[str, Union[str, None]]],
17
+ ]
18
+
19
+
20
+ def _get_biolib_package_version() -> str:
21
+ # try fetching version, if it fails (usually when in dev), add default
22
+ try:
23
+ return cast(str, importlib_metadata.version('pybiolib'))
24
+ except importlib_metadata.PackageNotFoundError:
25
+ return '0.0.0'
26
+
27
+
28
+ class ApiClientInitDict(TypedDict):
29
+ refresh_token: str
30
+ base_url: str
31
+ client_type: Optional[str]
32
+
33
+
34
+ class ApiClient(HttpClient):
35
+ _biolib_package_version: str = _get_biolib_package_version()
36
+
37
+ def __init__(self, _init_dict: Optional[ApiClientInitDict] = None) -> None:
38
+ self._access_token: Optional[str] = None
39
+ self._refresh_token: Optional[str] = _init_dict['refresh_token'] if _init_dict else None
40
+ self._base_url: Optional[str] = _init_dict['base_url'] if _init_dict else None
41
+ self._client_type: Optional[str] = _init_dict['client_type'] if _init_dict else None
42
+
43
+ def get(
44
+ self,
45
+ path: str,
46
+ params: Optional[Dict[str, Union[str, int]]] = None,
47
+ headers: OptionalHeaders = None,
48
+ authenticate: bool = True,
49
+ retries: int = 10,
50
+ ) -> HttpResponse:
51
+ return self.request(
52
+ headers=self._get_headers(opt_headers=headers, authenticate=authenticate),
53
+ method='GET',
54
+ retries=retries,
55
+ url=self._get_absolute_url(path=path, query_params=params),
56
+ )
57
+
58
+ def post(
59
+ self,
60
+ path: str,
61
+ data: Optional[Union[Dict, bytes]] = None,
62
+ params: Optional[Dict[str, Union[str, int]]] = None,
63
+ headers: OptionalHeaders = None,
64
+ authenticate: bool = True,
65
+ retries: int = 50, # TODO: reduce this back to 5 when timeout errors have been solved
66
+ ) -> HttpResponse:
67
+ return self.request(
68
+ data=data,
69
+ headers=self._get_headers(opt_headers=headers, authenticate=authenticate),
70
+ method='POST',
71
+ retries=retries,
72
+ url=self._get_absolute_url(path=path, query_params=params),
73
+ )
74
+
75
+ def patch(
76
+ self,
77
+ path: str,
78
+ data: Dict,
79
+ headers: OptionalHeaders = None,
80
+ retries: int = 5,
81
+ params: Optional[Dict[str, Union[str, int]]] = None,
82
+ ) -> HttpResponse:
83
+ return self.request(
84
+ data=data,
85
+ headers=self._get_headers(opt_headers=headers, authenticate=True),
86
+ method='PATCH',
87
+ retries=retries,
88
+ url=self._get_absolute_url(path=path, query_params=params),
89
+ )
90
+
91
+ def delete(
92
+ self,
93
+ path: str,
94
+ headers: OptionalHeaders = None,
95
+ retries: int = 0,
96
+ ) -> HttpResponse:
97
+ return self.request(
98
+ headers=self._get_headers(opt_headers=headers, authenticate=True),
99
+ method='DELETE',
100
+ retries=retries,
101
+ url=self._get_absolute_url(path=path, query_params=None),
102
+ )
103
+
104
+ def _get_headers(self, opt_headers: OptionalHeaders, authenticate: bool) -> Dict[str, str]:
105
+ # Only keep header keys with a value
106
+ headers: Dict[str, str] = {key: value for key, value in (opt_headers or {}).items() if value}
107
+
108
+ if authenticate:
109
+ if self._refresh_token:
110
+ headers['Authorization'] = f'Bearer {self._get_access_token()}'
111
+ else:
112
+ # TODO: Remove this block when deprecated api client is removed
113
+ deprecated_api_client = DeprecatedApiClient.get()
114
+ if deprecated_api_client.is_signed_in:
115
+ deprecated_api_client.refresh_access_token()
116
+
117
+ if deprecated_api_client.resource_deploy_key:
118
+ headers['Authorization'] = f'Token {deprecated_api_client.resource_deploy_key}'
119
+ else:
120
+ # Adding access_token outside is_signed_in check as job_worker.py currently sets access_token
121
+ # without setting refresh_token
122
+ access_token = deprecated_api_client.access_token
123
+ if access_token:
124
+ headers['Authorization'] = f'Bearer {access_token}'
125
+
126
+ headers['client-type'] = 'biolib-python'
127
+ headers['client-version'] = ApiClient._biolib_package_version
128
+ if self._client_type:
129
+ headers['client-opt-type'] = self._client_type
130
+
131
+ return headers
132
+
133
+ def _get_absolute_url(self, path: str, query_params: Optional[Dict[str, Union[str, int]]]) -> str:
134
+ deprecated_api_client = DeprecatedApiClient.get()
135
+ base_url = self._base_url or deprecated_api_client.base_url
136
+ base_api_url = urljoin(base_url, '/api/')
137
+ url = urljoin(base_api_url, path.strip('/') + '/')
138
+ if query_params:
139
+ url = url + '?' + urlencode(query_params)
140
+
141
+ return url
142
+
143
+ def _get_access_token(self) -> str:
144
+ if self._access_token:
145
+ decoded_token = decode_jwt_without_checking_signature(self._access_token)
146
+ if datetime.now(tz=timezone.utc).timestamp() < decoded_token['payload']['exp'] - 60: # 60 second buffer
147
+ # Token has not expired yet
148
+ return self._access_token
149
+
150
+ # TODO: Implement nicer error handling
151
+ try:
152
+ response = HttpClient.request(
153
+ method='POST',
154
+ url=f'{self._base_url}/api/user/token/refresh/',
155
+ data={'refresh': self._refresh_token},
156
+ )
157
+ except Exception as exception:
158
+ logger.error('Sign in with refresh token failed')
159
+ raise exception
160
+
161
+ try:
162
+ response_dict = response.json()
163
+ except JSONDecodeError as error:
164
+ logger.error('Could not decode response from server as JSON:')
165
+ raise BioLibError(response.text) from error
166
+
167
+ self._access_token = cast(str, response_dict['access'])
168
+ return self._access_token