pybiolib 1.2.911__py3-none-any.whl → 1.2.1642__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.

Potentially problematic release.


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

Files changed (113) hide show
  1. biolib/__init__.py +33 -10
  2. biolib/_data_record/data_record.py +24 -11
  3. biolib/_index/index.py +51 -0
  4. biolib/_index/types.py +7 -0
  5. biolib/_internal/add_copilot_prompts.py +3 -5
  6. biolib/_internal/add_gui_files.py +59 -0
  7. biolib/_internal/data_record/data_record.py +1 -1
  8. biolib/_internal/data_record/push_data.py +1 -1
  9. biolib/_internal/data_record/remote_storage_endpoint.py +3 -3
  10. biolib/_internal/file_utils.py +48 -0
  11. biolib/_internal/index/__init__.py +1 -0
  12. biolib/_internal/index/index.py +18 -0
  13. biolib/_internal/lfs/cache.py +4 -2
  14. biolib/_internal/push_application.py +89 -23
  15. biolib/_internal/runtime.py +2 -0
  16. biolib/_internal/string_utils.py +13 -0
  17. biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
  18. biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
  19. biolib/_internal/templates/gui_template/.yarnrc.yml +1 -0
  20. biolib/_internal/templates/gui_template/App.tsx +53 -0
  21. biolib/_internal/templates/gui_template/Dockerfile +28 -0
  22. biolib/_internal/templates/gui_template/biolib-sdk.ts +37 -0
  23. biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
  24. biolib/_internal/templates/gui_template/index.css +5 -0
  25. biolib/_internal/templates/gui_template/index.html +13 -0
  26. biolib/_internal/templates/gui_template/index.tsx +10 -0
  27. biolib/_internal/templates/gui_template/package.json +27 -0
  28. biolib/_internal/templates/gui_template/tsconfig.json +24 -0
  29. biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +49 -0
  30. biolib/_internal/templates/gui_template/vite.config.mts +9 -0
  31. biolib/_internal/templates/init_template/.biolib/config.yml +1 -0
  32. biolib/_internal/templates/init_template/.github/workflows/biolib.yml +6 -1
  33. biolib/_internal/templates/init_template/Dockerfile +2 -0
  34. biolib/_internal/templates/init_template/run.sh +1 -0
  35. biolib/_internal/templates/templates.py +9 -1
  36. biolib/_internal/utils/__init__.py +25 -0
  37. biolib/_internal/utils/job_url.py +33 -0
  38. biolib/_internal/utils/multinode.py +12 -14
  39. biolib/_runtime/runtime.py +15 -2
  40. biolib/_session/session.py +7 -5
  41. biolib/_shared/__init__.py +0 -0
  42. biolib/_shared/types/__init__.py +69 -0
  43. biolib/_shared/types/account.py +12 -0
  44. biolib/_shared/types/account_member.py +8 -0
  45. biolib/{_internal → _shared}/types/experiment.py +1 -0
  46. biolib/_shared/types/resource.py +17 -0
  47. biolib/_shared/types/resource_deploy_key.py +11 -0
  48. biolib/{_internal → _shared}/types/resource_permission.py +1 -1
  49. biolib/{_internal → _shared}/types/user.py +5 -5
  50. biolib/_shared/utils/__init__.py +7 -0
  51. biolib/_shared/utils/resource_uri.py +75 -0
  52. biolib/api/client.py +1 -1
  53. biolib/app/app.py +96 -45
  54. biolib/biolib_api_client/app_types.py +1 -0
  55. biolib/biolib_api_client/biolib_app_api.py +26 -0
  56. biolib/biolib_binary_format/module_input.py +8 -0
  57. biolib/biolib_binary_format/remote_endpoints.py +3 -3
  58. biolib/biolib_binary_format/remote_stream_seeker.py +39 -25
  59. biolib/biolib_logging.py +1 -1
  60. biolib/cli/__init__.py +2 -1
  61. biolib/cli/auth.py +4 -16
  62. biolib/cli/data_record.py +17 -0
  63. biolib/cli/index.py +32 -0
  64. biolib/cli/init.py +93 -11
  65. biolib/cli/lfs.py +1 -1
  66. biolib/cli/run.py +1 -1
  67. biolib/cli/start.py +14 -1
  68. biolib/compute_node/job_worker/executors/docker_executor.py +31 -9
  69. biolib/compute_node/job_worker/executors/docker_types.py +1 -1
  70. biolib/compute_node/job_worker/executors/types.py +6 -5
  71. biolib/compute_node/job_worker/job_storage.py +2 -1
  72. biolib/compute_node/job_worker/job_worker.py +155 -90
  73. biolib/compute_node/job_worker/large_file_system.py +2 -6
  74. biolib/compute_node/job_worker/network_alloc.py +99 -0
  75. biolib/compute_node/job_worker/network_buffer.py +240 -0
  76. biolib/compute_node/job_worker/utilization_reporter_thread.py +2 -2
  77. biolib/compute_node/remote_host_proxy.py +135 -67
  78. biolib/compute_node/utils.py +2 -0
  79. biolib/compute_node/webserver/compute_node_results_proxy.py +188 -0
  80. biolib/compute_node/webserver/proxy_utils.py +28 -0
  81. biolib/compute_node/webserver/webserver.py +64 -19
  82. biolib/experiments/experiment.py +98 -16
  83. biolib/jobs/job.py +128 -31
  84. biolib/jobs/job_result.py +73 -33
  85. biolib/jobs/types.py +1 -0
  86. biolib/sdk/__init__.py +17 -2
  87. biolib/typing_utils.py +1 -1
  88. biolib/utils/cache_state.py +2 -2
  89. biolib/utils/seq_util.py +1 -1
  90. {pybiolib-1.2.911.dist-info → pybiolib-1.2.1642.dist-info}/METADATA +4 -2
  91. pybiolib-1.2.1642.dist-info/RECORD +180 -0
  92. {pybiolib-1.2.911.dist-info → pybiolib-1.2.1642.dist-info}/WHEEL +1 -1
  93. biolib/_internal/llm_instructions/.github/instructions/style-react-ts.instructions.md +0 -22
  94. biolib/_internal/types/__init__.py +0 -6
  95. biolib/_internal/types/account.py +0 -10
  96. biolib/utils/app_uri.py +0 -57
  97. pybiolib-1.2.911.dist-info/RECORD +0 -150
  98. /biolib/{_internal/llm_instructions → _index}/__init__.py +0 -0
  99. /biolib/_internal/{llm_instructions → templates/copilot_template}/.github/instructions/general-app-knowledge.instructions.md +0 -0
  100. /biolib/_internal/{llm_instructions → templates/copilot_template}/.github/instructions/style-general.instructions.md +0 -0
  101. /biolib/_internal/{llm_instructions → templates/copilot_template}/.github/instructions/style-python.instructions.md +0 -0
  102. /biolib/_internal/{llm_instructions → templates/copilot_template}/.github/prompts/biolib_app_inputs.prompt.md +0 -0
  103. /biolib/_internal/{llm_instructions → templates/copilot_template}/.github/prompts/biolib_run_apps.prompt.md +0 -0
  104. /biolib/{_internal → _shared}/types/app.py +0 -0
  105. /biolib/{_internal → _shared}/types/data_record.py +0 -0
  106. /biolib/{_internal → _shared}/types/file_node.py +0 -0
  107. /biolib/{_internal → _shared}/types/push.py +0 -0
  108. /biolib/{_internal/types/resource.py → _shared/types/resource_types.py} +0 -0
  109. /biolib/{_internal → _shared}/types/resource_version.py +0 -0
  110. /biolib/{_internal → _shared}/types/result.py +0 -0
  111. /biolib/{_internal → _shared}/types/typing.py +0 -0
  112. {pybiolib-1.2.911.dist-info → pybiolib-1.2.1642.dist-info}/entry_points.txt +0 -0
  113. {pybiolib-1.2.911.dist-info → pybiolib-1.2.1642.dist-info/licenses}/LICENSE +0 -0
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import os
2
3
  import re
