pybiolib 1.2.1240__tar.gz → 1.2.1630__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.

Potentially problematic release.


This version of pybiolib might be problematic. Click here for more details.

Files changed (184) hide show
  1. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/PKG-INFO +1 -1
  2. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/__init__.py +33 -10
  3. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_data_record/data_record.py +11 -11
  4. pybiolib-1.2.1630/biolib/_index/index.py +51 -0
  5. pybiolib-1.2.1630/biolib/_index/types.py +7 -0
  6. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/data_record/data_record.py +1 -1
  7. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/data_record/push_data.py +1 -1
  8. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/data_record/remote_storage_endpoint.py +3 -3
  9. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/file_utils.py +7 -4
  10. pybiolib-1.2.1630/biolib/_internal/index/__init__.py +1 -0
  11. pybiolib-1.2.1630/biolib/_internal/index/index.py +18 -0
  12. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/lfs/cache.py +4 -2
  13. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/push_application.py +70 -20
  14. pybiolib-1.2.1630/biolib/_internal/templates/gui_template/App.tsx +53 -0
  15. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/gui_template/Dockerfile +2 -0
  16. pybiolib-1.2.1630/biolib/_internal/templates/gui_template/biolib-sdk.ts +37 -0
  17. pybiolib-1.2.1630/biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
  18. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/gui_template/package.json +1 -0
  19. pybiolib-1.2.1630/biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +49 -0
  20. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/gui_template/vite.config.mts +2 -1
  21. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/init_template/.github/workflows/biolib.yml +6 -1
  22. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/init_template/Dockerfile +2 -0
  23. pybiolib-1.2.1630/biolib/_internal/utils/job_url.py +33 -0
  24. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_session/session.py +7 -5
  25. pybiolib-1.2.1630/biolib/_shared/types/__init__.py +69 -0
  26. pybiolib-1.2.1630/biolib/_shared/types/resource.py +17 -0
  27. pybiolib-1.2.1630/biolib/_shared/types/resource_deploy_key.py +11 -0
  28. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_shared}/types/resource_permission.py +1 -1
  29. pybiolib-1.2.1630/biolib/_shared/utils/__init__.py +7 -0
  30. pybiolib-1.2.1630/biolib/_shared/utils/resource_uri.py +75 -0
  31. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/api/client.py +1 -1
  32. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/app/app.py +42 -19
  33. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_binary_format/module_input.py +8 -0
  34. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_binary_format/remote_endpoints.py +3 -3
  35. pybiolib-1.2.1630/biolib/biolib_binary_format/remote_stream_seeker.py +59 -0
  36. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/cli/__init__.py +2 -1
  37. pybiolib-1.2.1630/biolib/cli/index.py +32 -0
  38. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/cli/lfs.py +1 -1
  39. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/cli/start.py +14 -1
  40. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/executors/docker_executor.py +19 -2
  41. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/job_worker.py +145 -71
  42. pybiolib-1.2.1630/biolib/compute_node/job_worker/network_alloc.py +99 -0
  43. pybiolib-1.2.1630/biolib/compute_node/job_worker/network_buffer.py +240 -0
  44. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/utilization_reporter_thread.py +2 -2
  45. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/remote_host_proxy.py +84 -34
  46. pybiolib-1.2.1630/biolib/experiments/__init__.py +0 -0
  47. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/experiments/experiment.py +22 -16
  48. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/jobs/job.py +76 -26
  49. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/jobs/job_result.py +65 -7
  50. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/jobs/types.py +1 -0
  51. pybiolib-1.2.1630/biolib/py.typed +0 -0
  52. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/sdk/__init__.py +17 -2
  53. pybiolib-1.2.1630/biolib/typing_utils.py +2 -0
  54. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/utils/cache_state.py +2 -2
  55. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/pyproject.toml +1 -1
  56. pybiolib-1.2.1240/biolib/_internal/templates/gui_template/App.tsx +0 -17
  57. pybiolib-1.2.1240/biolib/_internal/types/__init__.py +0 -6
  58. pybiolib-1.2.1240/biolib/biolib_binary_format/remote_stream_seeker.py +0 -45
  59. pybiolib-1.2.1240/biolib/typing_utils.py +0 -2
  60. pybiolib-1.2.1240/biolib/utils/app_uri.py +0 -57
  61. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/LICENSE +0 -0
  62. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/PYPI_README.md +0 -0
  63. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_index}/__init__.py +0 -0
  64. {pybiolib-1.2.1240/biolib/compute_node → pybiolib-1.2.1630/biolib/_internal}/__init__.py +0 -0
  65. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/add_copilot_prompts.py +0 -0
  66. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/add_gui_files.py +0 -0
  67. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/data_record/__init__.py +0 -0
  68. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/errors.py +0 -0
  69. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/fuse_mount/__init__.py +0 -0
  70. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/fuse_mount/experiment_fuse_mount.py +0 -0
  71. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/http_client.py +0 -0
  72. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/lfs/__init__.py +0 -0
  73. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/libs/__init__.py +0 -0
  74. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/libs/fusepy/__init__.py +0 -0
  75. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/runtime.py +0 -0
  76. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/string_utils.py +0 -0
  77. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/__init__.py +0 -0
  78. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/copilot_template/.github/instructions/general-app-knowledge.instructions.md +0 -0
  79. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/copilot_template/.github/instructions/style-general.instructions.md +0 -0
  80. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/copilot_template/.github/instructions/style-python.instructions.md +0 -0
  81. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +0 -0
  82. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/copilot_template/.github/prompts/biolib_app_inputs.prompt.md +0 -0
  83. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +0 -0
  84. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/copilot_template/.github/prompts/biolib_run_apps.prompt.md +0 -0
  85. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/gui_template/.yarnrc.yml +0 -0
  86. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/gui_template/index.css +0 -0
  87. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/gui_template/index.html +0 -0
  88. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/gui_template/index.tsx +0 -0
  89. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/gui_template/tsconfig.json +0 -0
  90. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/init_template/.biolib/config.yml +0 -0
  91. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/init_template/.gitignore +0 -0
  92. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/init_template/requirements.txt +0 -0
  93. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/init_template/run.py +0 -0
  94. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/init_template/run.sh +0 -0
  95. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/templates/templates.py +0 -0
  96. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/tree_utils.py +0 -0
  97. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/utils/__init__.py +0 -0
  98. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_internal/utils/multinode.py +0 -0
  99. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/_runtime/runtime.py +0 -0
  100. {pybiolib-1.2.1240/biolib/compute_node/job_worker/executors/tars → pybiolib-1.2.1630/biolib/_shared}/__init__.py +0 -0
  101. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_shared}/types/account.py +0 -0
  102. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_shared}/types/account_member.py +0 -0
  103. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_shared}/types/app.py +0 -0
  104. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_shared}/types/data_record.py +0 -0
  105. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_shared}/types/experiment.py +0 -0
  106. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_shared}/types/file_node.py +0 -0
  107. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_shared}/types/push.py +0 -0
  108. /pybiolib-1.2.1240/biolib/_internal/types/resource.py → /pybiolib-1.2.1630/biolib/_shared/types/resource_types.py +0 -0
  109. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_shared}/types/resource_version.py +0 -0
  110. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_shared}/types/result.py +0 -0
  111. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_shared}/types/typing.py +0 -0
  112. {pybiolib-1.2.1240/biolib/_internal → pybiolib-1.2.1630/biolib/_shared}/types/user.py +0 -0
  113. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/api/__init__.py +0 -0
  114. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/app/__init__.py +0 -0
  115. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/app/search_apps.py +0 -0
  116. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_api_client/__init__.py +0 -0
  117. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_api_client/api_client.py +0 -0
  118. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_api_client/app_types.py +0 -0
  119. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_api_client/auth.py +0 -0
  120. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_api_client/biolib_app_api.py +0 -0
  121. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_api_client/biolib_job_api.py +0 -0
  122. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_api_client/common_types.py +0 -0
  123. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_api_client/job_types.py +0 -0
  124. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_api_client/lfs_types.py +0 -0
  125. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_api_client/user_state.py +0 -0
  126. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_binary_format/__init__.py +0 -0
  127. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_binary_format/base_bbf_package.py +0 -0
  128. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_binary_format/file_in_container.py +0 -0
  129. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_binary_format/module_output_v2.py +0 -0
  130. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_binary_format/saved_job.py +0 -0
  131. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_binary_format/stdout_and_stderr.py +0 -0
  132. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_binary_format/system_exception.py +0 -0
  133. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_binary_format/system_status_update.py +0 -0
  134. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_binary_format/utils.py +0 -0
  135. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_docker_client/__init__.py +0 -0
  136. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_download_container.py +0 -0
  137. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_errors.py +0 -0
  138. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/biolib_logging.py +0 -0
  139. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/cli/auth.py +0 -0
  140. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/cli/data_record.py +0 -0
  141. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/cli/download_container.py +0 -0
  142. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/cli/init.py +0 -0
  143. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/cli/push.py +0 -0
  144. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/cli/run.py +0 -0
  145. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/cli/runtime.py +0 -0
  146. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/cli/sdk.py +0 -0
  147. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/.gitignore +0 -0
  148. {pybiolib-1.2.1240/biolib/compute_node/webserver → pybiolib-1.2.1630/biolib/compute_node}/__init__.py +0 -0
  149. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/cloud_utils/__init__.py +0 -0
  150. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/cloud_utils/cloud_utils.py +0 -0
  151. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/__init__.py +0 -0
  152. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/cache_state.py +0 -0
  153. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/cache_types.py +0 -0
  154. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/docker_image_cache.py +0 -0
  155. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/executors/__init__.py +0 -0
  156. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/executors/docker_types.py +0 -0
  157. {pybiolib-1.2.1240/biolib/experiments → pybiolib-1.2.1630/biolib/compute_node/job_worker/executors/tars}/__init__.py +0 -0
  158. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/executors/types.py +0 -0
  159. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +0 -0
  160. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +0 -0
  161. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/job_storage.py +0 -0
  162. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/large_file_system.py +0 -0
  163. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/mappings.py +0 -0
  164. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/job_worker/utils.py +0 -0
  165. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/socker_listener_thread.py +0 -0
  166. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/socket_sender_thread.py +0 -0
  167. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/utils.py +0 -0
  168. /pybiolib-1.2.1240/biolib/py.typed → /pybiolib-1.2.1630/biolib/compute_node/webserver/__init__.py +0 -0
  169. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/webserver/compute_node_results_proxy.py +0 -0
  170. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/webserver/gunicorn_flask_application.py +0 -0
  171. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/webserver/proxy_utils.py +0 -0
  172. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/webserver/webserver.py +0 -0
  173. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/webserver/webserver_types.py +0 -0
  174. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/webserver/webserver_utils.py +0 -0
  175. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/compute_node/webserver/worker_thread.py +0 -0
  176. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/jobs/__init__.py +0 -0
  177. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/runtime/__init__.py +0 -0
  178. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/tables.py +0 -0
  179. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/user/__init__.py +0 -0
  180. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/user/sign_in.py +0 -0
  181. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/utils/__init__.py +0 -0
  182. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/utils/multipart_uploader.py +0 -0
  183. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/utils/seq_util.py +0 -0
  184. {pybiolib-1.2.1240 → pybiolib-1.2.1630}/biolib/utils/zip/remote_zip.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pybiolib
