pybiolib 1.2.576__tar.gz → 1.2.582__tar.gz

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 (127) hide show
  1. {pybiolib-1.2.576 → pybiolib-1.2.582}/PKG-INFO +1 -1
  2. pybiolib-1.2.582/biolib/_session/session.py +39 -0
  3. pybiolib-1.2.582/biolib/api/client.py +207 -0
  4. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/app/app.py +7 -3
  5. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_api_client/biolib_app_api.py +4 -2
  6. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_api_client/biolib_job_api.py +42 -39
  7. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/jobs/job.py +17 -9
  8. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/sdk/__init__.py +5 -0
  9. {pybiolib-1.2.576 → pybiolib-1.2.582}/pyproject.toml +1 -1
  10. pybiolib-1.2.576/biolib/api/client.py +0 -118
  11. {pybiolib-1.2.576 → pybiolib-1.2.582}/LICENSE +0 -0
  12. {pybiolib-1.2.576 → pybiolib-1.2.582}/PYPI_README.md +0 -0
  13. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/__init__.py +0 -0
  14. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_data_record/data_record.py +0 -0
  15. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/__init__.py +0 -0
  16. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/data_record/__init__.py +0 -0
  17. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/data_record/data_record.py +0 -0
  18. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/data_record/push_data.py +0 -0
  19. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/data_record/remote_storage_endpoint.py +0 -0
  20. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/file_utils.py +0 -0
  21. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/fuse_mount/__init__.py +0 -0
  22. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/fuse_mount/experiment_fuse_mount.py +0 -0
  23. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/http_client.py +0 -0
  24. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/lfs/__init__.py +0 -0
  25. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/lfs/cache.py +0 -0
  26. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/libs/__init__.py +0 -0
  27. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/libs/fusepy/__init__.py +0 -0
  28. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/push_application.py +0 -0
  29. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/runtime.py +0 -0
  30. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/types/__init__.py +0 -0
  31. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/types/app.py +0 -0
  32. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/types/data_record.py +0 -0
  33. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/types/experiment.py +0 -0
  34. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/types/file_node.py +0 -0
  35. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/types/resource.py +0 -0
  36. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/types/resource_version.py +0 -0
  37. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/types/typing.py +0 -0
  38. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/utils/__init__.py +0 -0
  39. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_internal/utils/multinode.py +0 -0
  40. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/_runtime/runtime.py +0 -0
  41. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/api/__init__.py +0 -0
  42. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/app/__init__.py +0 -0
  43. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/app/search_apps.py +0 -0
  44. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_api_client/__init__.py +0 -0
  45. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_api_client/api_client.py +0 -0
  46. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_api_client/app_types.py +0 -0
  47. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_api_client/auth.py +0 -0
  48. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_api_client/common_types.py +0 -0
  49. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_api_client/job_types.py +0 -0
  50. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_api_client/lfs_types.py +0 -0
  51. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_api_client/user_state.py +0 -0
  52. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_binary_format/__init__.py +0 -0
  53. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_binary_format/base_bbf_package.py +0 -0
  54. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_binary_format/file_in_container.py +0 -0
  55. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_binary_format/module_input.py +0 -0
  56. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_binary_format/module_output_v2.py +0 -0
  57. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_binary_format/remote_endpoints.py +0 -0
  58. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_binary_format/remote_stream_seeker.py +0 -0
  59. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_binary_format/saved_job.py +0 -0
  60. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_binary_format/stdout_and_stderr.py +0 -0
  61. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_binary_format/system_exception.py +0 -0
  62. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_binary_format/system_status_update.py +0 -0
  63. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_binary_format/utils.py +0 -0
  64. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_docker_client/__init__.py +0 -0
  65. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_download_container.py +0 -0
  66. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_errors.py +0 -0
  67. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/biolib_logging.py +0 -0
  68. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/cli/__init__.py +0 -0
  69. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/cli/auth.py +0 -0
  70. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/cli/data_record.py +0 -0
  71. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/cli/download_container.py +0 -0
  72. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/cli/init.py +0 -0
  73. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/cli/lfs.py +0 -0
  74. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/cli/push.py +0 -0
  75. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/cli/run.py +0 -0
  76. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/cli/runtime.py +0 -0
  77. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/cli/start.py +0 -0
  78. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/.gitignore +0 -0
  79. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/__init__.py +0 -0
  80. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/cloud_utils/__init__.py +0 -0
  81. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/cloud_utils/cloud_utils.py +0 -0
  82. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/__init__.py +0 -0
  83. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/cache_state.py +0 -0
  84. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/cache_types.py +0 -0
  85. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/docker_image_cache.py +0 -0
  86. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/executors/__init__.py +0 -0
  87. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/executors/docker_executor.py +0 -0
  88. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/executors/docker_types.py +0 -0
  89. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/executors/tars/__init__.py +0 -0
  90. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/executors/types.py +0 -0
  91. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +0 -0
  92. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +0 -0
  93. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/job_storage.py +0 -0
  94. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/job_worker.py +0 -0
  95. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/large_file_system.py +0 -0
  96. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/mappings.py +0 -0
  97. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/utilization_reporter_thread.py +0 -0
  98. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/job_worker/utils.py +0 -0
  99. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/remote_host_proxy.py +0 -0
  100. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/socker_listener_thread.py +0 -0
  101. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/socket_sender_thread.py +0 -0
  102. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/utils.py +0 -0
  103. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/webserver/__init__.py +0 -0
  104. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/webserver/gunicorn_flask_application.py +0 -0
  105. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/webserver/webserver.py +0 -0
  106. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/webserver/webserver_types.py +0 -0
  107. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/webserver/webserver_utils.py +0 -0
  108. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/compute_node/webserver/worker_thread.py +0 -0
  109. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/experiments/__init__.py +0 -0
  110. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/experiments/experiment.py +0 -0
  111. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/jobs/__init__.py +0 -0
  112. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/jobs/job_result.py +0 -0
  113. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/jobs/types.py +0 -0
  114. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/py.typed +0 -0
  115. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/runtime/__init__.py +0 -0
  116. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/tables.py +0 -0
  117. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/templates/__init__.py +0 -0
  118. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/templates/example_app.py +0 -0
  119. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/typing_utils.py +0 -0
  120. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/user/__init__.py +0 -0
  121. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/user/sign_in.py +0 -0
  122. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/utils/__init__.py +0 -0
  123. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/utils/app_uri.py +0 -0
  124. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/utils/cache_state.py +0 -0
  125. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/utils/multipart_uploader.py +0 -0
  126. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/utils/seq_util.py +0 -0
  127. {pybiolib-1.2.576 → pybiolib-1.2.582}/biolib/utils/zip/remote_zip.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pybiolib
