pybiolib 1.2.339__py3-none-any.whl → 1.2.340.dev1__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.
@@ -84,6 +84,8 @@ def fasta_batch_records(records, work_per_batch_min, work_per_residue=1, verbose
84
84
  n_seqs, n_res = batch_dict['records'], batch_dict['residues']
85
85
  print(f'Batch {i+1}: {n_res} residues from {n_seqs} sequences')
86
86
 
87
+ # records = SeqUtil.parse_fasta(fasta_file)
88
+
87
89
  batches = []
88
90
  batch = []
89
91
  current_work_units = 0
@@ -27,7 +27,7 @@ from biolib.compute_node import utils as compute_node_utils
27
27
  from biolib.compute_node.cloud_utils import CloudUtils
28
28
  from biolib.compute_node.job_worker.docker_image_cache import DockerImageCache
29
29
  from biolib.compute_node.job_worker.executors.docker_types import DockerDiffKind
30
- from biolib.compute_node.job_worker.executors.types import LocalExecutorOptions, StatusUpdate
30
+ from biolib.compute_node.job_worker.executors.types import LocalExecutorOptions, StatusUpdate, MetadataToSaveOutput
31
31
  from biolib.compute_node.job_worker.mappings import Mappings, path_without_first_folder
32
32
  from biolib.compute_node.job_worker.utilization_reporter_thread import UtilizationReporterThread
33
33
  from biolib.compute_node.job_worker.utils import ComputeProcessException
@@ -61,9 +61,12 @@ class DockerExecutor:
61
61
  os.makedirs(user_data_tar_dir, exist_ok=True)
62
62
 
63
63
  self._docker_container: Optional[Container] = None
64
+ self._docker_api_client = BiolibDockerClient.get_docker_client().api
64
65
  self._runtime_tar_path = f'{user_data_tar_dir}/runtime_{self._random_docker_id}.tar'
65
66
  self._input_tar_path = f'{user_data_tar_dir}/input_{self._random_docker_id}.tar'
66
67
 
68
+ self._metadata_for_save_output_on_cancel: Optional[MetadataToSaveOutput] = None
69
+
67
70
  if utils.IS_RUNNING_IN_CLOUD and not utils.BIOLIB_SECRETS_TMPFS_PATH:
68
71
  error_message = 'Running in cloud but no TMPFS path has been set for secrets'
69
72
  logger_no_user_data.error(error_message)
@@ -81,12 +84,12 @@ class DockerExecutor:
81
84
  raise Exception('Docker container was None')
82
85
  return self._docker_container
83
86
 
84
- def execute_module(self, module_input_path: str, module_output_path: str) -> None:
87
+ def execute_module(self) -> None:
85
88
  try:
86
89
  job_uuid = self._options['job']['public_id']
87
90
  send_status_update = self._options['send_status_update']
88
91
  logger_no_user_data.debug(f'Reading module input of {job_uuid}.')
89
- with open(module_input_path, 'rb') as fp:
92
+ with open(self._options['module_input_path'], 'rb') as fp:
90
93
  module_input_tmp = ModuleInput(fp.read())
91
94
  logger_no_user_data.debug(f'Deserialing module input of {job_uuid}...')
92
95
  module_input = module_input_tmp.deserialize()
@@ -100,7 +103,7 @@ class DockerExecutor:
100
103
 
101
104
  logger_no_user_data.debug(f'Starting execution of {job_uuid}.')
102
105
  try:
103
- self._execute_helper(module_input, module_output_path)
106
+ self._execute_helper(module_input)
104
107
  except docker.errors.NotFound as docker_error:
105
108
  raise DockerContainerNotFoundDuringExecutionException from docker_error
106
109
  except Exception as exception:
@@ -161,7 +164,8 @@ class DockerExecutor:
161
164
  may_contain_user_data=False,
162
165
  )
163
166
 
164
- def _execute_helper(self, module_input, module_output_path: str) -> None:
167
+
168
+ def _execute_helper(self, module_input) -> None:
165
169
  job_uuid = self._options['job']['public_id']
166
170
  logger_no_user_data.debug(f'Initializing container for {job_uuid}.')
167
171
  self._initialize_docker_container(module_input)
