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.
- biolib/__init__.py +33 -10
- biolib/_data_record/data_record.py +24 -11
- biolib/_index/index.py +51 -0
- biolib/_index/types.py +7 -0
- biolib/_internal/add_copilot_prompts.py +3 -5
- biolib/_internal/add_gui_files.py +59 -0
- biolib/_internal/data_record/data_record.py +1 -1
- biolib/_internal/data_record/push_data.py +1 -1
- biolib/_internal/data_record/remote_storage_endpoint.py +3 -3
- biolib/_internal/file_utils.py +48 -0
- biolib/_internal/index/__init__.py +1 -0
- biolib/_internal/index/index.py +18 -0
- biolib/_internal/lfs/cache.py +4 -2
- biolib/_internal/push_application.py +89 -23
- biolib/_internal/runtime.py +2 -0
- biolib/_internal/string_utils.py +13 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
- biolib/_internal/templates/gui_template/.yarnrc.yml +1 -0
- biolib/_internal/templates/gui_template/App.tsx +53 -0
- biolib/_internal/templates/gui_template/Dockerfile +28 -0
- biolib/_internal/templates/gui_template/biolib-sdk.ts +37 -0
- biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
- biolib/_internal/templates/gui_template/index.css +5 -0
- biolib/_internal/templates/gui_template/index.html +13 -0
- biolib/_internal/templates/gui_template/index.tsx +10 -0
- biolib/_internal/templates/gui_template/package.json +27 -0
- biolib/_internal/templates/gui_template/tsconfig.json +24 -0
- biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +49 -0
- biolib/_internal/templates/gui_template/vite.config.mts +9 -0
- biolib/_internal/templates/init_template/.biolib/config.yml +1 -0
- biolib/_internal/templates/init_template/.github/workflows/biolib.yml +6 -1
- biolib/_internal/templates/init_template/Dockerfile +2 -0
- biolib/_internal/templates/init_template/run.sh +1 -0
- biolib/_internal/templates/templates.py +9 -1
- biolib/_internal/utils/__init__.py +25 -0
- biolib/_internal/utils/job_url.py +33 -0
- biolib/_internal/utils/multinode.py +12 -14
- biolib/_runtime/runtime.py +15 -2
- biolib/_session/session.py +7 -5
- biolib/_shared/__init__.py +0 -0
- biolib/_shared/types/__init__.py +69 -0
- biolib/_shared/types/account.py +12 -0
- biolib/_shared/types/account_member.py +8 -0
- biolib/{_internal → _shared}/types/experiment.py +1 -0
- biolib/_shared/types/resource.py +17 -0
- biolib/_shared/types/resource_deploy_key.py +11 -0
- biolib/{_internal → _shared}/types/resource_permission.py +1 -1
- biolib/{_internal → _shared}/types/user.py +5 -5
- biolib/_shared/utils/__init__.py +7 -0
- biolib/_shared/utils/resource_uri.py +75 -0
- biolib/api/client.py +1 -1
- biolib/app/app.py +96 -45
- biolib/biolib_api_client/app_types.py +1 -0
- biolib/biolib_api_client/biolib_app_api.py +26 -0
- biolib/biolib_binary_format/module_input.py +8 -0
- biolib/biolib_binary_format/remote_endpoints.py +3 -3
- biolib/biolib_binary_format/remote_stream_seeker.py +39 -25
- biolib/biolib_logging.py +1 -1
- biolib/cli/__init__.py +2 -1
- biolib/cli/auth.py +4 -16
- biolib/cli/data_record.py +17 -0
- biolib/cli/index.py +32 -0
- biolib/cli/init.py +93 -11
- biolib/cli/lfs.py +1 -1
- biolib/cli/run.py +1 -1
- biolib/cli/start.py +14 -1
- biolib/compute_node/job_worker/executors/docker_executor.py +31 -9
- biolib/compute_node/job_worker/executors/docker_types.py +1 -1
- biolib/compute_node/job_worker/executors/types.py +6 -5
- biolib/compute_node/job_worker/job_storage.py +2 -1
- biolib/compute_node/job_worker/job_worker.py +155 -90
- biolib/compute_node/job_worker/large_file_system.py +2 -6
- biolib/compute_node/job_worker/network_alloc.py +99 -0
- biolib/compute_node/job_worker/network_buffer.py +240 -0
- biolib/compute_node/job_worker/utilization_reporter_thread.py +2 -2
- biolib/compute_node/remote_host_proxy.py +135 -67
- biolib/compute_node/utils.py +2 -0
- biolib/compute_node/webserver/compute_node_results_proxy.py +188 -0
- biolib/compute_node/webserver/proxy_utils.py +28 -0
- biolib/compute_node/webserver/webserver.py +64 -19
- biolib/experiments/experiment.py +98 -16
- biolib/jobs/job.py +128 -31
- biolib/jobs/job_result.py +73 -33
- biolib/jobs/types.py +1 -0
- biolib/sdk/__init__.py +17 -2
- biolib/typing_utils.py +1 -1
- biolib/utils/cache_state.py +2 -2
- biolib/utils/seq_util.py +1 -1
- {pybiolib-1.2.911.dist-info → pybiolib-1.2.1642.dist-info}/METADATA +4 -2
- pybiolib-1.2.1642.dist-info/RECORD +180 -0
- {pybiolib-1.2.911.dist-info → pybiolib-1.2.1642.dist-info}/WHEEL +1 -1
- biolib/_internal/llm_instructions/.github/instructions/style-react-ts.instructions.md +0 -22
- biolib/_internal/types/__init__.py +0 -6
- biolib/_internal/types/account.py +0 -10
- biolib/utils/app_uri.py +0 -57
- pybiolib-1.2.911.dist-info/RECORD +0 -150
- /biolib/{_internal/llm_instructions → _index}/__init__.py +0 -0
- /biolib/_internal/{llm_instructions → templates/copilot_template}/.github/instructions/general-app-knowledge.instructions.md +0 -0
- /biolib/_internal/{llm_instructions → templates/copilot_template}/.github/instructions/style-general.instructions.md +0 -0
- /biolib/_internal/{llm_instructions → templates/copilot_template}/.github/instructions/style-python.instructions.md +0 -0
- /biolib/_internal/{llm_instructions → templates/copilot_template}/.github/prompts/biolib_app_inputs.prompt.md +0 -0
- /biolib/_internal/{llm_instructions → templates/copilot_template}/.github/prompts/biolib_run_apps.prompt.md +0 -0
- /biolib/{_internal → _shared}/types/app.py +0 -0
- /biolib/{_internal → _shared}/types/data_record.py +0 -0
- /biolib/{_internal → _shared}/types/file_node.py +0 -0
- /biolib/{_internal → _shared}/types/push.py +0 -0
- /biolib/{_internal/types/resource.py → _shared/types/resource_types.py} +0 -0
- /biolib/{_internal → _shared}/types/resource_version.py +0 -0
- /biolib/{_internal → _shared}/types/result.py +0 -0
- /biolib/{_internal → _shared}/types/typing.py +0 -0
- {pybiolib-1.2.911.dist-info → pybiolib-1.2.1642.dist-info}/entry_points.txt +0 -0
- {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.
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
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 "
|
|
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=
|
|
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']}"
|
biolib/_internal/runtime.py
CHANGED
|
@@ -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
|
biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md
ADDED
|
@@ -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,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,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
|
+
});
|
|
@@ -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:
|
|
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,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:
|