3
- Version: 1.2.576
3
+ Version: 1.2.582
4
4
  Summary: BioLib Python Client
5
5
  License: MIT
6
6
  Keywords: biolib
@@ -0,0 +1,39 @@
1
+ from biolib import utils
2
+ from biolib._internal.types import Optional
3
+ from biolib.api.client import ApiClient, ApiClientInitDict
4
+ from biolib.app import BioLibApp
5
+
6
+
7
+ class Session:
8
+ def __init__(self, _init_dict: ApiClientInitDict) -> None:
9
+ self._api = ApiClient(_init_dict=_init_dict)
10
+
11
+ @staticmethod
12
+ def get_session(refresh_token: str, base_url: Optional[str] = None) -> 'Session':
13
+ return Session(
14
+ _init_dict=ApiClientInitDict(
15
+ refresh_token=refresh_token,
16
+ base_url=base_url or utils.load_base_url_from_env(),
17
+ )
18
+ )
19
+
20
+ def load(self, uri: str) -> BioLibApp:
21
+ r"""Load a BioLib application by its URI or website URL.
22
+
23
+ Args:
24
+ uri (str): The URI or website URL of the application to load. Can be either:
25
+ - App URI (e.g., 'biolib/myapp:1.0.0')
26
+ - Website URL (e.g., 'https://biolib.com/biolib/myapp/')
27
+
28
+ Returns:
29
+ BioLibApp: The loaded application object
30
+
31
+ Example::
32
+
33
+ >>> # Load by URI
34
+ >>> app = biolib.load('biolib/myapp:1.0.0')
35
+ >>> # Load by website URL
36
+ >>> app = biolib.load('https://biolib.com/biolib/myapp/')
37
+ >>> result = app.cli('--help')
38
+ """
39
+ return BioLibApp(uri=uri, _api_client=self._api)
@@ -0,0 +1,207 @@
1
+ import base64
2
+ import binascii
3
+ import json
4
+ from datetime import datetime, timezone
5
+ from json.decoder import JSONDecodeError
6
+ from urllib.parse import urlencode, urljoin
7
+
8
+ import importlib_metadata
9
+
10
+ from biolib._internal.http_client import HttpClient, HttpResponse
11
+ from biolib._internal.types.typing import Any, Dict, Optional, TypedDict, Union, cast
12
+ from biolib.biolib_api_client import BiolibApiClient as DeprecatedApiClient
13
+ from biolib.biolib_errors import BioLibError
14
+ from biolib.biolib_logging import logger
15
+
16
+ OptionalHeaders = Union[
17
+ Optional[Dict[str, str]],
18
+ Optional[Dict[str, Union[str, None]]],
19
+ ]
20
+
21
+
22
+ def _get_biolib_package_version() -> str:
23
+ # try fetching version, if it fails (usually when in dev), add default
24
+ try:
25
+ return cast(str, importlib_metadata.version('pybiolib'))
26
+ except importlib_metadata.PackageNotFoundError:
27
+ return '0.0.0'
28
+
29
+
30
+ class ApiClientInitDict(TypedDict):
31
+ refresh_token: str
32
+ base_url: str
33
+
34
+
35
+ class JwtDecodeError(Exception):
36
+ pass
37
+
38
+
39
+ class ApiClient(HttpClient):
40
+ _biolib_package_version: str = _get_biolib_package_version()
41
+
42
+ def __init__(self, _init_dict: Optional[ApiClientInitDict] = None) -> None:
43
+ self._access_token: Optional[str] = None
44
+ self._refresh_token: Optional[str] = _init_dict['refresh_token'] if _init_dict else None
45
+ self._base_url: Optional[str] = _init_dict['base_url'] if _init_dict else None
46
+
47
+ def get(
48
+ self,
49
+ path: str,
50
+ params: Optional[Dict[str, Union[str, int]]] = None,
51
+ headers: OptionalHeaders = None,
52
+ authenticate: bool = True,
53
+ retries: int = 10,
54
+ ) -> HttpResponse:
55
+ return self.request(
56
+ headers=self._get_headers(opt_headers=headers, authenticate=authenticate),
57
+ method='GET',
58
+ retries=retries,
59
+ url=self._get_absolute_url(path=path, query_params=params),
60
+ )
61
+
62
+ def post(
63
+ self,
64
+ path: str,
65
+ data: Optional[Union[Dict, bytes]] = None,
66
+ headers: OptionalHeaders = None,
67
+ authenticate: bool = True,
68
+ retries: int = 50, # TODO: reduce this back to 5 when timeout errors have been solved
69
+ ) -> HttpResponse:
70
+ return self.request(
71
+ data=data,
72
+ headers=self._get_headers(opt_headers=headers, authenticate=authenticate),
73
+ method='POST',
74
+ retries=retries,
75
+ url=self._get_absolute_url(path=path, query_params=None),
76
+ )
77
+
78
+ def patch(
79
+ self,
80
+ path: str,
81
+ data: Dict,
82
+ headers: OptionalHeaders = None,
83
+ retries: int = 5,
84
+ params: Optional[Dict[str, Union[str, int]]] = None,
85
+ ) -> HttpResponse:
86
+ return self.request(
87
+ data=data,
88
+ headers=self._get_headers(opt_headers=headers, authenticate=True),
89
+ method='PATCH',
90
+ retries=retries,
91
+ url=self._get_absolute_url(path=path, query_params=params),
92
+ )
93
+
94
+ def delete(
95
+ self,
96
+ path: str,
97
+ headers: OptionalHeaders = None,
98
+ retries: int = 0,
99
+ ) -> HttpResponse:
100
+ return self.request(
101
+ headers=self._get_headers(opt_headers=headers, authenticate=True),
102
+ method='DELETE',
103
+ retries=retries,
104
+ url=self._get_absolute_url(path=path, query_params=None),
105
+ )
106
+
107
+ def _get_headers(self, opt_headers: OptionalHeaders, authenticate: bool) -> Dict[str, str]:
108
+ # Only keep header keys with a value
109
+ headers: Dict[str, str] = {key: value for key, value in (opt_headers or {}).items() if value}
110
+
111
+ if authenticate:
112
+ if self._refresh_token:
113
+ headers['Authorization'] = f'Bearer {self._get_access_token()}'
114
+ else:
115
+ # TODO: Remove this block when deprecated api client is removed
116
+ deprecated_api_client = DeprecatedApiClient.get()
117
+ if deprecated_api_client.is_signed_in:
118
+ deprecated_api_client.refresh_access_token()
119
+
120
+ if deprecated_api_client.resource_deploy_key:
121
+ headers['Authorization'] = f'Token {deprecated_api_client.resource_deploy_key}'
122
+ else:
123
+ # Adding access_token outside is_signed_in check as job_worker.py currently sets access_token
124
+ # without setting refresh_token
125
+ access_token = deprecated_api_client.access_token
126
+ if access_token:
127
+ headers['Authorization'] = f'Bearer {access_token}'
128
+
129
+ headers['client-type'] = 'biolib-python'
130
+ headers['client-version'] = ApiClient._biolib_package_version
131
+
132
+ return headers
133
+
134
+ def _get_absolute_url(self, path: str, query_params: Optional[Dict[str, Union[str, int]]]) -> str:
135
+ deprecated_api_client = DeprecatedApiClient.get()
136
+ base_url = self._base_url or deprecated_api_client.base_url
137
+ base_api_url = urljoin(base_url, '/api/')
138
+ url = urljoin(base_api_url, path.strip('/') + '/')
139
+ if query_params:
140
+ url = url + '?' + urlencode(query_params)
141
+
142
+ return url
143
+
144
+ def _get_access_token(self) -> str:
145
+ if self._access_token:
146
+ decoded_token = self._decode_jwt_without_checking_signature(self._access_token)
147
+ if datetime.now(tz=timezone.utc).timestamp() < decoded_token['payload']['exp'] - 60: # 60 second buffer
148
+ # Token has not expired yet
149
+ return self._access_token
150
+
151
+ # TODO: Implement nicer error handling
152
+ try:
153
+ response = HttpClient.request(
154
+ method='POST',
155
+ url=f'{self._base_url}/api/user/token/refresh/',
156
+ data={'refresh': self._refresh_token},
157
+ )
158
+ except Exception as exception:
159
+ logger.error('Sign in with refresh token failed')
160
+ raise exception
161
+
162
+ try:
163
+ response_dict = response.json()
164
+ except JSONDecodeError as error:
165
+ logger.error('Could not decode response from server as JSON:')
166
+ raise BioLibError(response.text) from error
167
+
168
+ self._access_token = cast(str, response_dict['access'])
169
+ return self._access_token
170
+
171
+ @staticmethod
172
+ def _decode_jwt_without_checking_signature(jwt: str) -> Dict[str, Any]:
173
+ jwt_bytes = jwt.encode('utf-8')
174
+
175
+ try:
176
+ signing_input, _ = jwt_bytes.rsplit(b'.', 1)
177
+ header_segment, payload_segment = signing_input.split(b'.', 1)
178
+ except ValueError as error:
179
+ raise JwtDecodeError('Not enough segments') from error
180
+
181
+ try:
182
+ header_data = base64.urlsafe_b64decode(header_segment)
183
+ except (TypeError, binascii.Error) as error:
184
+ raise JwtDecodeError('Invalid header padding') from error
185
+
186
+ try:
187
+ header = json.loads(header_data)
188
+ except ValueError as error:
189
+ raise JwtDecodeError(f'Invalid header string: {error}') from error
190
+
191
+ if not isinstance(header, dict):
192
+ raise JwtDecodeError('Invalid header string: must be a json object')
193
+
194
+ try:
195
+ payload_data = base64.urlsafe_b64decode(payload_segment)
196
+ except (TypeError, binascii.Error) as error:
197
+ raise JwtDecodeError('Invalid payload padding') from error
198
+
199
+ try:
200
+ payload = json.loads(payload_data)
201
+ except ValueError as error:
202
+ raise JwtDecodeError(f'Invalid payload string: {error}') from error
203
+
204
+ if not isinstance(header, dict):
205
+ raise JwtDecodeError('Invalid payload string: must be a json object')
206
+
207
+ return dict(header=header, payload=payload)
@@ -6,6 +6,7 @@ import string
6
6
  from pathlib import Path