@@ -187,10 +191,9 @@ class DockerExecutor:
187
191
  logger_no_user_data.debug(f'_map_and_copy_input_files_to_container for {job_uuid}.')
188
192
  self._map_and_copy_input_files_to_container(module_input['files'], module_input['arguments'])
189
193
 
190
- docker_api_client = BiolibDockerClient.get_docker_client().api
191
194
  logger_no_user_data.debug(f'Attaching Docker container for {job_uuid}')
192
195
 
193
- stdout_and_stderr_stream = docker_api_client.attach(
196
+ stdout_and_stderr_stream = self._docker_api_client.attach(
194
197
  container=self._container.id,
195
198
  stderr=True,
196
199
  stdout=True,
@@ -211,6 +214,12 @@ class DockerExecutor:
211
214
  logger.debug(f'Warning: Job "{job_uuid}" failed to start container. Hit error: {startup_error_string}')
212
215
  # even though the container start failed we should still be able to call logs() and wait() on it, so we pass
213
216
 
217
+ self._metadata_for_save_output_on_cancel = MetadataToSaveOutput(
218
+ arguments=module_input['arguments'],
219
+ startup_error_string=startup_error_string,
220
+ pre_start_diff=pre_start_diff,
221
+ )
222
+
214
223
  if self._options['job']['app_version'].get('stdout_render_type') != 'markdown':
215
224
  logger_no_user_data.debug(f'Streaming stdout for {job_uuid}')
216
225
  for stdout_and_stderr in stdout_and_stderr_stream:
@@ -220,7 +229,14 @@ class DockerExecutor:
220
229
  self._send_stdout_and_stderr(stdout_and_stderr)
221
230
 
222
231
  logger_no_user_data.debug(f'Waiting on docker for {job_uuid}')
223
- docker_result = docker_api_client.wait(self._container.id)
232
+ try:
233
+ docker_result = self._docker_api_client.wait(self._container.id)
234
+ except docker.errors.NotFound as error:
235
+ if self._is_cleaning_up:
236
+ return
237
+ else:
238
+ raise error
239
+
224
240
  logger_no_user_data.debug(f'Got result from docker for {job_uuid}')
225
241
  exit_code = docker_result['StatusCode']
226
242
  # 137 is the error code from linux OOM killer (Should catch 90% of OOM errors)
@@ -232,22 +248,26 @@ class DockerExecutor:
232
248
  )
233
249
 
234
250
  logger_no_user_data.debug(f'Docker container exited with code {exit_code} for {job_uuid}')
251
+ self._save_module_output_from_container(exit_code, self._metadata_for_save_output_on_cancel)
252
+
235
253
 
236
- full_stdout = docker_api_client.logs(self._container.id, stdout=True, stderr=False)
237
- full_stderr = docker_api_client.logs(self._container.id, stdout=False, stderr=True)
254
+ def _save_module_output_from_container(self, exit_code: int, metadata: MetadataToSaveOutput) -> None:
255
+ full_stdout = self._docker_api_client.logs(self._container.id, stdout=True, stderr=False)
256
+ full_stderr = self._docker_api_client.logs(self._container.id, stdout=False, stderr=True)
238
257
 
239
- if startup_error_string:
240
- full_stderr = full_stderr + startup_error_string.encode()
258
+ if metadata['startup_error_string']:
259
+ full_stderr = full_stderr + metadata['startup_error_string'].encode()
241
260
 
242
261
  self._write_module_output_to_file(
243
- arguments=module_input['arguments'],
262
+ arguments=metadata['arguments'],
244
263
  exit_code=exit_code,
245
- module_output_path=module_output_path,
264
+ module_output_path=self._options['module_output_path'],
246
265
  stderr=full_stderr,
247
266
  stdout=full_stdout,
248
- pre_start_diff=pre_start_diff,
267
+ pre_start_diff=metadata['pre_start_diff'],
249
268
  )
250
269
 
270
+
251
271
  def cleanup(self):
252
272
  # Don't clean up if already in the process of doing so, or done doing so
253
273
  if self._is_cleaning_up:
@@ -255,6 +275,22 @@ class DockerExecutor:
255
275
  else:
256
276
  self._is_cleaning_up = True
257
277
 
278
+ if self._metadata_for_save_output_on_cancel is not None:
279
+ try:
280
+ logger_no_user_data.debug('Attempting to save results')
281
+ self._docker_container.stop()
282
+ self._docker_container.reload()
283
+ logger_no_user_data.debug(f'Container state {self._docker_container.status}')
284
+ self._save_module_output_from_container(
285
+ exit_code=self._docker_container.attrs['State']['ExitCode'],
286
+ metadata=self._metadata_for_save_output_on_cancel
287
+ )
288
+ logger_no_user_data.debug('Saved results')
289
+ except BaseException as error:
290
+ logger_no_user_data.error(f'Failed to save results on cancel with error: {error}')
291
+ else:
292
+ logger_no_user_data.debug('Missing metadata, cannot save results')
293
+
258
294
  tar_time = time.time()
259
295
  for path_to_delete in [self._input_tar_path, self._runtime_tar_path]:
260
296
  if os.path.exists(path_to_delete):
@@ -34,9 +34,16 @@ class LocalExecutorOptions(TypedDict):
34
34
  cloud_job: Optional[CloudJob]
35
35
  large_file_systems: Dict[str, LargeFileSystem]
36
36
  module: Module
37
+ module_input_path: str
38
+ module_output_path: str
37
39
  remote_host_proxies: List[RemoteHostProxy]
38
40
  root_job_id: str
39
41
  runtime_zip_bytes: Optional[bytes] # TODO: replace this with a module_source_serialized
40
42
  send_status_update: SendStatusUpdateType
41
43
  send_system_exception: SendSystemExceptionType
42
44
  send_stdout_and_stderr: SendStdoutAndStderrType
45
+
46
+ class MetadataToSaveOutput(TypedDict):
47
+ arguments: List[str]
48
+ startup_error_string: Optional[str]
49
+ pre_start_diff: List[Dict]
@@ -400,15 +400,15 @@ class JobWorker:
400
400
  cloud_job=self._root_job_wrapper['cloud_job'],
401
401
  large_file_systems=lfs_dict,
402
402
  module=main_module,
403
+ module_input_path=module_input_path,
404
+ module_output_path=main_module_output_path,
403
405
  remote_host_proxies=self._remote_host_proxies,
404
406
  root_job_id=root_job_id,
405
407
  runtime_zip_bytes=runtime_zip_bytes,
406
408
  send_status_update=self._send_status_update,
407
409
  send_system_exception=self.send_system_exception,
408
410
  send_stdout_and_stderr=self.send_stdout_and_stderr,
409
- ),
410
- module_input_path,
411
- main_module_output_path,
411
+ )
412
412
  )