3
- Version: 1.2.1240
3
+ Version: 1.2.1630
4
4
  Summary: BioLib Python Client
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,3 +1,4 @@
1
+ # ruff: noqa: I001
1
2
  # Imports to hide
2
3
  import os
3
4
  from urllib.parse import urlparse as _urlparse
@@ -15,6 +16,7 @@ from biolib.jobs.job import Result as _Result
15
16
  from biolib import user as _user
16
17
  from biolib.typing_utils import List, Optional, cast as _cast
17
18
  from biolib._data_record.data_record import DataRecord as _DataRecord
19
+ from biolib._internal.utils.job_url import parse_result_id_or_url as _parse_result_id_or_url
18
20
 
19
21
  import biolib.api
20
22
  import biolib.app
@@ -22,7 +24,6 @@ import biolib.cli
22
24
  import biolib.sdk
23
25
  import biolib.utils
24
26
 
25
-
26
27
  # ------------------------------------ Function definitions for public Python API ------------------------------------
27
28
 
28
29
 
@@ -83,43 +84,65 @@ def search(
83
84
 
84
85
 
85
86
  def get_job(job_id: str, job_token: Optional[str] = None) -> _Result:
86
- r"""Get a job by its ID.
87
+ r"""Get a job by its ID or full URL.
87
88
 
88
89
  Args:
89
- job_id (str): The UUID of the job to retrieve
90
+ job_id (str): The UUID of the job to retrieve, or a full URL to the job.
91
+ Can be either:
92
+ - Job UUID (e.g., 'abc123')
93
+ - Full URL (e.g., 'https://biolib.com/result/abc123/?token=xyz789')
94
+ - Full URL with token parameter (e.g., 'biolib.com/result/abc123/token=xyz789')
90
95
  job_token (str, optional): Authentication token for accessing the job.
91
96
  Only needed for jobs that aren't owned by the current user.
97
+ If the URL contains a token, this parameter is ignored.
92
98
 
93
99
  Returns:
94
100
  Job: The job object
95
101
 
96
102
  Example::
97
103
 
104
+ >>> # Get by UUID
98
105
  >>> job = biolib.get_job('abc123')
99
- >>> # Access shared job
106
+ >>> # Get with explicit token
100
107
  >>> job = biolib.get_job('abc123', job_token='xyz789')
108
+ >>> # Get by full URL with token
109
+ >>> job = biolib.get_job('https://biolib.com/result/abc123/?token=xyz789')
110
+ >>> # Get by URL with inline token format
111
+ >>> job = biolib.get_job('biolib.com/result/abc123/token=xyz789')
101
112
  """
102
- return _Result.create_from_uuid(uuid=job_id, auth_token=job_token)
113
+ uuid, token = _parse_result_id_or_url(job_id, job_token)
114
+ return _Result.create_from_uuid(uuid=uuid, auth_token=token)
103
115
 
104
116
 
105
117
  def get_result(result_id: str, result_token: Optional[str] = None) -> _Result:
106
- r"""Get a result by its ID.
118
+ r"""Get a result by its ID or full URL.
107
119
 
108
120
  Args:
109
- result_id (str): The UUID of the result to retrieve
121
+ result_id (str): The UUID of the result to retrieve, or a full URL to the result.
122
+ Can be either:
123
+ - Result UUID (e.g., 'abc123')
124
+ - Full URL (e.g., 'https://biolib.com/result/abc123/?token=xyz789')
125
+ - Full URL with token parameter (e.g., 'biolib.com/result/abc123/token=xyz789')
110
126
  result_token (str, optional): Authentication token for accessing the result.
111
- Only needed for result that aren't owned by the current user.
127
+ Only needed for results that aren't owned by the current user.
128
+ If the URL contains a token, this parameter is ignored.
112
129
 
113
130
  Returns:
114
131
  Result: The result object
115
132
 
116
133
  Example::
117
134
 
135
+ >>> # Get by UUID
118
136
  >>> result = biolib.get_result('abc123')
119
- >>> # Access shared result
137
+ >>> # Get with explicit token
120
138
  >>> result = biolib.get_result('abc123', result_token='xyz789')
139
+ >>> # Get by full URL with token
140
+ >>> result = biolib.get_result('https://biolib.com/result/abc123/?token=xyz789')
141
+ >>> # Get by URL with inline token format
142
+ >>> result = biolib.get_result('biolib.com/result/abc123/token=xyz789')
121
143
  """
122
- return _Result.create_from_uuid(uuid=result_id, auth_token=result_token)
144
+ uuid, token = _parse_result_id_or_url(result_id, result_token)
145
+ return _Result.create_from_uuid(uuid=uuid, auth_token=token)
123
146
 
124
147
 
125
148
  def get_data_record(uri: str) -> _DataRecord:
@@ -6,7 +6,6 @@ from struct import Struct
6
6
  from typing import Callable, Dict, Iterable, List, Optional, Union, cast
7
7
 
8
8
  from biolib import api
9
- from biolib._internal import types
10
9
  from biolib._internal.data_record import get_data_record_state_from_uri
11
10
  from biolib._internal.data_record.data_record import validate_sqlite_v1
12
11
  from biolib._internal.data_record.push_data import (
@@ -15,14 +14,15 @@ from biolib._internal.data_record.push_data import (
15
14
  )
16
15
  from biolib._internal.data_record.remote_storage_endpoint import DataRecordRemoteStorageEndpoint
17
16
  from biolib._internal.http_client import HttpClient
18
- from biolib._internal.types.file_node import ZipFileNodeDict
17
+ from biolib._shared import types
18
+ from biolib._shared.types import ZipFileNodeDict
19
+ from biolib._shared.utils import parse_resource_uri
19
20
  from biolib.api import client as api_client
20
21
  from biolib.biolib_api_client import BiolibApiClient
21
22
  from biolib.biolib_api_client.lfs_types import DataRecordInfo, DataRecordVersion, DataRecordVersionInfo
22
23
  from biolib.biolib_binary_format import LazyLoadedFile
23
24
  from biolib.biolib_binary_format.utils import RemoteIndexableBuffer
24
25
  from biolib.biolib_logging import logger
25
- from biolib.utils.app_uri import parse_app_uri
26
26
 
27
27
  PathFilter = Union[str, List[str], Callable[[str], bool]]
28
28
 
@@ -44,11 +44,11 @@ class DataRecord:
44
44
 
45
45
  @property
46
46
  def name(self) -> str:
47
- uri_parsed = parse_app_uri(self._state['resource_uri'], use_account_as_name_default=False)
48
- if not uri_parsed['app_name']:
47
+ uri_parsed = parse_resource_uri(self._state['resource_uri'], use_account_as_name_default=False)
48
+ if not uri_parsed['resource_name']:
49
49
  raise ValueError('Expected parameter "resource_uri" to contain resource name')
50
50
 
51
- return uri_parsed['app_name']
51
+ return uri_parsed['resource_name']
52
52
 
53
53
  def list_files(
54
54
  self,
@@ -142,8 +142,8 @@ class DataRecord:
142
142
  BiolibApiClient.assert_is_signed_in(authenticated_action_description='create a Data Record')
143
143
  if data_path is not None:
144
144
  assert os.path.isdir(data_path), f'The path "{data_path}" is not a directory.'
145
- uri_parsed = parse_app_uri(destination, use_account_as_name_default=False)
146
- if uri_parsed['app_name_normalized']:
145
+ uri_parsed = parse_resource_uri(destination, use_account_as_name_default=False)
146
+ if uri_parsed['resource_name_normalized']:
147
147
  data_record_uri = destination
148
148
  else:
149
149
  record_name = 'data-record-' + datetime.now().isoformat().split('.')[0].replace(':', '-')
@@ -173,10 +173,10 @@ class DataRecord:
173
173
  'resource_type': 'data-record',
174
174
  }
175
175
  if uri:
176
- uri_parsed = parse_app_uri(uri, use_account_as_name_default=False)
176
+ uri_parsed = parse_resource_uri(uri, use_account_as_name_default=False)
177
177
  params['account_handle'] = uri_parsed['account_handle_normalized']
178
- if uri_parsed['app_name_normalized']:
179
- params['app_name'] = uri_parsed['app_name_normalized']
178
+ if uri_parsed['resource_name_normalized']:
179
+ params['app_name'] = uri_parsed['resource_name_normalized']
180
180
 
181
181
  results = api_client.get(path='/apps/', params=params).json()['results']
182
182
  if count is None and len(results) == max_page_size:
@@ -0,0 +1,51 @@
1
+ import json
2
+ from typing import Any, Dict
3
+
4
+ from biolib import api
5
+ from biolib._index.types import IndexInfo
6
+ from biolib._internal.index import get_index_from_uri
7
+ from biolib.biolib_api_client import BiolibApiClient
8
+ from biolib.biolib_logging import logger
9
+
10
+
11
+ class Index:
12
+ def __init__(self, _internal_state: IndexInfo):
13
+ self._state = _internal_state
14
+
15
+ def __repr__(self) -> str:
16
+ return f'Index: {self._state["resource_uri"]}'
17
+
18
+ @property
19
+ def uri(self) -> str:
20
+ return self._state['resource_uri']
21
+
22
+ @property
23
+ def id(self) -> str:
24
+ return f"{self._state['group_uuid']}.{self._state['resource_uuid']}".replace("-", "_")
25
+
26
+ @staticmethod
27
+ def get_by_uri(uri: str) -> 'Index':
28
+ return Index(_internal_state=get_index_from_uri(uri))
29
+
30
+ @staticmethod
31
+ def create(uri: str, config: Dict[str, Any]) -> str:
32
+ BiolibApiClient.assert_is_signed_in(authenticated_action_description='create an Index')
33
+
34
+ response = api.client.post(
35
+ path='/resources/indexes/',
36
+ data={
37
+ 'uri': uri,
38
+ 'index_config': config,
39
+ },
40
+ )
41
+ result = response.json()
42
+ created_uri: str = result['uri']
43
+ logger.info(f"Successfully created Index '{created_uri}'")
44
+ return created_uri
45
+
46
+ @staticmethod
47
+ def create_from_config_file(uri: str, config_path: str) -> str:
48
+ with open(config_path) as config_file:
49
+ index_config = json.load(config_file)
50
+
51
+ return Index.create(uri=uri, config=index_config)
@@ -0,0 +1,7 @@
1
+ from typing import TypedDict
2
+
3
+
4
+ class IndexInfo(TypedDict):
5
+ resource_uri: str
6
+ resource_uuid: str
7
+ group_uuid: str
@@ -1,7 +1,7 @@
1
1
  import sqlite3
2
2
  from pathlib import Path
3
3
 
4
- from biolib._internal.types.data_record import SqliteV1DatabaseSchema
4
+ from biolib._shared.types import SqliteV1DatabaseSchema
5
5
  from biolib.api import client as api_client
6
6
  from biolib.biolib_api_client import AppGetResponse
7
7
  from biolib.biolib_api_client.biolib_app_api import _get_app_uri_from_str
@@ -2,9 +2,9 @@ import os
2
2
 
3
3
  from biolib import utils
4
4
  from biolib._internal.file_utils import get_files_and_size_of_directory, get_iterable_zip_stream
5
- from biolib._internal.types.typing import List, Optional, Tuple
6
5
  from biolib.biolib_errors import BioLibError
7
6
  from biolib.biolib_logging import logger
7
+ from biolib.typing_utils import List, Optional, Tuple
8
8
 
9
9
 
10
10
  def validate_data_path_and_get_files_and_size_of_directory(data_path: str) -> Tuple[List[str], int]:
@@ -1,5 +1,5 @@
1
1
  import os
2
- from datetime import datetime, timedelta
2
+ from datetime import datetime, timedelta, timezone
3
3
  from urllib.parse import urlparse
4
4
 
5
5
  from biolib.api import client as api_client
@@ -16,7 +16,7 @@ class DataRecordRemoteStorageEndpoint(RemoteEndpoint):
16
16
  self._presigned_url: Optional[str] = None
17
17
 
18
18
  def get_remote_url(self) -> str:
19
- if not self._presigned_url or not self._expires_at or datetime.utcnow() > self._expires_at:
19
+ if not self._presigned_url or not self._expires_at or datetime.now(timezone.utc) > self._expires_at:
20
20
  lfs_version: DataRecordVersion = api_client.get(
21
21
  path=f'/lfs/versions/{self._resource_version_uuid}/',
22
22
  ).json()
@@ -29,7 +29,7 @@ class DataRecordRemoteStorageEndpoint(RemoteEndpoint):
29
29
  else:
30
30
  self._presigned_url = lfs_version['presigned_download_url']
31
31
 
32
- self._expires_at = datetime.utcnow() + timedelta(minutes=8)
32
+ self._expires_at = datetime.now(timezone.utc) + timedelta(minutes=8)
33
33
  logger.debug(
34
34
  f'DataRecord "{self._resource_version_uuid}" fetched presigned URL '
35
35
  f'with expiry at {self._expires_at.isoformat()}'
@@ -1,6 +1,7 @@
1
1
  import hashlib
2
2
  import io
3
3
  import os
4
+ import posixpath
4
5
  import zipfile as zf
5
6
  from pathlib import Path
6
7
 
@@ -114,9 +115,11 @@ def path_to_renamed_path(path_str: str, prefix_with_slash: bool = True) -> str:
114
115
 
115
116
  if prefix_with_slash:
116
117
  if not result.startswith('/'):
117
- return '/' + result
118
- return result
118
+ result = '/' + result
119
+ # Normalize to handle cases like '/./mydir' -> '/mydir' and remove trailing slashes.
120
+ # Required because downstream Mappings class does exact string-prefix matching.
121
+ return posixpath.normpath(result)
119
122
  else:
120
123
  if result.startswith('/'):
121
- return result[1:]
122
- return result
124
+ result = result[1:]
125
+ return posixpath.normpath(result)
@@ -0,0 +1 @@
1
+ from .index import get_index_from_uri
@@ -0,0 +1,18 @@
1
+ from typing import Any, Dict
2
+
3
+ from biolib._index.types import IndexInfo
4
+ from biolib.api import client as api_client
5
+ from biolib.biolib_api_client.biolib_app_api import _get_app_uri_from_str
6
+
7
+
8
+ def get_index_from_uri(uri: str) -> IndexInfo:
9
+ normalized_uri = _get_app_uri_from_str(uri)
10
+ app_response: Dict[str, Any] = api_client.get(path='/app/', params={'uri': normalized_uri}).json()
11
+ resource_uri = app_response['app_version']['app_uri']
12
+ if app_response['app']['type'] != 'index':
13
+ raise Exception(f'Resource "{resource_uri}" is not an Index')
14
+ return IndexInfo(
15
+ resource_uri=app_response['app_version']['app_uri'],
16
+ resource_uuid=app_response['app']['public_id'],
17
+ group_uuid=app_response['app']['group_uuid'],
18
+ )
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  import subprocess
3
- from datetime import datetime, timedelta
3
+ from datetime import datetime, timedelta, timezone
4
4
 
5
5
  from biolib.biolib_logging import logger_no_user_data
6
6
  from biolib.compute_node.job_worker.cache_state import LfsCacheState
@@ -9,7 +9,7 @@ from biolib.compute_node.job_worker.cache_state import LfsCacheState
9
9
  def prune_lfs_cache(dry_run: bool) -> None:
10
10
  logger_no_user_data.info(f'Pruning LFS cache (dry run = {dry_run})...')
11
11
 
12
- current_time = datetime.utcnow()
12
+ current_time = datetime.now(timezone.utc)
13
13
  paths_to_delete = set()
14
14
 
15
15
  with LfsCacheState() as state:
@@ -24,6 +24,8 @@ def prune_lfs_cache(dry_run: bool) -> None:
24
24
  lfs_uuids_to_keep_in_state = set()
25
25
  for lfs_uuid, lfs in state['large_file_systems'].items():
26
26
  last_used_at = datetime.fromisoformat(lfs['last_used_at'])
27
+ if last_used_at.tzinfo is None:
28
+ last_used_at = last_used_at.replace(tzinfo=timezone.utc)
27
29
  lfs_time_to_live_in_days = 60 if lfs['state'] == 'ready' else 7
28
30
 
29
31
  if last_used_at < current_time - timedelta(days=lfs_time_to_live_in_days):
@@ -15,14 +15,14 @@ from biolib._internal.data_record.push_data import (
15
15
  )
16
16
  from biolib._internal.errors import AuthenticationError
17
17
  from biolib._internal.file_utils import get_files_and_size_of_directory, get_iterable_zip_stream
18
- from biolib._internal.types.push import PushResponseDict
18
+ from biolib._shared.types import PushResponseDict
19
+ from biolib._shared.utils import parse_resource_uri
19
20
  from biolib.biolib_api_client import BiolibApiClient
20
21
  from biolib.biolib_api_client.biolib_app_api import BiolibAppApi
21
22
  from biolib.biolib_docker_client import BiolibDockerClient
22
23
  from biolib.biolib_errors import BioLibError
23
24
  from biolib.biolib_logging import logger
24
- from biolib.typing_utils import Iterable, Optional, Set, TypedDict
25
- from biolib.utils.app_uri import parse_app_uri
25
+ from biolib.typing_utils import Dict, Iterable, Optional, Set, TypedDict, Union
26
26
 
27
27
  REGEX_MARKDOWN_INLINE_IMAGE = re.compile(r'!\[(?P<alt>.*)\]\((?P<src>.*)\)')
28
28
 
@@ -109,8 +109,10 @@ def _process_docker_status_updates_with_progress_bar(status_updates: Iterable[Do
109
109
 
110
110
 
111
111
  def _process_docker_status_updates_with_logging(status_updates: Iterable[DockerStatusUpdate], action: str) -> None:
112
- layer_progress = {}
113
- layer_status = {}
112
+ layer_progress: Dict[str, float] = {}
113
+ layer_status: Dict[str, str] = {}
114
+ layer_details: Dict[str, Dict[str, int]] = {}
115
+ layer_bytes_at_last_log: Dict[str, int] = {}
114
116
  last_log_time = time.time()
115
117
 
116
118
  logger.info(f'{action} Docker image...')
@@ -128,6 +130,7 @@ def _process_docker_status_updates_with_logging(status_updates: Iterable[DockerS
128
130
  percentage = (current / total * 100) if total > 0 else 0
129
131
  layer_progress[layer_id] = percentage
130
132
  layer_status[layer_id] = f'{action.lower()}'
133
+ layer_details[layer_id] = {'current': current, 'total': total}
131
134
  elif update.get('status') == 'Layer already exists':
132
135
  layer_progress[layer_id] = 100
133
136
  layer_status[layer_id] = 'already exists'
@@ -146,16 +149,33 @@ def _process_docker_status_updates_with_logging(status_updates: Iterable[DockerS
146
149
  logger.info(f'{action} Docker image - {status}')
147
150
 
148
151
  if current_time - last_log_time >= 10.0:
149
- _log_progress_summary(action, layer_progress, layer_status)
152
+ _log_progress_summary(
153
+ action,
154
+ layer_progress,
155
+ layer_status,
156
+ layer_details,
157
+ layer_bytes_at_last_log,
158
+ current_time - last_log_time,
159
+ )
160
+ layer_bytes_at_last_log = {lid: details['current'] for lid, details in layer_details.items()}
150
161
  last_log_time = current_time
151
162
 
152
- _log_progress_summary(action, layer_progress, layer_status)
163
+ _log_progress_summary(
164
+ action, layer_progress, layer_status, layer_details, layer_bytes_at_last_log, time.time() - last_log_time
165
+ )
153
166
  if action == 'Pushing':
154
167
  logger.info('Pushing final image manifest...')
155
168
  logger.info(f'{action} Docker image completed')
156
169
 
157
170
 
158
- def _log_progress_summary(action: str, layer_progress: dict, layer_status: dict) -> None:
171
+ def _log_progress_summary(
172
+ action: str,
173
+ layer_progress: Dict[str, float],
174
+ layer_status: Dict[str, str],
175
+ layer_details: Dict[str, Dict[str, int]],
176
+ layer_bytes_at_last_log: Dict[str, int],
177
+ time_delta: float,
178
+ ) -> None:
159
179
  if not layer_progress and not layer_status:
160
180
  return
161
181
 
@@ -174,7 +194,36 @@ def _log_progress_summary(action: str, layer_progress: dict, layer_status: dict)
174
194
  if status in ['preparing', 'waiting', 'pushing', 'uploading'] and layer_progress.get(layer_id, 0) < 100
175
195
  ]
176
196
 
177
- if active_layers:
197
+ if active_layers and layer_details:
198
+ total_bytes_transferred = 0
199
+ layer_info_parts = []
200
+
201
+ for layer_id in active_layers[:5]:
202
+ if layer_id in layer_details:
203
+ details = layer_details[layer_id]
204
+ current = details['current']
205
+ total = details['total']
206
+ percentage = layer_progress.get(layer_id, 0)
207
+
208
+ bytes_since_last = current - layer_bytes_at_last_log.get(layer_id, 0)
209
+ total_bytes_transferred += bytes_since_last
210
+
211
+ current_mb = current / (1024 * 1024)
212
+ total_mb = total / (1024 * 1024)
213
+ layer_info_parts.append(f'{layer_id}: {current_mb:.1f}/{total_mb:.1f} MB ({percentage:.1f}%)')
214
+
215
+ speed_info = ''
216
+ if time_delta > 0 and total_bytes_transferred > 0:
217
+ speed_mbps = (total_bytes_transferred / (1024 * 1024)) / time_delta
218
+ speed_info = f' @ {speed_mbps:.2f} MB/s'
219
+
220
+ more_layers_info = ''
221
+ if len(active_layers) > 5:
222
+ more_layers_info = f' (+ {len(active_layers) - 5} more)'
223
+
224
+ if layer_info_parts:
225
+ logger.info(f'Active layers: {", ".join(layer_info_parts)}{speed_info}{more_layers_info}')
226
+ elif active_layers:
178
227
  logger.info(f'Active layers: {", ".join(active_layers[:5])}{"..." if len(active_layers) > 5 else ""}')
179
228
 
180
229
 
@@ -196,13 +245,12 @@ def push_application(
196
245
  set_as_published: bool,
197
246
  dry_run: bool = False,
198
247
  ) -> Optional[PushResponseDict]:
199
- parsed_uri = parse_app_uri(app_uri)
200
- app_name = parsed_uri['app_name']
248
+ app_uri = app_uri.rstrip('/')
249
+ parsed_uri = parse_resource_uri(app_uri)
250
+ resource_name = parsed_uri['resource_name']
201
251
 
202
- app_uri_prefix = (
203
- f"@{parsed_uri['resource_name_prefix']}/" if parsed_uri['resource_name_prefix'] != 'biolib.com' else ''
204
- )
205
- app_uri_to_fetch = f"{app_uri_prefix}{parsed_uri['account_handle_normalized']}/{app_name}"
252
+ app_uri_prefix = f"@{parsed_uri['resource_prefix']}/" if parsed_uri['resource_prefix'] is not None else ''
253
+ app_uri_to_fetch = f"{app_uri_prefix}{parsed_uri['account_handle_normalized']}/{resource_name}"
206
254
 
207
255
  version = parsed_uri['version']
208
256
  semantic_version = f"{version['major']}.{version['minor']}.{version['patch']}" if version else None
@@ -337,10 +385,6 @@ def push_application(
337
385
  app_response = BiolibAppApi.get_by_uri(app_uri_to_fetch)
338
386
  app = app_response['app']
339
387
 
340
- if app_data and not app['allow_client_side_execution'] and 'app_data' in config:
341
- raise BioLibError(
342
- 'To push a version with app_data the app must be set to "Allow Client-Side Source Code Access"'
343
- )
344
388
  if dry_run:
345
389
  logger.info('Successfully completed dry-run. No new version was pushed.')
346
390
  return None
@@ -426,9 +470,15 @@ def push_application(
426
470
  logger.info(f'Successfully pushed {docker_image_name}')
427
471
 
428
472
  app_version_uuid = new_app_version_json['public_id']
473
+ complete_push_data: Dict[str, Union[bool, str]] = {
474
+ 'set_as_active': set_as_active,
475
+ 'set_as_published': set_as_published,
476
+ }
477
+ if parsed_uri['tag']:
478
+ complete_push_data['tag'] = parsed_uri['tag']
429
479
  api.client.post(
430
480
  path=f'/app-versions/{app_version_uuid}/complete-push/',
431
- data={'set_as_active': set_as_active, 'set_as_published': set_as_published},
481
+ data=complete_push_data,
432
482
  )
433
483
 
434
484
  sematic_version = f"{new_app_version_json['major']}.{new_app_version_json['minor']}.{new_app_version_json['patch']}"
@@ -0,0 +1,53 @@
1
+ import { useState, useEffect } from "react";
2
+ import biolib from "./biolib-sdk";
3
+
4
+ export default function App() {
5
+ const [outputFileData, setOutputFileData] = useState<Uint8Array | null>(null);
6
+ const [loading, setLoading] = useState(true);
7
+
8
+ const loadOutputData = async () => {
9
+ setLoading(true);
10
+ try {
11
+ const data = await biolib.getOutputFileData("output.json");
12
+ setOutputFileData(data);
13
+ } catch (error) {
14
+ console.error("Error loading output data:", error);
15
+ setOutputFileData(null);
16
+ } finally {
17
+ setLoading(false);
18
+ }
19
+ };
20
+
21
+ useEffect(() => {
22
+ loadOutputData();
23
+ }, []);
24
+
25
+ return (
26
+ <div className="min-h-screen bg-gray-100 flex items-center justify-center">
27
+ <div className="text-center max-w-2xl mx-auto p-8">
28
+ <h1 className="text-4xl font-bold mb-4">
29
+ Hello, BioLib!
30
+ </h1>
31
+ <p className="text-lg mb-2">
32
+ You have successfully set up your BioLib GUI application.
33
+ </p>
34
+ <p className="italic mb-6">
35
+ This is a simple React template with Tailwind CSS styling.
36
+ </p>
37
+
38
+ <div className="mt-8 p-4 bg-white rounded-lg shadow">
39
+ <h2 className="text-xl font-semibold mb-4">Example: Reading Output Files</h2>
40
+ {loading ? (
41
+ <p className="text-gray-500">Loading output.json...</p>
42
+ ) : outputFileData ? (
43
+ <div className="p-3 bg-gray-50 rounded text-left">
44
+ <pre className="text-sm">{new TextDecoder().decode(outputFileData)}</pre>
45
+ </div>
46
+ ) : (
47
+ <p className="text-red-500">Failed to load output.json</p>
48
+ )}
49
+ </div>
50
+ </div>
51
+ </div>
52
+ );
53
+ }
@@ -1,3 +1,5 @@
1
+ # syntax=docker/dockerfile:1
2
+
1
3
  FROM node:24.4.1-alpine3.21 AS gui_builder
2
4
 
3
5
  WORKDIR /home/biolib/
@@ -0,0 +1,37 @@
1
+ interface IBioLibGlobals {
2
+ getOutputFileData: (path: string) => Promise<Uint8Array>;
3
+ }
4
+
5
+ declare global {
6
+ const biolib: IBioLibGlobals;
7
+ }
8
+
9
+ // DO NOT MODIFY: Development data files are injected at build time from gui/dev-data/ folder
10
+ const DEV_DATA_FILES: Record<string, string> = {};
11
+
12
+ const devSdkBioLib: IBioLibGlobals = {
13
+ getOutputFileData: async (path: string): Promise<Uint8Array> => {
14
+ console.log(`[SDK] getOutputFileData called with path: ${path}`);
15
+
16
+ const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
17
+
18
+ if (typeof DEV_DATA_FILES !== 'undefined' && normalizedPath in DEV_DATA_FILES) {
19
+ const base64Data = DEV_DATA_FILES[normalizedPath];
20
+ const binaryString = atob(base64Data);
21
+ const bytes = new Uint8Array(binaryString.length);
22
+ for (let i = 0; i < binaryString.length; i++) {
23
+ bytes[i] = binaryString.charCodeAt(i);
24
+ }
25
+ return bytes;
26
+ }
27
+
28
+ throw new Error(`File not found: ${path}. Add this file to the dev-data/ folder for local development.`);
29
+ },
30
+ };
31
+
32
+ const biolib: IBioLibGlobals =
33
+ process.env.NODE_ENV === "development"
34
+ ? devSdkBioLib
35
+ : (window as any).biolib;
36
+
37
+ export default biolib;
@@ -0,0 +1,7 @@
1
+ {
2
+ "message": "Example JSON data for development",
3
+ "results": [
4
+ { "id": 1, "value": "Sample result 1" },
5
+ { "id": 2, "value": "Sample result 2" }
6
+ ]
7
+ }
@@ -14,6 +14,7 @@
14
14
  },
15
15
  "devDependencies": {
16
16
  "@tailwindcss/vite": "4.0.14",
17
+ "@types/node": "20.17.10",
17
18
  "@types/react": "18.3.3",
18
19
  "@types/react-dom": "18.3.0",
19
20
  "@vitejs/plugin-react": "4.2.1",