7
7
 
8
8
  from biolib import utils
9
+ from biolib.api.client import ApiClient
9
10
  from biolib.biolib_api_client import JobState
10
11
  from biolib.biolib_api_client.app_types import App, AppVersion
11
12
  from biolib.biolib_api_client.biolib_app_api import BiolibAppApi
@@ -16,13 +17,15 @@ from biolib.biolib_logging import logger
16
17
  from biolib.compute_node.job_worker.job_worker import JobWorker
17
18
  from biolib.experiments.experiment import Experiment
18
19
  from biolib.jobs import Job
19
- from biolib.typing_utils import Optional, Dict
20
+ from biolib.typing_utils import Dict, Optional
20
21
  from biolib.utils.app_uri import parse_app_uri
21
22
 
22
23
 
23
24
  class BioLibApp:
24
- def __init__(self, uri: str):
25
- app_response = BiolibAppApi.get_by_uri(uri)
25
+ def __init__(self, uri: str, _api_client: Optional[ApiClient] = None):
26
+ self._api_client: Optional[ApiClient] = _api_client
27
+
28
+ app_response = BiolibAppApi.get_by_uri(uri=uri, api_client=self._api_client)
26
29
  self._app: App = app_response['app']
27
30
  self._app_uri = app_response['app_uri']