413
413
 
414
414
  if utils.IS_RUNNING_IN_CLOUD:
@@ -418,11 +418,11 @@ class JobWorker:
418
418
  def _run_module(
419
419
  self,
420
420
  options: LocalExecutorOptions,
421
- module_input_path: str,
422
- module_output_path: str,
423
421
  ) -> None:
424
422
  module = options['module']
425
423
  job_id = options['job']['public_id']
424
+ module_output_path = options['module_output_path']
425
+ module_input_path = options['module_input_path']
426
426
  logger_no_user_data.debug(f'Job "{job_id}" running module "{module["name"]}"...')
427
427
 
428
428
  executor_instance: DockerExecutor
@@ -469,7 +469,7 @@ class JobWorker:
469
469
  # Log memory and disk before pulling and executing module
470
470
  log_disk_and_memory_usage_info()
471
471
 
472
- executor_instance.execute_module(module_input_path, module_output_path)
472
+ executor_instance.execute_module()
473
473
 
474
474
  def _connect_to_parent(self):
475
475
  try:
@@ -7,6 +7,7 @@ import sys
7
7
  import threading
8
8
  import time
9
9
  from queue import Queue
10
+ from typing import Optional
10
11
 
11
12
  from biolib import api, utils
12
13
  from biolib.biolib_binary_format import ModuleOutputV2, SystemException, SystemStatusUpdate
@@ -55,6 +56,26 @@ class WorkerThread(threading.Thread):
55
56
  def _job_temporary_dir(self):
56
57
  return self.compute_state['job_temporary_dir']
57
58
 
