pybiolib 0.2.951__py3-none-any.whl → 1.2.1890__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- biolib/__init__.py +357 -11
- biolib/_data_record/data_record.py +380 -0
- biolib/_index/__init__.py +0 -0
- biolib/_index/index.py +55 -0
- biolib/_index/query_result.py +103 -0
- biolib/_internal/__init__.py +0 -0
- biolib/_internal/add_copilot_prompts.py +58 -0
- biolib/_internal/add_gui_files.py +81 -0
- biolib/_internal/data_record/__init__.py +1 -0
- biolib/_internal/data_record/data_record.py +85 -0
- biolib/_internal/data_record/push_data.py +116 -0
- biolib/_internal/data_record/remote_storage_endpoint.py +43 -0
- biolib/_internal/errors.py +5 -0
- biolib/_internal/file_utils.py +125 -0
- biolib/_internal/fuse_mount/__init__.py +1 -0
- biolib/_internal/fuse_mount/experiment_fuse_mount.py +209 -0
- biolib/_internal/http_client.py +159 -0
- biolib/_internal/lfs/__init__.py +1 -0
- biolib/_internal/lfs/cache.py +51 -0
- biolib/_internal/libs/__init__.py +1 -0
- biolib/_internal/libs/fusepy/__init__.py +1257 -0
- biolib/_internal/push_application.py +488 -0
- biolib/_internal/runtime.py +22 -0
- biolib/_internal/string_utils.py +13 -0
- biolib/_internal/templates/__init__.py +1 -0
- biolib/_internal/templates/copilot_template/.github/instructions/general-app-knowledge.instructions.md +10 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-general.instructions.md +20 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-python.instructions.md +16 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_app_inputs.prompt.md +11 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_run_apps.prompt.md +12 -0
- biolib/_internal/templates/dashboard_template/.biolib/config.yml +5 -0
- biolib/_internal/templates/github_workflow_template/.github/workflows/biolib.yml +21 -0
- biolib/_internal/templates/gitignore_template/.gitignore +10 -0
- biolib/_internal/templates/gui_template/.yarnrc.yml +1 -0
- biolib/_internal/templates/gui_template/App.tsx +53 -0
- biolib/_internal/templates/gui_template/Dockerfile +27 -0
- biolib/_internal/templates/gui_template/biolib-sdk.ts +82 -0
- biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
- biolib/_internal/templates/gui_template/index.css +5 -0
- biolib/_internal/templates/gui_template/index.html +13 -0
- biolib/_internal/templates/gui_template/index.tsx +10 -0
- biolib/_internal/templates/gui_template/package.json +27 -0
- biolib/_internal/templates/gui_template/tsconfig.json +24 -0
- biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +50 -0
- biolib/_internal/templates/gui_template/vite.config.mts +10 -0
- biolib/_internal/templates/init_template/.biolib/config.yml +19 -0
- biolib/_internal/templates/init_template/Dockerfile +14 -0
- biolib/_internal/templates/init_template/requirements.txt +1 -0
- biolib/_internal/templates/init_template/run.py +12 -0
- biolib/_internal/templates/init_template/run.sh +4 -0
- biolib/_internal/templates/templates.py +25 -0
- biolib/_internal/tree_utils.py +106 -0
- biolib/_internal/utils/__init__.py +65 -0
- biolib/_internal/utils/auth.py +46 -0
- biolib/_internal/utils/job_url.py +33 -0
- biolib/_internal/utils/multinode.py +263 -0
- biolib/_runtime/runtime.py +157 -0
- biolib/_session/session.py +44 -0
- biolib/_shared/__init__.py +0 -0
- biolib/_shared/types/__init__.py +74 -0
- biolib/_shared/types/account.py +12 -0
- biolib/_shared/types/account_member.py +8 -0
- biolib/_shared/types/app.py +9 -0
- biolib/_shared/types/data_record.py +40 -0
- biolib/_shared/types/experiment.py +32 -0
- biolib/_shared/types/file_node.py +17 -0
- biolib/_shared/types/push.py +6 -0
- biolib/_shared/types/resource.py +37 -0
- biolib/_shared/types/resource_deploy_key.py +11 -0
- biolib/_shared/types/resource_permission.py +14 -0
- biolib/_shared/types/resource_version.py +19 -0
- biolib/_shared/types/result.py +14 -0
- biolib/_shared/types/typing.py +10 -0
- biolib/_shared/types/user.py +19 -0
- biolib/_shared/utils/__init__.py +7 -0
- biolib/_shared/utils/resource_uri.py +75 -0
- biolib/api/__init__.py +6 -0
- biolib/api/client.py +168 -0
- biolib/app/app.py +252 -49
- biolib/app/search_apps.py +45 -0
- biolib/biolib_api_client/api_client.py +126 -31
- biolib/biolib_api_client/app_types.py +24 -4
- biolib/biolib_api_client/auth.py +31 -8
- biolib/biolib_api_client/biolib_app_api.py +147 -52
- biolib/biolib_api_client/biolib_job_api.py +161 -141
- biolib/biolib_api_client/job_types.py +21 -5
- biolib/biolib_api_client/lfs_types.py +7 -23
- biolib/biolib_api_client/user_state.py +56 -0
- biolib/biolib_binary_format/__init__.py +1 -4
- biolib/biolib_binary_format/file_in_container.py +105 -0
- biolib/biolib_binary_format/module_input.py +24 -7
- biolib/biolib_binary_format/module_output_v2.py +149 -0
- biolib/biolib_binary_format/remote_endpoints.py +34 -0
- biolib/biolib_binary_format/remote_stream_seeker.py +59 -0
- biolib/biolib_binary_format/saved_job.py +3 -2
- biolib/biolib_binary_format/{attestation_document.py → stdout_and_stderr.py} +8 -8
- biolib/biolib_binary_format/system_status_update.py +3 -2
- biolib/biolib_binary_format/utils.py +175 -0
- biolib/biolib_docker_client/__init__.py +11 -2
- biolib/biolib_errors.py +36 -0
- biolib/biolib_logging.py +27 -10
- biolib/cli/__init__.py +38 -0
- biolib/cli/auth.py +46 -0
- biolib/cli/data_record.py +164 -0
- biolib/cli/index.py +32 -0
- biolib/cli/init.py +421 -0
- biolib/cli/lfs.py +101 -0
- biolib/cli/push.py +50 -0
- biolib/cli/run.py +63 -0
- biolib/cli/runtime.py +14 -0
- biolib/cli/sdk.py +16 -0
- biolib/cli/start.py +56 -0
- biolib/compute_node/cloud_utils/cloud_utils.py +110 -161
- biolib/compute_node/job_worker/cache_state.py +66 -88
- biolib/compute_node/job_worker/cache_types.py +1 -6
- biolib/compute_node/job_worker/docker_image_cache.py +112 -37
- biolib/compute_node/job_worker/executors/__init__.py +0 -3
- biolib/compute_node/job_worker/executors/docker_executor.py +532 -199
- biolib/compute_node/job_worker/executors/docker_types.py +9 -1
- biolib/compute_node/job_worker/executors/types.py +19 -9
- biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +30 -0
- biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +3 -5
- biolib/compute_node/job_worker/job_storage.py +108 -0
- biolib/compute_node/job_worker/job_worker.py +397 -212
- biolib/compute_node/job_worker/large_file_system.py +87 -38
- biolib/compute_node/job_worker/network_alloc.py +99 -0
- biolib/compute_node/job_worker/network_buffer.py +240 -0
- biolib/compute_node/job_worker/utilization_reporter_thread.py +197 -0
- biolib/compute_node/job_worker/utils.py +9 -24
- biolib/compute_node/remote_host_proxy.py +400 -98
- biolib/compute_node/utils.py +31 -9
- biolib/compute_node/webserver/compute_node_results_proxy.py +189 -0
- biolib/compute_node/webserver/proxy_utils.py +28 -0
- biolib/compute_node/webserver/webserver.py +130 -44
- biolib/compute_node/webserver/webserver_types.py +2 -6
- biolib/compute_node/webserver/webserver_utils.py +77 -12
- biolib/compute_node/webserver/worker_thread.py +183 -42
- biolib/experiments/__init__.py +0 -0
- biolib/experiments/experiment.py +356 -0
- biolib/jobs/__init__.py +1 -0
- biolib/jobs/job.py +741 -0
- biolib/jobs/job_result.py +185 -0
- biolib/jobs/types.py +50 -0
- biolib/py.typed +0 -0
- biolib/runtime/__init__.py +14 -0
- biolib/sdk/__init__.py +91 -0
- biolib/tables.py +34 -0
- biolib/typing_utils.py +2 -7
- biolib/user/__init__.py +1 -0
- biolib/user/sign_in.py +54 -0
- biolib/utils/__init__.py +162 -0
- biolib/utils/cache_state.py +94 -0
- biolib/utils/multipart_uploader.py +194 -0
- biolib/utils/seq_util.py +150 -0
- biolib/utils/zip/remote_zip.py +640 -0
- pybiolib-1.2.1890.dist-info/METADATA +41 -0
- pybiolib-1.2.1890.dist-info/RECORD +177 -0
- {pybiolib-0.2.951.dist-info → pybiolib-1.2.1890.dist-info}/WHEEL +1 -1
- pybiolib-1.2.1890.dist-info/entry_points.txt +2 -0
- README.md +0 -17
- biolib/app/app_result.py +0 -68
- biolib/app/utils.py +0 -62
- biolib/biolib-js/0-biolib.worker.js +0 -1
- biolib/biolib-js/1-biolib.worker.js +0 -1
- biolib/biolib-js/2-biolib.worker.js +0 -1
- biolib/biolib-js/3-biolib.worker.js +0 -1
- biolib/biolib-js/4-biolib.worker.js +0 -1
- biolib/biolib-js/5-biolib.worker.js +0 -1
- biolib/biolib-js/6-biolib.worker.js +0 -1
- biolib/biolib-js/index.html +0 -10
- biolib/biolib-js/main-biolib.js +0 -1
- biolib/biolib_api_client/biolib_account_api.py +0 -21
- biolib/biolib_api_client/biolib_large_file_system_api.py +0 -108
- biolib/biolib_binary_format/aes_encrypted_package.py +0 -42
- biolib/biolib_binary_format/module_output.py +0 -58
- biolib/biolib_binary_format/rsa_encrypted_aes_package.py +0 -57
- biolib/biolib_push.py +0 -114
- biolib/cli.py +0 -203
- biolib/cli_utils.py +0 -273
- biolib/compute_node/cloud_utils/enclave_parent_types.py +0 -7
- biolib/compute_node/enclave/__init__.py +0 -2
- biolib/compute_node/enclave/enclave_remote_hosts.py +0 -53
- biolib/compute_node/enclave/nitro_secure_module_utils.py +0 -64
- biolib/compute_node/job_worker/executors/base_executor.py +0 -18
- biolib/compute_node/job_worker/executors/pyppeteer_executor.py +0 -173
- biolib/compute_node/job_worker/executors/remote/__init__.py +0 -1
- biolib/compute_node/job_worker/executors/remote/nitro_enclave_utils.py +0 -81
- biolib/compute_node/job_worker/executors/remote/remote_executor.py +0 -51
- biolib/lfs.py +0 -196
- biolib/pyppeteer/.circleci/config.yml +0 -100
- biolib/pyppeteer/.coveragerc +0 -3
- biolib/pyppeteer/.gitignore +0 -89
- biolib/pyppeteer/.pre-commit-config.yaml +0 -28
- biolib/pyppeteer/CHANGES.md +0 -253
- biolib/pyppeteer/CONTRIBUTING.md +0 -26
- biolib/pyppeteer/LICENSE +0 -12
- biolib/pyppeteer/README.md +0 -137
- biolib/pyppeteer/docs/Makefile +0 -177
- biolib/pyppeteer/docs/_static/custom.css +0 -28
- biolib/pyppeteer/docs/_templates/layout.html +0 -10
- biolib/pyppeteer/docs/changes.md +0 -1
- biolib/pyppeteer/docs/conf.py +0 -299
- biolib/pyppeteer/docs/index.md +0 -21
- biolib/pyppeteer/docs/make.bat +0 -242
- biolib/pyppeteer/docs/reference.md +0 -211
- biolib/pyppeteer/docs/server.py +0 -60
- biolib/pyppeteer/poetry.lock +0 -1699
- biolib/pyppeteer/pyppeteer/__init__.py +0 -135
- biolib/pyppeteer/pyppeteer/accessibility.py +0 -286
- biolib/pyppeteer/pyppeteer/browser.py +0 -401
- biolib/pyppeteer/pyppeteer/browser_fetcher.py +0 -194
- biolib/pyppeteer/pyppeteer/command.py +0 -22
- biolib/pyppeteer/pyppeteer/connection/__init__.py +0 -242
- biolib/pyppeteer/pyppeteer/connection/cdpsession.py +0 -101
- biolib/pyppeteer/pyppeteer/coverage.py +0 -346
- biolib/pyppeteer/pyppeteer/device_descriptors.py +0 -787
- biolib/pyppeteer/pyppeteer/dialog.py +0 -79
- biolib/pyppeteer/pyppeteer/domworld.py +0 -597
- biolib/pyppeteer/pyppeteer/emulation_manager.py +0 -53
- biolib/pyppeteer/pyppeteer/errors.py +0 -48
- biolib/pyppeteer/pyppeteer/events.py +0 -63
- biolib/pyppeteer/pyppeteer/execution_context.py +0 -156
- biolib/pyppeteer/pyppeteer/frame/__init__.py +0 -299
- biolib/pyppeteer/pyppeteer/frame/frame_manager.py +0 -306
- biolib/pyppeteer/pyppeteer/helpers.py +0 -245
- biolib/pyppeteer/pyppeteer/input.py +0 -371
- biolib/pyppeteer/pyppeteer/jshandle.py +0 -598
- biolib/pyppeteer/pyppeteer/launcher.py +0 -683
- biolib/pyppeteer/pyppeteer/lifecycle_watcher.py +0 -169
- biolib/pyppeteer/pyppeteer/models/__init__.py +0 -103
- biolib/pyppeteer/pyppeteer/models/_protocol.py +0 -12460
- biolib/pyppeteer/pyppeteer/multimap.py +0 -82
- biolib/pyppeteer/pyppeteer/network_manager.py +0 -678
- biolib/pyppeteer/pyppeteer/options.py +0 -8
- biolib/pyppeteer/pyppeteer/page.py +0 -1728
- biolib/pyppeteer/pyppeteer/pipe_transport.py +0 -59
- biolib/pyppeteer/pyppeteer/target.py +0 -147
- biolib/pyppeteer/pyppeteer/task_queue.py +0 -24
- biolib/pyppeteer/pyppeteer/timeout_settings.py +0 -36
- biolib/pyppeteer/pyppeteer/tracing.py +0 -93
- biolib/pyppeteer/pyppeteer/us_keyboard_layout.py +0 -305
- biolib/pyppeteer/pyppeteer/util.py +0 -18
- biolib/pyppeteer/pyppeteer/websocket_transport.py +0 -47
- biolib/pyppeteer/pyppeteer/worker.py +0 -101
- biolib/pyppeteer/pyproject.toml +0 -97
- biolib/pyppeteer/spell.txt +0 -137
- biolib/pyppeteer/tox.ini +0 -72
- biolib/pyppeteer/utils/generate_protocol_types.py +0 -603
- biolib/start_cli.py +0 -7
- biolib/utils.py +0 -47
- biolib/validators/validate_app_version.py +0 -183
- biolib/validators/validate_argument.py +0 -134
- biolib/validators/validate_module.py +0 -323
- biolib/validators/validate_zip_file.py +0 -40
- biolib/validators/validator_utils.py +0 -103
- pybiolib-0.2.951.dist-info/LICENSE +0 -21
- pybiolib-0.2.951.dist-info/METADATA +0 -61
- pybiolib-0.2.951.dist-info/RECORD +0 -153
- pybiolib-0.2.951.dist-info/entry_points.txt +0 -3
- /LICENSE → /pybiolib-1.2.1890.dist-info/licenses/LICENSE +0 -0
|
@@ -0,0 +1,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,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,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,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,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
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
|