28
31
  self._app_version: AppVersion = app_response['app_version']
@@ -98,6 +101,7 @@ class BioLibApp:
98
101
  timeout=timeout,
99
102
  requested_machine_count=machine_count,
100
103
  temporary_client_secrets=temporary_client_secrets,
104
+ api_client=self._api_client,
101
105
  )
102
106
  logger.info(f'View the result in your browser at: {utils.BIOLIB_BASE_URL}/results/{job.id}/')
103
107
  if blocking:
@@ -8,6 +8,7 @@ import urllib.parse
8
8
  import biolib.api
9
9
  from biolib import biolib_errors
10
10
  from biolib._internal.http_client import HttpError
11
+ from biolib.api.client import ApiClient
11
12
  from biolib.biolib_api_client import AppGetResponse
12
13
  from biolib.biolib_logging import logger
13
14
  from biolib.typing_utils import Optional
@@ -84,10 +85,11 @@ def _get_app_uri_from_str(input_str: str) -> str:
84
85
 
85
86
  class BiolibAppApi:
86
87
  @staticmethod
87
- def get_by_uri(uri: str) -> AppGetResponse:
88
+ def get_by_uri(uri: str, api_client: Optional[ApiClient] = None) -> AppGetResponse:
88
89
  uri = _get_app_uri_from_str(uri)