3
4
  import sys
@@ -14,14 +15,14 @@ from biolib._internal.data_record.push_data import (
14
15
  )
15
16
  from biolib._internal.errors import AuthenticationError
16
17
  from biolib._internal.file_utils import get_files_and_size_of_directory, get_iterable_zip_stream
17
- from biolib._internal.types.push import PushResponseDict
18
+ from biolib._shared.types import PushResponseDict
19
+ from biolib._shared.utils import parse_resource_uri
18
20
  from biolib.biolib_api_client import BiolibApiClient
19
21
  from biolib.biolib_api_client.biolib_app_api import BiolibAppApi
20
22
  from biolib.biolib_docker_client import BiolibDockerClient
21
23
  from biolib.biolib_errors import BioLibError
22
24
  from biolib.biolib_logging import logger
23
- from biolib.typing_utils import Iterable, Optional, Set, TypedDict
24
- from biolib.utils.app_uri import parse_app_uri
25
+ from biolib.typing_utils import Dict, Iterable, Optional, Set, TypedDict, Union
25
26
 
26
27
  REGEX_MARKDOWN_INLINE_IMAGE = re.compile(r'!\[(?P<alt>.*)\]\((?P<src>.*)\)')
27
28
 
@@ -108,8 +109,10 @@ def _process_docker_status_updates_with_progress_bar(status_updates: Iterable[Do
108
109
 
109
110
 
110
111
  def _process_docker_status_updates_with_logging(status_updates: Iterable[DockerStatusUpdate], action: str) -> None:
111
- layer_progress = {}
112
- 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] = {}
113
116
  last_log_time = time.time()
114
117
 
115
118
  logger.info(f'{action} Docker image...')
@@ -127,6 +130,7 @@ def _process_docker_status_updates_with_logging(status_updates: Iterable[DockerS
127
130
  percentage = (current / total * 100) if total > 0 else 0
128
131
  layer_progress[layer_id] = percentage
129
132
  layer_status[layer_id] = f'{action.lower()}'
133
+ layer_details[layer_id] = {'current': current, 'total': total}
130
134
  elif update.get('status') == 'Layer already exists':
131
135
  layer_progress[layer_id] = 100
132
136
  layer_status[layer_id] = 'already exists'
@@ -145,16 +149,33 @@ def _process_docker_status_updates_with_logging(status_updates: Iterable[DockerS
145
149
  logger.info(f'{action} Docker image - {status}')
146
150
 
147
151
  if current_time - last_log_time >= 10.0:
148
- _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()}
149
161
  last_log_time = current_time
150
162
 
151
- _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
+ )
152
166
  if action == 'Pushing':
153
167
  logger.info('Pushing final image manifest...')
154
168
  logger.info(f'{action} Docker image completed')
155
169
 
156
170
 
157
- 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:
158
179
  if not layer_progress and not layer_status:
159
180
  return
160
181
 
@@ -173,7 +194,36 @@ def _log_progress_summary(action: str, layer_progress: dict, layer_status: dict)
173
194
  if status in ['preparing', 'waiting', 'pushing', 'uploading'] and layer_progress.get(layer_id, 0) < 100
174
195
  ]
175
196
 
176
- 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:
177
227
  logger.info(f'Active layers: {", ".join(active_layers[:5])}{"..." if len(active_layers) > 5 else ""}')
178
228
 
179
229
 
@@ -195,13 +245,12 @@ def push_application(
195
245
  set_as_published: bool,
196
246
  dry_run: bool = False,
197
247
  ) -> Optional[PushResponseDict]:
198
- parsed_uri = parse_app_uri(app_uri)
199
- 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']
200
251
 
201
- app_uri_prefix = (
202
- f"@{parsed_uri['resource_name_prefix']}/" if parsed_uri['resource_name_prefix'] != 'biolib.com' else ''
203
- )
204
- 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}"
205
254
 
206
255
  version = parsed_uri['version']
207
256
  semantic_version = f"{version['major']}.{version['minor']}.{version['patch']}" if version else None
@@ -238,19 +287,34 @@ def push_application(
238
287
  app_data_path: Optional[Path] = None
239
288
  try:
240
289
  with open(config_yml_path) as config_yml_file:
241
- config = yaml.safe_load(config_yml_file.read())
290
+ try:
291
+ config = json.loads(json.dumps(yaml.safe_load(config_yml_file.read())))
292
+ except (TypeError, ValueError) as e:
293
+ raise BioLibError(
294
+ f'The .biolib/config.yml file contains data types that are not supported '
295
+ f'(must be JSON-serializable). Please ensure only standard JSON types '
296
+ f'(str, int, float, bool, list, dict, null) are used. Original error: {e}'
297
+ ) from e
298
+
299
+ if 'assets' in config and 'app_data' not in config:
300
+ config['app_data'] = config.pop('assets')
301
+ elif 'assets' in config and 'app_data' in config:
302
+ raise BioLibError(
303
+ 'In .biolib/config.yml you cannot specify both "app_data" and "assets" fields. Please use only one.'
304
+ )
242
305
 
243
306
  app_data = config.get('app_data')
244
307
  if app_data:
308
+ field_name = 'app_data' if 'app_data' in config else 'assets'
245
309
  if not isinstance(app_data, str):
246
310
  raise BioLibError(
247
- f'In .biolib/config.yml the value of "app_data" must be a string but got {type(app_data)}'
311
+ f'In .biolib/config.yml the value of "{field_name}" must be a string but got {type(app_data)}'
248
312
  )
249
313
 
250
314
  app_data_path = app_path_absolute.joinpath(app_data).resolve()
251
315
  if not app_data_path.is_dir():
252
316
  raise BioLibError(
253
- 'In .biolib/config.yml the value of "app_data" must be a path to a directory '
317
+ f'In .biolib/config.yml the value of "{field_name}" must be a path to a directory '
254
318
  'in the application directory'
255
319
  )
256
320
 
@@ -321,10 +385,6 @@ def push_application(
321
385
  app_response = BiolibAppApi.get_by_uri(app_uri_to_fetch)
322
386
  app = app_response['app']
323
387
 
324
- if app_data and not app['allow_client_side_execution']:
325
- raise BioLibError(
326
- 'To push a version with app_data the app must be set to "Allow Client-Side Source Code Access"'
327
- )
328
388
  if dry_run:
329
389
  logger.info('Successfully completed dry-run. No new version was pushed.')
330
390
  return None
@@ -410,9 +470,15 @@ def push_application(
410
470
  logger.info(f'Successfully pushed {docker_image_name}')
411
471
 
412
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']
413
479
  api.client.post(
414
480
  path=f'/app-versions/{app_version_uuid}/complete-push/',
415
- data={'set_as_active': set_as_active, 'set_as_published': set_as_published},
481
+ data=complete_push_data,
416
482
  )
417
483
 
418
484
  sematic_version = f"{new_app_version_json['major']}.{new_app_version_json['minor']}.{new_app_version_json['patch']}"
@@ -4,10 +4,12 @@ from biolib.typing_utils import TypedDict
4
4
  class RuntimeJobDataDict(TypedDict):
5
5
  version: str
6
6
  job_requested_machine: str
7
+ job_requested_machine_spot: bool
7
8
  job_uuid: str
8
9
  job_auth_token: str
9
10
  app_uri: str
10
11
  is_environment_biolib_cloud: bool
12
+ job_reserved_machines: int
11
13
 
12
14
 
13
15
  class BioLibRuntimeError(Exception):
@@ -0,0 +1,13 @@
1
+ import re
2
+
3
+
4
+ def normalize_for_docker_tag(name: str) -> str:
5
+ if not name:
6
+ return ''
7
+
8
+ normalized = re.sub(r'[^a-z0-9-]', '-', name.lower())
9
+
10
+ normalized = re.sub(r'-+', '-', normalized)
11
+ normalized = normalized.strip('-')
12
+
13
+ return normalized
@@ -0,0 +1,47 @@
1
+ ---
2
+ applyTo: "**/*.ts,**/*.tsx,**/package.json,**/vite.config.*,**/.yarnrc.yml,**/yarn.lock,**/gui/**"
3
+ ---
4
+
5
+ Apply the [general coding guidelines](./style-general.instructions.md) to all code.
6
+
7
+ # General Project Guidelines
8
+ - Prefer using `export default function` over exporting at the end of the file.
9
+
10
+ # Package Management
11
+ - **Always use yarn instead of npm** for all package management operations
12
+ - Use `yarn install` instead of `npm install`
13
+ - Use `yarn add <package>` instead of `npm install <package>`
14
+ - Use `yarn remove <package>` instead of `npm uninstall <package>`
15
+ - Use `yarn dev` instead of `npm run dev`
16
+ - Use `yarn build` instead of `npm run build`
17
+
18
+ # Build Process
19
+ - BioLib GUI projects use Vite for building and development
20
+ - The build process compiles TypeScript and React into a single HTML file
21
+ - Always run `yarn build` to create the production build before deployment
22
+ - Use `yarn dev` for local development with hot reloading
23
+
24
+ # Configuration Files
25
+ - Respect the `.yarnrc.yml` configuration for yarn settings
26
+ - The `package.json` should specify `"packageManager": "yarn@4.6.0"` or similar
27
+ - Never modify yarn.lock manually - let yarn manage it automatically
28
+
29
+ # Dependencies
30
+ - Add new dependencies using `yarn add <package>` for runtime dependencies
31
+ - Add development dependencies using `yarn add -D <package>`
32
+ - Keep dependencies up to date but test thoroughly after updates
33
+
34
+ # TypeScript Guidelines
35
+ - Use TypeScript for all new code
36
+ - Follow functional programming principles where possible
37
+ - Use interfaces for data structures prefixed with I like `interface IRecord`
38
+ - Prefer immutable data (const, readonly)
39
+ - Use optional chaining (?.) and nullish coalescing (??) operators
40
+
41
+ # React Guidelines
42
+ - Use functional components with hooks
43
+ - Follow the React hooks rules (no conditional hooks)
44
+ - Prefer one component per file
45
+ - Use Tailwindcss for styling
46
+ - Extract props in components with object destructuring like `const { prop1, prop2 } = props;`
47
+ - Instantiate functional components with props like `export default function MyComponent(props: IProps) { ... }`.
@@ -0,0 +1,19 @@
1
+ ---
2
+ mode: 'agent'
3
+ tools: ['githubRepo', 'codebase']
4
+ description: 'Handle onboarding and implementing code from a GitHub repository into a biolib application, with focus on creating easily editable and maintainable code structure.'
5
+ ---
6
+
7
+ # Main task
8
+ Your task is to help onboard and implement code from a GitHub repository into a biolib application. This involves understanding the repository structure, implementing the core functionality, and ensuring the code is easily editable for future iterations.
9
+ Generally, you can do this by adding the repository into an src folder as a submodule, and reading the README.md file to understand how to run the code.
10
+ You will then call the relevant functions or classes from the cloned repository in your biolib application
11
+
12
+ ## Key requirements:
13
+ - Always ask the user for the GitHub repository if not already provided. Inform them that it needs to be in the format `author/repo_name`.
14
+ - Use the #githubRepo tool to examine the repository structure, README, and key files to understand the project.
15
+ - Focus on creating code that is easily editable and maintainable, as it's likely the implementation won't be perfect on the first attempt.
16
+ - Structure the code in a modular way that allows for easy modifications and improvements.
17
+ - Include clear comments for complex logic, but avoid over-commenting obvious code.
18
+ - Follow the existing biolib application patterns and conventions.
19
+ - Ensure all dependencies are properly specified in requirements.txt with versions locked down.
@@ -0,0 +1 @@
1
+ nodeLinker: node-modules
@@ -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
+ }
@@ -0,0 +1,28 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ FROM node:24.4.1-alpine3.21 AS gui_builder
4
+
5
+ WORKDIR /home/biolib/
6
+
7
+ RUN corepack enable
8
+ COPY .yarnrc.yml .
9
+ COPY package.json .
10
+ COPY index.html .
11
+ COPY vite.config.mts .
12
+ RUN yarn install
13
+
14
+ COPY gui gui
15
+ RUN yarn build
16
+
17
+ FROM python:3.13.5-slim
18
+ WORKDIR /home/biolib/
19
+
20
+ COPY requirements.txt .
21
+ RUN pip install --no-cache-dir -r requirements.txt
22
+
23
+ COPY run.sh .
24
+ COPY run.py .
25
+
26
+ RUN mkdir output
27
+
28
+ COPY --from=gui_builder /home/biolib/dist/index.html result.html
@@ -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
+ }
@@ -0,0 +1,5 @@
1
+ @import "tailwindcss";
2
+
3
+ * {
4
+ font-family: "Noto Sans", sans-serif;
5
+ }
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link href="gui/index.css" rel="stylesheet">
7
+ <title>BIOLIB_REPLACE_APP_NAME</title>
8
+ </head>
9
+ <body style="overflow: hidden; background: white">
10
+ <div id="root"></div>
11
+ <script type="module" src="gui/index.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import App from "./App.tsx";
4
+ import "./index.css";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>
10
+ );
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "biolib_replace_app_name",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc --noEmit -p gui/tsconfig.json && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "18.3.1",
13
+ "react-dom": "18.3.1"
14
+ },
15
+ "devDependencies": {
16
+ "@tailwindcss/vite": "4.0.14",
17
+ "@types/node": "20.17.10",
18
+ "@types/react": "18.3.3",
19
+ "@types/react-dom": "18.3.0",
20
+ "@vitejs/plugin-react": "4.2.1",
21
+ "tailwindcss": "4.0.14",
22
+ "typescript": "5.2.2",
23
+ "vite": "5.3.4",
24
+ "vite-plugin-singlefile": "2.0.2"
25
+ },
26
+ "packageManager": "yarn@4.6.0"
27
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "target": "ES2020",
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+ "jsx": "react-jsx",
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "noFallthroughCasesInSwitch": true
22
+ },
23
+ "include": ["."]
24
+ }
@@ -0,0 +1,49 @@
1
+ import type { Plugin } from 'vite';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ export function devDataPlugin(): Plugin {
6
+ let isDev = false;
7
+
8
+ return {
9
+ name: 'dev-data-plugin',
10
+ configResolved(config) {
11
+ isDev = config.mode === 'development';
12
+ },
13
+ transform(code: string, id: string) {
14
+ if (id.endsWith('biolib-sdk.ts')) {
15
+ let injectedCode: string;
16
+
17
+ if (isDev) {
18
+ const devDataDir = path.join(__dirname, 'dev-data');
19
+ const devDataMap: Record<string, string> = {};
20
+
21
+ if (fs.existsSync(devDataDir)) {
22
+ const files = fs.readdirSync(devDataDir);
23
+ for (const file of files) {
24
+ const filePath = path.join(devDataDir, file);
25
+ if (fs.statSync(filePath).isFile()) {
26
+ const content = fs.readFileSync(filePath);
27
+ const base64Content = content.toString('base64');
28
+ devDataMap[file] = base64Content;
29
+ }
30
+ }
31
+ }
32
+
33
+ const devDataJson = JSON.stringify(devDataMap);
34
+ injectedCode = code.replace(
35
+ "const DEV_DATA_FILES = {};",
36
+ `const DEV_DATA_FILES = ${devDataJson};`
37
+ );
38
+ } else {
39
+ injectedCode = code;
40
+ }
41
+
42
+ return {
43
+ code: injectedCode,
44
+ map: null
45
+ };
46
+ }
47
+ }
48
+ };
49
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+ import tailwindcss from "@tailwindcss/vite";
4
+ import { viteSingleFile } from "vite-plugin-singlefile";
5
+ import { devDataPlugin } from "./gui/vite-plugin-dev-data";
6
+
7
+ export default defineConfig({
8
+ plugins: [react(), tailwindcss(), devDataPlugin(), viteSingleFile()],
9
+ });
@@ -10,6 +10,7 @@ modules:
10
10
  output_files:
11
11
  - COPY /home/biolib/output/ /
12
12
 
13
+ BIOLIB_REPLACE_GUI_CONFIG
13
14
  arguments:
14
15
  -
15
16
  key: --input
@@ -11,6 +11,11 @@ jobs:
11
11
  - name: Build
12
12
  run: docker build -t BIOLIB_REPLACE_DOCKER_TAG:latest .
13
13
  - name: Push
14
- run: biolib push $([ "$GITHUB_REF_NAME" != "main" ] && echo -n "--dev") BIOLIB_REPLACE_APP_URI
14
+ run: |
15
+ if [ "$GITHUB_REF_NAME" == "main" ]; then
16
+ biolib push BIOLIB_REPLACE_APP_URI
17
+ else
18
+ biolib push --dev BIOLIB_REPLACE_APP_URI:latest-dev
19
+ fi
15
20
  env:
16
21
  BIOLIB_TOKEN: ${{ secrets.BIOLIB_TOKEN }}
@@ -1,3 +1,5 @@
1
+ # syntax=docker/dockerfile:1
2
+
1
3
  FROM python:3.13.3-slim
2
4
  WORKDIR /home/biolib/
3
5
 
@@ -1,3 +1,4 @@
1
1
  #!/bin/bash
2
2
  set -e
3
+ BIOLIB_REPLACE_GUI_MV_COMMAND
3
4
  python3 run.py "$@"
@@ -1,5 +1,13 @@
1
1
  import os
2
2
 
3
3
 
4
- def init_template():
4
+ def init_template() -> str:
5
5
  return os.path.join(os.path.dirname(__file__), 'init_template')
6
+
7
+
8
+ def copilot_template() -> str:
9
+ return os.path.join(os.path.dirname(__file__), 'copilot_template')
10
+
11
+
12
+ def gui_template() -> str:
13
+ return os.path.join(os.path.dirname(__file__), 'gui_template')
@@ -1,5 +1,30 @@
1
1
  import time
2
2
  import uuid
3
+ from fnmatch import fnmatch
4
+
5
+ from biolib.biolib_binary_format.utils import LazyLoadedFile
6
+ from biolib.typing_utils import Callable, List, Union, cast
7
+
8
+ PathFilter = Union[str, Callable[[str], bool]]
9
+
10
+
11
+ def filter_lazy_loaded_files(files: List[LazyLoadedFile], path_filter: PathFilter) -> List[LazyLoadedFile]:
12
+ if not (isinstance(path_filter, str) or callable(path_filter)):
13
+ raise Exception('Expected path_filter to be a string or a function')
14
+
15
+ if callable(path_filter):
16
+ return list(filter(lambda x: path_filter(x.path), files)) # type: ignore
17
+
18
+ glob_filter = cast(str, path_filter)
19
+
20
+ # since all file paths start with /, make sure filter does too
21
+ if not glob_filter.startswith('/'):
22
+ glob_filter = '/' + glob_filter
23
+
24
+ def _filter_function(file: LazyLoadedFile) -> bool:
25
+ return fnmatch(file.path, glob_filter)
26
+
27
+ return list(filter(_filter_function, files))
3
28
 
4
29
 
5
30
  def open_browser_window_from_notebook(url_to_open: str) -> None: