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,640 @@
1
+ # pylint: skip-file
2
+ """
3
+ Code from:
4
+ https://github.com/gtsystem/python-remotezip
5
+ &
6
+ https://github.com/uktrade/stream-unzip
7
+ """
8
+
9
+ import io
10
+ import zipfile
11
+
12
+ from functools import partial
13
+ from struct import Struct
14
+ import zlib
15
+
16
+ __all__ = ['RemoteIOError', 'RemoteZip']
17
+
18
+ from biolib._internal.http_client import HttpClient
19
+
20
+
21
+ class RemoteZipError(Exception):
22
+ pass
23
+
24
+ class OutOfBound(RemoteZipError):
25
+ pass
26
+
27
+
28
+ class RemoteIOError(RemoteZipError):
29
+ pass
30
+
31
+
32
+ class RangeNotSupported(RemoteZipError):
33
+ pass
34
+
35
+
36
+ class PartialBuffer:
37
+ def __init__(self, buffer, offset, size, stream):
38
+ self.buffer = buffer if stream else io.BytesIO(buffer.read())
39
+ self.offset = offset
40
+ self.size = size
41
+ self.position = offset
42
+ self.stream = stream
43
+
44
+ def __repr__(self):
45
+ return "<PartialBuffer off=%s size=%s stream=%s>" % (self.offset, self.size, self.stream)
46
+
47
+ def read(self, size=0):
48
+ if size == 0:
49
+ size = self.offset + self.size - self.position
50
+
51
+ content = self.buffer.read(size)
52
+ self.position = self.offset + self.buffer.tell()
53
+ return content
54
+
55
+ def close(self):
56
+ if not self.buffer.closed:
57
+ self.buffer.close()
58
+ if hasattr(self.buffer, 'release_conn'):
59
+ self.buffer.release_conn()
60
+
61
+ def seek(self, offset, whence):
62
+ if whence == 2:
63
+ self.position = self.size + self.offset + offset
64
+ elif whence == 0:
65
+ self.position = offset
66
+ else:
67
+ self.position += offset
68
+
69
+ relative_position = self.position - self.offset
70
+
71
+ if relative_position < 0 or relative_position >= self.size:
72
+ raise OutOfBound("Position out of buffer bound")
73
+
74
+ if self.stream:
75
+ buff_pos = self.buffer.tell()
76
+ if relative_position < buff_pos:
77
+ raise OutOfBound("Negative seek not supported")
78
+
79
+ skip_bytes = relative_position - buff_pos
80
+ if skip_bytes == 0:
81
+ return self.position
82
+ self.buffer.read(skip_bytes)
83
+ else:
84
+ self.buffer.seek(relative_position)
85
+
86
+ return self.position
87
+
88
+
89
+ class RemoteIO(io.IOBase):
90
+ def __init__(self, fetch_fun, initial_buffer_size=64*1024):
91
+ self.fetch_fun = fetch_fun
92
+ self.initial_buffer_size = initial_buffer_size
93
+ self.buffer = None
94
+ self.file_size = None
95
+ self.position = None
96
+ self._seek_succeeded = False
97
+ self.member_pos2size = None
98
+ self._last_member_pos = None
99
+
100
+ def set_pos2size(self, pos2size):
101
+ self.member_pos2size = pos2size
102
+
103
+ def read(self, size=0):
104
+ if size == 0:
105
+ size = self.file_size - self.buffer.position
106
+
107
+ if not self._seek_succeeded:
108
+ if self.member_pos2size is None:
109
+ fetch_size = size
110
+ stream = False
111
+ else:
112
+ try:
113
+ fetch_size = self.member_pos2size[self.buffer.position]
114
+ self._last_member_pos = self.buffer.position
115
+ except KeyError:
116
+ if self._last_member_pos and self._last_member_pos < self.buffer.position:
117
+ fetch_size = self.member_pos2size[self._last_member_pos]
118
+ fetch_size -= (self.buffer.position - self._last_member_pos)
119
+ else:
120
+ raise OutOfBound("Attempt to seek outside boundary of current zip member")
121
+ stream = True
122
+
123
+ self._seek_succeeded = True
124
+ self.buffer.close()
125
+ self.buffer = self.fetch_fun((self.buffer.position, self.buffer.position + fetch_size -1), stream=stream)
126
+
127
+ return self.buffer.read(size)
128
+
129
+ def seekable(self):
130
+ return True
131
+
132
+ def seek(self, offset, whence=0):
133
+ if whence == 2 and self.file_size is None:
134
+ size = self.initial_buffer_size
135
+ self.buffer = self.fetch_fun((-size, None), stream=False)
136
+ self.file_size = self.buffer.size + self.buffer.position
137
+
138
+ try:
139
+ pos = self.buffer.seek(offset, whence)
140
+ self._seek_succeeded = True
141
+ return pos
142
+ except OutOfBound:
143
+ self._seek_succeeded = False
144
+ return self.buffer.position # we ignore the issue here, we will check if buffer is fine during read
145
+
146
+ def tell(self):
147
+ return self.buffer.position
148
+
149
+ def close(self):
150
+ if self.buffer:
151
+ self.buffer.close()
152
+ self.buffer = None
153
+
154
+
155
+ class RemoteZip(zipfile.ZipFile):
156
+ def __init__(self, url, initial_buffer_size=64*1024):
157
+ self.url = url
158
+
159
+ rio = RemoteIO(self.fetch_fun, initial_buffer_size)
160
+ super(RemoteZip, self).__init__(rio)
161
+ rio.set_pos2size(self.get_position2size())
162
+
163
+ def get_central_directory(self):
164
+ return {
165
+ file.filename: {
166
+ attribute: getattr(file, attribute) for attribute in zipfile.ZipInfo.__slots__
167
+ } for file in self.infolist()
168
+ }
169
+
170
+ def get_position2size(self):
171
+ ilist = self.infolist()
172
+ if len(ilist) == 0:
173
+ return {}
174
+
175
+ position2size = {ilist[-1].header_offset: self.start_dir - ilist[-1].header_offset}
176
+ for i in range(len(ilist) - 1):
177
+ m1, m2 = ilist[i: i+2]
178
+ position2size[m1.header_offset] = m2.header_offset - m1.header_offset
179
+
180
+ return position2size
181
+
182
+ @staticmethod
183
+ def make_buffer(io_buffer, content_range_header, stream):
184
+ range_min, range_max = content_range_header.split("/")[0][6:].split("-")
185
+ range_min, range_max = int(range_min), int(range_max)
186
+ return PartialBuffer(io_buffer, range_min, range_max - range_min + 1, stream)
187
+
188
+ @staticmethod
189
+ def make_header(range_min, range_max):
190
+ if range_max is None:
191
+ return "bytes=%s%s" % (range_min, '' if range_min < 0 else '-')
192
+ return "bytes=%s-%s" % (range_min, range_max)
193
+
194
+ def fetch_fun(self, data_range, stream=False):
195
+ range_header = self.make_header(*data_range)
196
+ try:
197
+ response = HttpClient.request(url=self.url, headers={'Range': range_header})
198
+ if 'Content-Range' not in response.headers:
199
+ raise RangeNotSupported("The server doesn't support range requests")
200
+
201
+ return self.make_buffer(io.BytesIO(response.content), response.headers['Content-Range'], stream=False)
202
+ except IOError as e:
203
+ raise RemoteIOError(str(e))
204
+
205
+ def stream_unzip(self, zipfile_chunks, password=None, chunk_size=65536):
206
+ local_file_header_signature = b'\x50\x4b\x03\x04'
207
+ local_file_header_struct = Struct('<H2sHHHIIIHH')
208
+ zip64_version = 45
209
+ zip64_compressed_size = 4294967295
210
+ zip64_size_signature = b'\x01\x00'
211
+ aes_extra_signature = b'\x01\x99'
212
+ central_directory_signature = b'\x50\x4b\x01\x02'
213
+ central_directory_info = self.get_central_directory()
214
+
215
+ def next_or_truncated_error(it):
216
+ try:
217
+ return next(it)
218
+ except StopIteration:
219
+ raise TruncatedDataError from None
220
+
221
+ def get_byte_readers(iterable):
222
+ # Return functions to return/"replace" bytes from/to the iterable
223
+ # - _yield_all: yields chunks as they come up (often for a "body")
224
+ # - _get_num: returns a single `bytes` of a given length
225
+ # - _return_unused: puts "unused" bytes "back", to be retrieved by a yield/get call
226
+
227
+ chunk = b''
228
+ offset = 0
229
+ it = iter(iterable)
230
+
231
+ def _yield_all():
232
+ nonlocal chunk, offset
233
+
234
+ while True:
235
+ if offset == len(chunk):
236
+ try:
237
+ chunk = next(it)
238
+ except StopIteration:
239
+ break
240
+ else:
241
+ offset = 0
242
+ to_yield = min(len(chunk) - offset, chunk_size)
243
+ offset = offset + to_yield
244
+ yield chunk[offset - to_yield:offset]
245
+
246
+ def _yield_num(num):
247
+ nonlocal chunk, offset
248
+
249
+ while num:
250
+ if offset == len(chunk):
251
+ chunk = next_or_truncated_error(it)
252
+ offset = 0
253
+ to_yield = min(num, len(chunk) - offset, chunk_size)
254
+ offset = offset + to_yield
255
+ num -= to_yield
256
+ yield chunk[offset - to_yield:offset]
257
+
258
+ def _get_num(num):
259
+ return b''.join(_yield_num(num))
260
+
261
+ def _return_unused(num_unused):
262
+ nonlocal offset
263
+ offset -= num_unused
264
+
265
+ return _yield_all, _get_num, _return_unused
266
+
267
+ def get_decompressor_none(num_bytes):
268
+ num_decompressed = 0
269
+ num_unused = 0
270
+
271
+ def _decompress(compressed_chunk):
272
+ nonlocal num_decompressed, num_unused
273
+ to_yield = min(len(compressed_chunk), num_bytes - num_decompressed)
274
+ num_decompressed += to_yield
275
+ num_unused = len(compressed_chunk) - to_yield
276
+ yield compressed_chunk[:to_yield]
277
+
278
+ def _is_done():
279
+ return num_decompressed == num_bytes
280
+
281
+ def _num_unused():
282
+ return num_unused
283
+
284
+ return _decompress, _is_done, _num_unused
285
+
286
+ def get_decompressor_deflate():
287
+ dobj = zlib.decompressobj(wbits=-zlib.MAX_WBITS)
288
+
289
+ def _decompress_single(compressed_chunk):
290
+ try:
291
+ return dobj.decompress(compressed_chunk, chunk_size)
292
+ except zlib.error as e:
293
+ raise DeflateError() from e
294
+
295
+ def _decompress(compressed_chunk):
296
+ uncompressed_chunk = _decompress_single(compressed_chunk)
297
+ if uncompressed_chunk:
298
+ yield uncompressed_chunk
299
+
300
+ while dobj.unconsumed_tail and not dobj.eof:
301
+ uncompressed_chunk = _decompress_single(dobj.unconsumed_tail)
302
+ if uncompressed_chunk:
303
+ yield uncompressed_chunk
304
+
305
+ def _is_done():
306
+ return dobj.eof
307
+
308
+ def _num_unused():
309
+ return len(dobj.unused_data)
310
+
311
+ return _decompress, _is_done, _num_unused
312
+
313
+ def get_decompressor_deflate64():
314
+ uncompressed_chunks, is_done, num_bytes_unconsumed = stream_inflate64()
315
+
316
+ def _decompress(compressed_chunk):
317
+ yield from uncompressed_chunks((compressed_chunk,))
318
+
319
+ return _decompress, is_done, num_bytes_unconsumed
320
+
321
+ def yield_file(yield_all, get_num, return_unused):
322
+
323
+ def get_flag_bits(flags):
324
+ for b in flags:
325
+ for i in range(8):
326
+ yield (b >> i) & 1
327
+
328
+ def parse_extra(extra):
329
+ extra_offset = 0
330
+ while extra_offset <= len(extra) - 4:
331
+ extra_signature = extra[extra_offset:extra_offset + 2]
332
+ extra_offset += 2
333
+ extra_data_size, = Struct('<H').unpack(extra[extra_offset:extra_offset + 2])
334
+ extra_offset += 2
335
+ extra_data = extra[extra_offset:extra_offset + extra_data_size]
336
+ extra_offset += extra_data_size
337
+ yield (extra_signature, extra_data)
338
+
339
+ def get_extra_value(extra, if_true, signature, exception_if_missing, min_length, exception_if_too_short):
340
+ if if_true:
341
+ try:
342
+ value = extra[signature]
343
+ except KeyError:
344
+ raise exception_if_missing()
345
+
346
+ if len(value) < min_length:
347
+ raise exception_if_too_short()
348
+ else:
349
+ value = None
350
+
351
+ return value
352
+
353
+ def decrypt_weak_decompress(chunks, decompress, is_done, num_unused):
354
+ key_0 = 305419896
355
+ key_1 = 591751049
356
+ key_2 = 878082192
357
+ crc32 = zlib.crc32
358
+ bytes_c = bytes
359
+
360
+ def update_keys(byte):
361
+ nonlocal key_0, key_1, key_2
362
+ key_0 = ~crc32(bytes_c((byte,)), ~key_0) & 0xFFFFFFFF
363
+ key_1 = (key_1 + (key_0 & 0xFF)) & 0xFFFFFFFF
364
+ key_1 = ((key_1 * 134775813) + 1) & 0xFFFFFFFF
365
+ key_2 = ~crc32(bytes_c((key_1 >> 24,)), ~key_2) & 0xFFFFFFFF
366
+
367
+ def decrypt(chunk):
368
+ chunk = bytearray(chunk)
369
+ for i, byte in enumerate(chunk):
370
+ temp = key_2 | 2
371
+ byte ^= ((temp * (temp ^ 1)) >> 8) & 0xFF
372
+ update_keys(byte)
373
+ chunk[i] = byte
374
+ return bytes(chunk)
375
+
376
+ for byte in password:
377
+ update_keys(byte)
378
+
379
+ if decrypt(get_num(12))[11] != mod_time >> 8:
380
+ raise IncorrectZipCryptoPasswordError()
381
+
382
+ while not is_done():
383
+ yield from decompress(decrypt(next_or_truncated_error(chunks)))
384
+
385
+ return_unused(num_unused())
386
+
387
+ def decrypt_aes_decompress(chunks, decompress, is_done, num_unused, key_length_raw):
388
+ try:
389
+ key_length, salt_length = {1: (16, 8), 2: (24, 12), 3: (32, 16)}[key_length_raw]
390
+ except KeyError:
391
+ raise InvalidAESKeyLengthError(key_length_raw)
392
+
393
+ salt = get_num(salt_length)
394
+ password_verification_length = 2
395
+
396
+ keys = PBKDF2(password, salt, 2 * key_length + password_verification_length, 1000)
397
+ if keys[-password_verification_length:] != get_num(password_verification_length):
398
+ raise IncorrectAESPasswordError()
399
+
400
+ decrypter = AES.new(
401
+ keys[:key_length], AES.MODE_CTR,
402
+ counter=Counter.new(nbits=128, little_endian=True)
403
+ )
404
+ hmac = HMAC.new(keys[key_length:key_length * 2], digestmod=SHA1)
405
+
406
+ while not is_done():
407
+ chunk = next_or_truncated_error(chunks)
408
+ yield from decompress(decrypter.decrypt(chunk))
409
+ hmac.update(chunk[:len(chunk) - num_unused()])
410
+
411
+ return_unused(num_unused())
412
+
413
+ if get_num(10) != hmac.digest()[:10]:
414
+ raise HMACIntegrityError()
415
+
416
+ def decrypt_none_decompress(chunks, decompress, is_done, num_unused):
417
+ while not is_done():
418
+ yield from decompress(next_or_truncated_error(chunks))
419
+
420
+ return_unused(num_unused())
421
+
422
+ def get_crc_32_expected_from_data_descriptor(is_zip64, file_size_stored_as_long_long):
423
+ dd_optional_signature = get_num(4)
424
+ dd_so_far_num = \
425
+ 0 if dd_optional_signature == b'PK\x07\x08' else \
426
+ 4
427
+ dd_so_far = dd_optional_signature[:dd_so_far_num]
428
+ # Have to check both if zip64 and if we store as long long (8), since some zip64 store only as long (4)
429
+ dd_remaining = \
430
+ (20 - dd_so_far_num) if is_zip64 and file_size_stored_as_long_long else \
431
+ (12 - dd_so_far_num)
432
+ dd = dd_so_far + get_num(dd_remaining)
433
+ crc_32_expected, = Struct('<I').unpack(dd[:4])
434
+ return crc_32_expected
435
+
436
+ def get_crc_32_expected_from_file_header():
437
+ return crc_32_expected
438
+
439
+ def read_data_and_crc_32_ignore(get_crc_32_expected, chunks):
440
+ yield from chunks
441
+ get_crc_32_expected()
442
+
443
+ def read_data_and_crc_32_verify(get_crc_32_expected, chunks):
444
+ crc_32_actual = zlib.crc32(b'')
445
+ for chunk in chunks:
446
+ crc_32_actual = zlib.crc32(chunk, crc_32_actual)
447
+ yield chunk
448
+
449
+ if crc_32_actual != get_crc_32_expected():
450
+ raise CRC32IntegrityError()
451
+
452
+ version, flags, compression_raw, mod_time, mod_date, crc_32_expected, compressed_size_raw, uncompressed_size_raw, file_name_len, extra_field_len = \
453
+ local_file_header_struct.unpack(get_num(local_file_header_struct.size))
454
+
455
+ flag_bits = tuple(get_flag_bits(flags))
456
+ if (
457
+ flag_bits[4] # Enhanced deflating
458
+ or flag_bits[5] # Compressed patched
459
+ or flag_bits[6] # Strong encrypted
460
+ or flag_bits[13] # Masked header values
461
+ ):
462
+ raise UnsupportedFlagsError(flag_bits)
463
+
464
+ file_name = get_num(file_name_len)
465
+ file_name_str = file_name.decode()
466
+
467
+ # Get these attributes from central directory as they are incorrect in the File Header
468
+ uncompressed_size_raw = central_directory_info[file_name_str]['file_size']
469
+ extract_version = central_directory_info[file_name_str]['extract_version']
470
+ central_directory_extra = central_directory_info[file_name_str]['extra']
471
+
472
+ # Zip64 Extra field requires 20 bytes to store Header (2) + Field Length (2) + File size (8) + Compressed size (8)
473
+ # The length of the filesize field determines if the length of the data descriptor is 12 or 20 bytes.
474
+ if zip64_size_signature in central_directory_extra and len(central_directory_extra) >= 20:
475
+ file_size_stored_as_long_long = True
476
+ else:
477
+ file_size_stored_as_long_long = False
478
+
479
+ extra = dict(parse_extra(get_num(extra_field_len)))
480
+
481
+ is_weak_encrypted = flag_bits[0] and compression_raw != 99
482
+ is_aes_encrypted = flag_bits[0] and compression_raw == 99
483
+ aes_extra = get_extra_value(extra, is_aes_encrypted, aes_extra_signature, MissingAESExtraError, 7,
484
+ TruncatedAESExtraError)
485
+ is_aes_2_encrypted = is_aes_encrypted and aes_extra[0:2] == b'\x02\x00'
486
+
487
+ if is_weak_encrypted and password is None:
488
+ raise MissingZipCryptoPasswordError()
489
+
490
+ if is_aes_encrypted and password is None:
491
+ raise MissingAESPasswordError()
492
+
493
+ compression = \
494
+ Struct('<H').unpack(aes_extra[5:7])[0] if is_aes_encrypted else \
495
+ compression_raw
496
+
497
+ if compression not in (0, 8, 9):
498
+ raise UnsupportedCompressionTypeError(compression)
499
+
500
+ has_data_descriptor = flag_bits[3]
501
+ is_zip64 = compressed_size_raw == zip64_compressed_size and uncompressed_size_raw == zip64_compressed_size \
502
+ or extract_version == zip64_version
503
+ zip64_extra = get_extra_value(extra, not has_data_descriptor and is_zip64, zip64_size_signature,
504
+ MissingZip64ExtraError, 16, TruncatedZip64ExtraError)
505
+
506
+ # zip64_extra can be None in some cases where is_zip64 is True so it is necessary to check.
507
+ uncompressed_size = \
508
+ None if has_data_descriptor and compression in (8, 9) else \
509
+ Struct('<Q').unpack(zip64_extra[:8])[0] if is_zip64 and zip64_extra else \
510
+ uncompressed_size_raw
511
+
512
+ decompressor = \
513
+ get_decompressor_none(uncompressed_size) if compression == 0 else \
514
+ get_decompressor_deflate() if compression == 8 else \
515
+ get_decompressor_deflate64()
516
+
517
+ decompressed_bytes = \
518
+ decrypt_weak_decompress(yield_all(), *decompressor) if is_weak_encrypted else \
519
+ decrypt_aes_decompress(yield_all(), *decompressor,
520
+ key_length_raw=aes_extra[4]) if is_aes_encrypted else \
521
+ decrypt_none_decompress(yield_all(), *decompressor)
522
+
523
+ get_crc_32_expected = \
524
+ partial(get_crc_32_expected_from_data_descriptor, is_zip64, file_size_stored_as_long_long) \
525
+ if has_data_descriptor else get_crc_32_expected_from_file_header
526
+
527
+ crc_checked_bytes = \
528
+ read_data_and_crc_32_ignore(get_crc_32_expected, decompressed_bytes) if is_aes_2_encrypted else \
529
+ read_data_and_crc_32_verify(get_crc_32_expected, decompressed_bytes)
530
+
531
+ return file_name, uncompressed_size, crc_checked_bytes
532
+
533
+ def all():
534
+ yield_all, get_num, return_unused = get_byte_readers(zipfile_chunks)
535
+
536
+ while True:
537
+ signature = get_num(len(local_file_header_signature))
538
+ if signature == local_file_header_signature:
539
+ yield yield_file(yield_all, get_num, return_unused)
540
+ elif signature == central_directory_signature:
541
+ for _ in yield_all():
542
+ pass
543
+ break
544
+ else:
545
+ raise UnexpectedSignatureError(signature)
546
+
547
+ for file_name, file_size, unzipped_chunks in all():
548
+ yield file_name, file_size, unzipped_chunks
549
+ for _ in unzipped_chunks:
550
+ raise UnfinishedIterationError()
551
+
552
+ class UnzipError(Exception):
553
+ pass
554
+
555
+ class InvalidOperationError(UnzipError):
556
+ pass
557
+
558
+ class UnfinishedIterationError(InvalidOperationError):
559
+ pass
560
+
561
+ class UnzipValueError(UnzipError, ValueError):
562
+ pass
563
+
564
+ class DataError(UnzipValueError):
565
+ pass
566
+
567
+ class UncompressError(UnzipValueError):
568
+ pass
569
+
570
+ class DeflateError(UncompressError):
571
+ pass
572
+
573
+ class UnsupportedFeatureError(DataError):
574
+ pass
575
+
576
+ class UnsupportedFlagsError(UnsupportedFeatureError):
577
+ pass
578
+
579
+ class UnsupportedCompressionTypeError(UnsupportedFeatureError):
580
+ pass
581
+
582
+ class TruncatedDataError(DataError):
583
+ pass
584
+
585
+ class UnexpectedSignatureError(DataError):
586
+ pass
587
+
588
+ class MissingExtraError(DataError):
589
+ pass
590
+
591
+ class MissingZip64ExtraError(MissingExtraError):
592
+ pass
593
+
594
+ class MissingAESExtraError(MissingExtraError):
595
+ pass
596
+
597
+ class TruncatedExtraError(DataError):
598
+ pass
599
+
600
+ class TruncatedZip64ExtraError(TruncatedExtraError):
601
+ pass
602
+
603
+ class TruncatedAESExtraError(TruncatedExtraError):
604
+ pass
605
+
606
+ class InvalidExtraError(TruncatedExtraError):
607
+ pass
608
+
609
+ class InvalidAESKeyLengthError(TruncatedExtraError):
610
+ pass
611
+
612
+ class IntegrityError(DataError):
613
+ pass
614
+
615
+ class HMACIntegrityError(IntegrityError):
616
+ pass
617
+
618
+ class CRC32IntegrityError(IntegrityError):
619
+ pass
620
+
621
+ class PasswordError(UnzipValueError):
622
+ pass
623
+
624
+ class MissingPasswordError(UnzipValueError):
625
+ pass
626
+
627
+ class MissingZipCryptoPasswordError(MissingPasswordError):
628
+ pass
629
+
630
+ class MissingAESPasswordError(MissingPasswordError):
631
+ pass
632
+
633
+ class IncorrectPasswordError(PasswordError):
634
+ pass
635
+
636
+ class IncorrectZipCryptoPasswordError(IncorrectPasswordError):
637
+ pass
638
+
639
+ class IncorrectAESPasswordError(IncorrectPasswordError):
640
+ pass
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.4
2
+ Name: pybiolib
3
+ Version: 1.2.1890
4
+ Summary: BioLib Python Client
5
+ Project-URL: Homepage, https://github.com/biolib
6
+ Author-email: biolib <hello@biolib.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: biolib
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.6.3
13
+ Requires-Dist: appdirs>=1.4.3
14
+ Requires-Dist: click>=8.0.0
15
+ Requires-Dist: docker>=5.0.3
16
+ Requires-Dist: importlib-metadata>=1.6.1
17
+ Requires-Dist: pyyaml>=5.3.1
18
+ Requires-Dist: rich>=12.4.4
19
+ Requires-Dist: typing-extensions>=4.1.0; python_version < '3.11'
20
+ Provides-Extra: compute-node
21
+ Requires-Dist: flask>=2.0.1; extra == 'compute-node'
22
+ Requires-Dist: gunicorn>=20.1.0; extra == 'compute-node'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # PyBioLib
26
+
27
+ PyBioLib is a Python package for running BioLib applications from Python scripts and the command line.
28
+
29
+ ### Python Example
30
+ ```python
31
+ # pip3 install -U pybiolib
32
+ import biolib
33
+ samtools = biolib.load('samtools/samtools')
34
+ print(samtools.cli(args='--help'))
35
+ ```
36
+
37
+ ### Command Line Example
38
+ ```bash
39
+ pip3 install -U pybiolib
40
+ biolib run samtools/samtools --help
41
+ ```