90
+ api = api_client or biolib.api.client
89
91
  try:
90
- response = biolib.api.client.get(path='/app/', params={'uri': uri})
92
+ response = api.get(path='/app/', params={'uri': uri})
91
93
  app_response: AppGetResponse = response.json()
92
94
  return app_response
93
95
 
@@ -5,6 +5,7 @@ import biolib.api
5
5
 
6
6
  from biolib import utils
7
7
  from biolib._internal.http_client import HttpError
8
+ from biolib.api.client import ApiClient
8
9
  from biolib.biolib_api_client import CloudJob, JobState
9
10
  from biolib.biolib_errors import JobResultPermissionError, JobResultError, JobResultNotFound, StorageDownloadFailed
10
11
  from biolib.biolib_logging import logger
@@ -35,19 +36,19 @@ def _get_user_info() -> Optional[str]:
35
36
 
36
37
 
37
38
  class BiolibJobApi:
38
-
39
39
  @staticmethod
40
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,
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,
51
52
  ):
52
53
  data = {
53
54
  'app_version_id': app_version_id,
@@ -58,22 +59,16 @@ class BiolibJobApi:
58
59
  }
59
60
 
60
61
  if app_resource_name_prefix:
61
- data.update({
62
- 'app_resource_name_prefix': app_resource_name_prefix
63
- })
62
+ data.update({'app_resource_name_prefix': app_resource_name_prefix})
64
63
 
65
64
  if override_command:
66
- data.update({
67
- 'arguments_override_command': override_command
68
- })
65
+ data.update({'arguments_override_command': override_command})
69
66
 
70
67
  if caller_job:
71
68
  data['caller_job'] = caller_job
72
69
 
73
70
  if machine:
74
- data.update({
75
- 'requested_machine': machine
76
- })
71
+ data.update({'requested_machine': machine})
77
72
 
78
73
  if requested_machine_count:
79
74
  data.update({'requested_machine_count': requested_machine_count})
@@ -87,7 +82,8 @@ class BiolibJobApi:
87
82
  if temporary_client_secrets:
88
83
  data['temporary_client_secrets'] = temporary_client_secrets
89
84
 
90
- response = biolib.api.client.post(path='/jobs/', data=data)
85
+ api = api_client or biolib.api.client
86
+ response = api.post(path='/jobs/', data=data)
91
87
 
92
88
  return response.json()
93
89
 