59
+ def _upload_module_output_and_get_exit_code(self) -> Optional[int]:
60
+ exit_code = None
61
+ try:
62
+ module_output_path = os.path.join(
63
+ self._job_temporary_dir,
64
+ JobStorage.module_output_file_name,
65
+ )
66
+ if os.path.exists(module_output_path):
67
+ module_output = ModuleOutputV2(buffer=LocalFileIndexableBuffer(filename=module_output_path))
68
+ exit_code = module_output.get_exit_code()
69
+ logger_no_user_data.debug(f'Got exit code: {exit_code}')
70
+ if utils.IS_RUNNING_IN_CLOUD:
71
+ JobStorage.upload_module_output(
72
+ job_temporary_dir=self._job_temporary_dir,
73
+ job_uuid=self._job_uuid,
74
+ )
75
+ except Exception as error:
76
+ logger_no_user_data.error(f'Could not upload module output or get exit code: {error}')
77
+ return exit_code
78
+
58
79
  def run(self):
59
80
  try:
60
81
  while True:
@@ -77,25 +98,7 @@ class WorkerThread(threading.Thread):
77
98
 
78
99
  # If 'Computation Finished'
79
100
  if progress == 94:
80
- # Get Job exit code
81
- try:
82
- module_output_path = os.path.join(
83
- self._job_temporary_dir,
84
- JobStorage.module_output_file_name,
85
- )
86
- module_output = ModuleOutputV2(buffer=LocalFileIndexableBuffer(filename=module_output_path))
87
- self.compute_state['exit_code'] = module_output.get_exit_code()
88
- logger_no_user_data.debug(f"Got exit code: {self.compute_state['exit_code']}")
89
-
90
- except Exception as error: # pylint: disable=broad-except
91
- logger_no_user_data.error(f'Could not get exit_code from module output due to: {error}')
92
-
93
- if utils.IS_RUNNING_IN_CLOUD:
94
- JobStorage.upload_module_output(
95
- job_temporary_dir=self._job_temporary_dir,
96
- job_uuid=self._job_uuid,
97
- )
98
-
101
+ self.compute_state['exit_code'] = self._upload_module_output_and_get_exit_code()
99
102
  self._set_status_update(progress=95, log_message='Result Ready')
100
103
  self.compute_state['is_completed'] = True
101
104
  self.terminate()
@@ -174,13 +177,13 @@ class WorkerThread(threading.Thread):
174
177
 
175
178
  def terminate(self) -> None:
176
179
  cloud_job_uuid = self.compute_state['cloud_job_id']
177
- exit_code = self.compute_state.get('exit_code')
178
180
  system_exception_code = self.compute_state['status'].get('error_code')
179
- if utils.IS_RUNNING_IN_CLOUD:
181
+
182
+ if utils.IS_RUNNING_IN_CLOUD and system_exception_code != SystemExceptionCodes.CANCELLED_BY_USER.value:
180
183
  CloudUtils.finish_cloud_job(
181
184
  cloud_job_id=cloud_job_uuid,
182
185
  system_exception_code=system_exception_code,
183
- exit_code=exit_code,
186
+ exit_code=self.compute_state.get('exit_code', None),
184
187
  )
185
188
 
186
189
  deregistered_due_to_error = False
@@ -211,6 +214,15 @@ class WorkerThread(threading.Thread):
211
214
  CloudUtils.deregister(error='job_cleanup_timed_out')
212
215
  deregistered_due_to_error = True
213
216
 
217
+ elif system_exception_code == SystemExceptionCodes.CANCELLED_BY_USER.value:
218
+ self.compute_state['exit_code'] = self._upload_module_output_and_get_exit_code()
219
+ CloudUtils.finish_cloud_job(
220
+ cloud_job_id=cloud_job_uuid,
221
+ system_exception_code=system_exception_code,
222
+ exit_code=self.compute_state.get('exit_code', None),
223
+ )
224
+
225
+
214
226
  # Delete result as error occurred
215
227
  if system_exception_code and os.path.exists(self._job_temporary_dir):
216
228
  shutil.rmtree(self._job_temporary_dir)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pybiolib
3
- Version: 1.2.339
3
+ Version: 1.2.340.dev1
4
4
  Summary: BioLib Python Client
5
5
  License: MIT
6
6
  Keywords: biolib
@@ -24,7 +24,7 @@ biolib/_internal/types/resource.py,sha256=G-vPkZoe4Um6FPxsQZtRzAlbSW5sDW4NFkbjn2
24
24
  biolib/_internal/types/resource_version.py,sha256=sLxViYXloDDUhTDFgjegiQCj097OM1Ih1-uqlC_4ULA,174
25
25
  biolib/_internal/types/typing.py,sha256=D4EKKEe7kDx0K6lJi-H_XLtk-8w6nu2fdqn9bvzI-Xo,288
26
26
  biolib/_internal/utils/__init__.py,sha256=p5vsIFyu-zYqBgdSMfwW9NC_jk7rXvvCbV4Bzd3As7c,630
27
- biolib/_internal/utils/multinode.py,sha256=-J3PEAK3NaOwCn--5T7vWHkA3yu5w9QhmuhkQcH-2wY,8229
27
+ biolib/_internal/utils/multinode.py,sha256=G0t7dkHtoxIfdCD0vjG8eyQwRxQ0zWazOWZUFQ3hQxw,8278
28
28
  biolib/_runtime/runtime.py,sha256=bZQ0m39R9jOBVAtlyvzDnOobKueOAQUCwMUZjDQnO7E,4439
29
29
  biolib/api/__init__.py,sha256=mQ4u8FijqyLzjYMezMUUbbBGNB3iFmkNdjXnWPZ7Jlw,138
30
30
  biolib/api/client.py,sha256=_kmwaGI_-u7kOeWVCYmo-pD2K1imwEv9n2gNZRb5F-I,3790
@@ -76,14 +76,14 @@ biolib/compute_node/job_worker/cache_state.py,sha256=MwjSRzcJJ_4jybqvBL4xdgnDYSI
76
76
  biolib/compute_node/job_worker/cache_types.py,sha256=ajpLy8i09QeQS9dEqTn3T6NVNMY_YsHQkSD5nvIHccQ,818
77
77
  biolib/compute_node/job_worker/docker_image_cache.py,sha256=ansHIkJIq_EMW1nZNlW-RRLVVeKWTbzNICYaOHpKiRE,7460
78
78
  biolib/compute_node/job_worker/executors/__init__.py,sha256=bW6t1qi3PZTlHM4quaTLa8EI4ALTCk83cqcVJfJfJfE,145
79
- biolib/compute_node/job_worker/executors/docker_executor.py,sha256=J-wv7FTb_HJnfRdIByiOdtj18Fw5nYvVm-n0D1mrqwA,29377
79
+ biolib/compute_node/job_worker/executors/docker_executor.py,sha256=NpDSgNWbnC0Qr6aFaQMEfhib3O9BMOPsia-YqeTMhpU,30966
80
80
  biolib/compute_node/job_worker/executors/docker_types.py,sha256=VhsU1DKtJjx_BbCkVmiPZPH4ROiL1ygW1Y_s1Kbpa2o,216
81
81
  biolib/compute_node/job_worker/executors/tars/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
82
- biolib/compute_node/job_worker/executors/types.py,sha256=yP5gG39hr-DLnw9bOE--VHi-1arDbIYiGuV1rlTbbHI,1466
82
+ biolib/compute_node/job_worker/executors/types.py,sha256=gw_T-oq0lty8Hg8WsTrqhtV-kyOF_l5nr9Xe8-kzetc,1657
83
83
  biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py,sha256=_cvEiZbOwfkv6fYmfrvdi_FVviIEYr_dSClQcOQaUWM,1198
84
84
  biolib/compute_node/job_worker/job_max_runtime_timer_thread.py,sha256=K_xgz7IhiIjpLlXRk8sqaMyLoApcidJkgu29sJX0gb8,1174
85
85
  biolib/compute_node/job_worker/job_storage.py,sha256=lScHI3ubcHKagSEW243tgbIWXUfbWDHDjEOPMvXxJE8,4603
86
- biolib/compute_node/job_worker/job_worker.py,sha256=fuWoYJo9HOqLmWl8yeCXh0mhT4ebbkrWac-BVb58khs,28842
86
+ biolib/compute_node/job_worker/job_worker.py,sha256=xp1laEWUhUsKzF4ITdsTWcQlSn133pAjkeiPMAG5jqQ,28892
87
87
  biolib/compute_node/job_worker/large_file_system.py,sha256=XXqRlVtYhs-Ji9zQGIk5KQPXFO_Q5jJH0nnlw4GkeMY,10461
88
88
  biolib/compute_node/job_worker/mappings.py,sha256=Z48Kg4nbcOvsT2-9o3RRikBkqflgO4XeaWxTGz-CNvI,2499
89
89
  biolib/compute_node/job_worker/utilization_reporter_thread.py,sha256=7tm5Yk9coqJ9VbEdnO86tSXI0iM0omwIyKENxdxiVXk,8575
@@ -97,7 +97,7 @@ biolib/compute_node/webserver/gunicorn_flask_application.py,sha256=jPfR_YvNBekLU
97
97
  biolib/compute_node/webserver/webserver.py,sha256=o4kOAStsqThUtKlnRE-U5TP0JIYntuySDjU7PH310xg,6620
98
98
  biolib/compute_node/webserver/webserver_types.py,sha256=2t8EaFKESnves3BA_NBdnS2yAdo1qwamCFHiSt888nE,380
99
99
  biolib/compute_node/webserver/webserver_utils.py,sha256=XWvwYPbWNR3qS0FYbLLp-MDDfVk0QdaAmg3xPrT0H2s,4234
100
- biolib/compute_node/webserver/worker_thread.py,sha256=GRRBUqXdMKvbjyLQhYlqGIbFKeU2iiEXIe5IXi9wgdg,11806
100
+ biolib/compute_node/webserver/worker_thread.py,sha256=0kb7RgwDSIzTZfjttUHelyAS8g7VPx1J1E7GbqYObu0,12288
101
101
  biolib/experiments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
102
102
  biolib/experiments/experiment.py,sha256=m7FB7hot8CMOVrNyWGPB6FTkWQJwI0ytpIvtPxfoPME,7684
103
103
  biolib/jobs/__init__.py,sha256=aIb2H2DHjQbM2Bs-dysFijhwFcL58Blp0Co0gimED3w,32
@@ -118,8 +118,8 @@ biolib/utils/cache_state.py,sha256=u256F37QSRIVwqKlbnCyzAX4EMI-kl6Dwu6qwj-Qmag,3
118
118
  biolib/utils/multipart_uploader.py,sha256=XvGP1I8tQuKhAH-QugPRoEsCi9qvbRk-DVBs5PNwwJo,8452
119
119
  biolib/utils/seq_util.py,sha256=Ozk0blGtPur_D9MwShD02r_mphyQmgZkx-lOHOwnlIM,6730
120
120
  biolib/utils/zip/remote_zip.py,sha256=0wErYlxir5921agfFeV1xVjf29l9VNgGQvNlWOlj2Yc,23232
121
- pybiolib-1.2.339.dist-info/LICENSE,sha256=F2h7gf8i0agDIeWoBPXDMYScvQOz02pAWkKhTGOHaaw,1067
122
- pybiolib-1.2.339.dist-info/METADATA,sha256=7w5znJtkMyO9KQ_CD0PdX2Hz1pBuTjnEOeSnfaOMrsk,1570
123
- pybiolib-1.2.339.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
124
- pybiolib-1.2.339.dist-info/entry_points.txt,sha256=p6DyaP_2kctxegTX23WBznnrDi4mz6gx04O5uKtRDXg,42
125
- pybiolib-1.2.339.dist-info/RECORD,,
121
+ pybiolib-1.2.340.dev1.dist-info/LICENSE,sha256=F2h7gf8i0agDIeWoBPXDMYScvQOz02pAWkKhTGOHaaw,1067
122
+ pybiolib-1.2.340.dev1.dist-info/METADATA,sha256=nZlIvyDlGZX801BNcASFHgpASI2ztyIKhkBMgoWfhss,1575
123
+ pybiolib-1.2.340.dev1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
124
+ pybiolib-1.2.340.dev1.dist-info/entry_points.txt,sha256=p6DyaP_2kctxegTX23WBznnrDi4mz6gx04O5uKtRDXg,42
125
+ pybiolib-1.2.340.dev1.dist-info/RECORD,,