@@ -99,20 +95,25 @@ class BiolibJobApi:
99
95
  logger.error(f'Failed to update job "{job_uuid}" to state "{state.value}" due to {error}')
100
96
 
101
97
  @staticmethod
102
- def create_cloud_job(job_id: str, result_name_prefix: Optional[str]) -> CloudJob:
98
+ def create_cloud_job(
99
+ job_id: str,
100
+ result_name_prefix: Optional[str],
101
+ api_client: Optional[ApiClient] = None,
102
+ ) -> CloudJob:
103
103
  data = {'job_id': job_id}
104
104
  if result_name_prefix:
105
105
  data['result_name_prefix'] = result_name_prefix
106
106
 
107
- response = biolib.api.client.post(path='/jobs/cloud/', data=data)
107
+ api = api_client or biolib.api.client
108
+ response = api.post(path='/jobs/cloud/', data=data)
108
109
  cloud_job: CloudJob = response.json()
109
110
  return cloud_job
110
111
 
111
112
  @staticmethod
112
113
  def get_job_storage_download_url(
113
- job_uuid: str,
114
- job_auth_token: str,
115
- storage_type: Literal['input', 'results'],
114
+ job_uuid: str,
115
+ job_auth_token: str,
116
+ storage_type: Literal['input', 'results'],
116
117
  ) -> str:
117
118
  try:
118
119
  response = biolib.api.client.get(
@@ -154,19 +155,21 @@ class BiolibJobApi:
154
155
 
155
156
  @staticmethod
156
157
  def create_job_with_data(
157
- app_version_uuid: str,
158
- app_resource_name_prefix: Optional[str],
159
- module_input_serialized: bytes,
160
- arguments_override_command: bool,
161
- experiment_uuid: Optional[str],
162
- requested_machine: Optional[str],
163
- result_name_prefix: Optional[str],
164
- caller_job_uuid: Optional[str] = None,
165
- requested_timeout_seconds: Optional[int] = None,
166
- notify: bool = False,
167
- requested_machine_count: Optional[int] = None,
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,
168
170
  ) -> Dict:
169
- job_dict: Dict = biolib.api.client.post(
171
+ api = api_client or biolib.api.client
172
+ job_dict: Dict = api.post(
170
173
  path='/jobs/create_job_with_data/',
171
174
  data=module_input_serialized,
172
175
  headers={
@@ -184,6 +187,6 @@ class BiolibJobApi:
184
187
  'result-name-prefix': result_name_prefix,
185
188
  'requested-timeout-seconds': str(requested_timeout_seconds) if requested_timeout_seconds else None,
186
189
  'notify': 'true' if notify else 'false',
187
- }
190
+ },
188
191
  ).json()
189
192
  return job_dict
@@ -6,9 +6,11 @@ from datetime import datetime, timedelta
6
6
  from pathlib import Path
7
7
  from urllib.parse import urlparse
8
8
 
9
- from biolib import api, utils
9
+ import biolib.api.client
10
+ from biolib import utils
10
11
  from biolib._internal.http_client import HttpClient
11
12
  from biolib._internal.utils import open_browser_window_from_notebook
13
+ from biolib.api.client import ApiClient
12
14
  from biolib.biolib_api_client import BiolibApiClient, CreatedJobDict
13
15
  from biolib.biolib_api_client.biolib_app_api import BiolibAppApi
14
16
  from biolib.biolib_api_client.biolib_job_api import BiolibJobApi
@@ -39,7 +41,9 @@ class Job:
39
41
  }
40
42
  )
41
43
 
42
- def __init__(self, job_dict: JobDict):
44
+ def __init__(self, job_dict: JobDict, _api_client: Optional[ApiClient] = None):
45
+ self._api_client: Optional[ApiClient] = _api_client
46
+
43
47
  self._uuid: str = job_dict['uuid']
44
48
  self._auth_token: str = job_dict['auth_token']
45
49
 
@@ -208,7 +212,7 @@ class Job:
208
212
 
209
213
  def cancel(self) -> None:
210
214
  try:
211
- api.client.patch(
215
+ biolib.api.client.patch(
212
216
  path=f'/jobs/{self._uuid}/',
213
217
  headers={'Job-Auth-Token': self._auth_token} if self._auth_token else None,
214
218
  data={'state': 'cancelled'},
@@ -219,7 +223,7 @@ class Job:
219
223
 
220
224
  def rename(self, name: str) -> None:
221
225
  try:
222
- api.client.patch(
226
+ biolib.api.client.patch(
223
227
  path=f'/jobs/{self._uuid}/main_result/',
224
228
  headers={'Job-Auth-Token': self._auth_token} if self._auth_token else None,
225
229
  data={'result_name_prefix': name},
@@ -325,20 +329,21 @@ class Job:
325
329
  params['state'] = status
326
330
 
327
331
  api_path = '/jobs/'
328
- response = api.client.get(api_path, params=params).json()
332
+ response = biolib.api.client.get(api_path, params=params).json()
329
333
  jobs = [job_dict for job_dict in response['results']]
330
334
 
331
335
  for page_number in range(2, response['page_count'] + 1):
332
336
  if len(jobs) >= count:
333
337
  break
334
- page_response = api.client.get(path=api_path, params=dict(**params, page=page_number)).json()
338
+ page_response = biolib.api.client.get(path=api_path, params=dict(**params, page=page_number)).json()
335
339
  jobs.extend([job_dict for job_dict in page_response['results']])
336
340
 
337
341
  return jobs[:count]
338
342
 
339
343
  @staticmethod
340
- def _get_job_dict(uuid: str, auth_token: Optional[str] = None) -> JobDict:
341
- job_dict: JobDict = api.client.get(
344
+ def _get_job_dict(uuid: str, auth_token: Optional[str] = None, api_client: Optional[ApiClient] = None) -> JobDict:
345
+ api = api_client or biolib.api.client
346
+ job_dict: JobDict = api.get(
342
347
  path=f'/jobs/{uuid}/',
343
348
  headers={'Job-Auth-Token': auth_token} if auth_token else None,
344
349
  ).json()
@@ -506,6 +511,7 @@ class Job:
506
511
  notify: bool = False,
507
512
  requested_machine_count: Optional[int] = None,
508
513
  temporary_client_secrets: Optional[Dict[str, str]] = None,
514
+ api_client: Optional[ApiClient] = None,
509
515
  ) -> 'Job':
510
516
  if len(module_input_serialized) < 500_000 and temporary_client_secrets is None:
511
517
  _job_dict = BiolibJobApi.create_job_with_data(
@@ -519,6 +525,7 @@ class Job:
519
525
  requested_timeout_seconds=timeout,
520
526
  result_name_prefix=result_prefix,
521
527
  requested_machine_count=requested_machine_count,
528
+ api_client=api_client,
522
529
  )
523
530
  return Job(cast(JobDict, _job_dict))
524
531
 
@@ -532,8 +539,9 @@ class Job:
532
539
  timeout=timeout,
533
540
  requested_machine_count=requested_machine_count,
534
541
  temporary_client_secrets=temporary_client_secrets,
542
+ api_client=api_client,
535
543
  )
536
544
  JobStorage.upload_module_input(job=job_dict, module_input_serialized=module_input_serialized)
537
545
  cloud_job = BiolibJobApi.create_cloud_job(job_id=job_dict['public_id'], result_name_prefix=result_prefix)
538
546
  logger.debug(f"Cloud: Job created with id {cloud_job['public_id']}")
539
- return Job(cast(JobDict, job_dict))
547
+ return Job(cast(JobDict, job_dict), _api_client=api_client)
@@ -5,12 +5,17 @@ from biolib._data_record.data_record import DataRecord as _DataRecord
5
5
  from biolib._internal.push_application import push_application as _push_application
6
6
  from biolib._internal.push_application import set_app_version_as_active as _set_app_version_as_active
7
7
  from biolib._runtime.runtime import Runtime as _Runtime
8
+ from biolib._session.session import Session as _Session
8
9
  from biolib.app import BioLibApp as _BioLibApp
9
10
 
10
11
  # Classes to expose as public API
11
12
  Runtime = _Runtime
12
13
 
13
14
 
15
+ def get_session(refresh_token: str, base_url: Optional[str] = None) -> _Session:
16
+ return _Session.get_session(refresh_token=refresh_token, base_url=base_url)
17
+
18
+
14
19
  def push_app_version(uri: str, path: str) -> _BioLibApp:
15
20
  push_data = _push_application(
16
21
  app_uri=uri,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pybiolib"
3
- version = "1.2.576"
3
+ version = "1.2.582"
4
4
  description = "BioLib Python Client"
5
5
  readme = "PYPI_README.md"
6
6
  license = "MIT"
@@ -1,118 +0,0 @@
1
- from urllib.parse import urlencode, urljoin
2
-
3
- import importlib_metadata
4
-
5
- from biolib._internal.http_client import HttpClient, HttpResponse
6
- from biolib.biolib_api_client import BiolibApiClient as DeprecatedApiClient
7
- from biolib.typing_utils import Dict, Optional, Union, cast
8
-
9
- OptionalHeaders = Union[
10
- Optional[Dict[str, str]],
11
- Optional[Dict[str, Union[str, None]]],
12
- ]
13
-
14
-
15
- def _get_biolib_package_version() -> str:
16
- # try fetching version, if it fails (usually when in dev), add default
17
- try:
18
- return cast(str, importlib_metadata.version('pybiolib'))
19
- except importlib_metadata.PackageNotFoundError:
20
- return '0.0.0'
21
-
22
-
23
- class ApiClient(HttpClient):
24
- _biolib_package_version: str = _get_biolib_package_version()
25
-
26
- def get(
27
- self,
28
- path: str,
29
- params: Optional[Dict[str, Union[str, int]]] = None,
30
- headers: OptionalHeaders = None,
31
- authenticate: bool = True,
32
- retries: int = 10,
33
- ) -> HttpResponse:
34
- return self.request(
35
- headers=self._get_headers(opt_headers=headers, authenticate=authenticate),
36
- method='GET',
37
- retries=retries,
38
- url=self._get_absolute_url(path=path, query_params=params),
39
- )
40
-
41
- def post(
42
- self,
43
- path: str,
44
- data: Optional[Union[Dict, bytes]] = None,
45
- headers: OptionalHeaders = None,
46
- authenticate: bool = True,
47
- retries: int = 50, # TODO: reduce this back to 5 when timeout errors have been solved
48
- ) -> HttpResponse:
49
- return self.request(
50
- data=data,
51
- headers=self._get_headers(opt_headers=headers, authenticate=authenticate),
52
- method='POST',
53
- retries=retries,
54
- url=self._get_absolute_url(path=path, query_params=None),
55
- )
56
-
57
- def patch(
58
- self,
59
- path: str,
60
- data: Dict,
61
- headers: OptionalHeaders = None,
62
- retries: int = 5,
63
- params: Optional[Dict[str, Union[str, int]]] = None,
64
- ) -> HttpResponse:
65
- return self.request(
66
- data=data,
67
- headers=self._get_headers(opt_headers=headers, authenticate=True),
68
- method='PATCH',
69
- retries=retries,
70
- url=self._get_absolute_url(path=path, query_params=params),
71
- )
72
-
73
- def delete(
74
- self,
75
- path: str,
76
- headers: OptionalHeaders = None,
77
- retries: int = 0,
78
- ) -> HttpResponse:
79
- return self.request(
80
- headers=self._get_headers(opt_headers=headers, authenticate=True),
81
- method='DELETE',
82
- retries=retries,
83
- url=self._get_absolute_url(path=path, query_params=None),
84
- )
85
-
86
- @staticmethod
87
- def _get_headers(opt_headers: OptionalHeaders, authenticate: bool) -> Dict[str, str]:
88
- # Only keep header keys with a value
89
- headers: Dict[str, str] = {key: value for key, value in (opt_headers or {}).items() if value}
90
-
91
- if authenticate:
92
- deprecated_api_client = DeprecatedApiClient.get()
93
- if deprecated_api_client.is_signed_in:
94
- deprecated_api_client.refresh_access_token()
95
-
96
- if deprecated_api_client.resource_deploy_key:
97
- headers['Authorization'] = f'Token {deprecated_api_client.resource_deploy_key}'
98
- else:
99
- # Adding access_token outside is_signed_in check as job_worker.py currently sets access_token
100
- # without setting refresh_token
101
- access_token = deprecated_api_client.access_token
102
- if access_token:
103
- headers['Authorization'] = f'Bearer {access_token}'
104
-
105
- headers['client-type'] = 'biolib-python'
106
- headers['client-version'] = ApiClient._biolib_package_version
107
-
108
- return headers
109
-
110
- @staticmethod
111
- def _get_absolute_url(path: str, query_params: Optional[Dict[str, Union[str, int]]]) -> str:
112
- deprecated_api_client = DeprecatedApiClient.get()
113
- base_api_url = urljoin(deprecated_api_client.base_url, '/api/')
114
- url = urljoin(base_api_url, path.strip('/') + '/')
115
- if query_params:
116
- url = url + '?' + urlencode(query_params)
117
-
118
- return url
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes