pybiolib 0.2.951__py3-none-any.whl → 1.2.1890__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- biolib/__init__.py +357 -11
- biolib/_data_record/data_record.py +380 -0
- biolib/_index/__init__.py +0 -0
- biolib/_index/index.py +55 -0
- biolib/_index/query_result.py +103 -0
- biolib/_internal/__init__.py +0 -0
- biolib/_internal/add_copilot_prompts.py +58 -0
- biolib/_internal/add_gui_files.py +81 -0
- biolib/_internal/data_record/__init__.py +1 -0
- biolib/_internal/data_record/data_record.py +85 -0
- biolib/_internal/data_record/push_data.py +116 -0
- biolib/_internal/data_record/remote_storage_endpoint.py +43 -0
- biolib/_internal/errors.py +5 -0
- biolib/_internal/file_utils.py +125 -0
- biolib/_internal/fuse_mount/__init__.py +1 -0
- biolib/_internal/fuse_mount/experiment_fuse_mount.py +209 -0
- biolib/_internal/http_client.py +159 -0
- biolib/_internal/lfs/__init__.py +1 -0
- biolib/_internal/lfs/cache.py +51 -0
- biolib/_internal/libs/__init__.py +1 -0
- biolib/_internal/libs/fusepy/__init__.py +1257 -0
- biolib/_internal/push_application.py +488 -0
- biolib/_internal/runtime.py +22 -0
- biolib/_internal/string_utils.py +13 -0
- biolib/_internal/templates/__init__.py +1 -0
- biolib/_internal/templates/copilot_template/.github/instructions/general-app-knowledge.instructions.md +10 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-general.instructions.md +20 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-python.instructions.md +16 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_app_inputs.prompt.md +11 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_run_apps.prompt.md +12 -0
- biolib/_internal/templates/dashboard_template/.biolib/config.yml +5 -0
- biolib/_internal/templates/github_workflow_template/.github/workflows/biolib.yml +21 -0
- biolib/_internal/templates/gitignore_template/.gitignore +10 -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 +27 -0
- biolib/_internal/templates/gui_template/biolib-sdk.ts +82 -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 +50 -0
- biolib/_internal/templates/gui_template/vite.config.mts +10 -0
- biolib/_internal/templates/init_template/.biolib/config.yml +19 -0
- biolib/_internal/templates/init_template/Dockerfile +14 -0
- biolib/_internal/templates/init_template/requirements.txt +1 -0
- biolib/_internal/templates/init_template/run.py +12 -0
- biolib/_internal/templates/init_template/run.sh +4 -0
- biolib/_internal/templates/templates.py +25 -0
- biolib/_internal/tree_utils.py +106 -0
- biolib/_internal/utils/__init__.py +65 -0
- biolib/_internal/utils/auth.py +46 -0
- biolib/_internal/utils/job_url.py +33 -0
- biolib/_internal/utils/multinode.py +263 -0
- biolib/_runtime/runtime.py +157 -0
- biolib/_session/session.py +44 -0
- biolib/_shared/__init__.py +0 -0
- biolib/_shared/types/__init__.py +74 -0
- biolib/_shared/types/account.py +12 -0
- biolib/_shared/types/account_member.py +8 -0
- biolib/_shared/types/app.py +9 -0
- biolib/_shared/types/data_record.py +40 -0
- biolib/_shared/types/experiment.py +32 -0
- biolib/_shared/types/file_node.py +17 -0
- biolib/_shared/types/push.py +6 -0
- biolib/_shared/types/resource.py +37 -0
- biolib/_shared/types/resource_deploy_key.py +11 -0
- biolib/_shared/types/resource_permission.py +14 -0
- biolib/_shared/types/resource_version.py +19 -0
- biolib/_shared/types/result.py +14 -0
- biolib/_shared/types/typing.py +10 -0
- biolib/_shared/types/user.py +19 -0
- biolib/_shared/utils/__init__.py +7 -0
- biolib/_shared/utils/resource_uri.py +75 -0
- biolib/api/__init__.py +6 -0
- biolib/api/client.py +168 -0
- biolib/app/app.py +252 -49
- biolib/app/search_apps.py +45 -0
- biolib/biolib_api_client/api_client.py +126 -31
- biolib/biolib_api_client/app_types.py +24 -4
- biolib/biolib_api_client/auth.py +31 -8
- biolib/biolib_api_client/biolib_app_api.py +147 -52
- biolib/biolib_api_client/biolib_job_api.py +161 -141
- biolib/biolib_api_client/job_types.py +21 -5
- biolib/biolib_api_client/lfs_types.py +7 -23
- biolib/biolib_api_client/user_state.py +56 -0
- biolib/biolib_binary_format/__init__.py +1 -4
- biolib/biolib_binary_format/file_in_container.py +105 -0
- biolib/biolib_binary_format/module_input.py +24 -7
- biolib/biolib_binary_format/module_output_v2.py +149 -0
- biolib/biolib_binary_format/remote_endpoints.py +34 -0
- biolib/biolib_binary_format/remote_stream_seeker.py +59 -0
- biolib/biolib_binary_format/saved_job.py +3 -2
- biolib/biolib_binary_format/{attestation_document.py → stdout_and_stderr.py} +8 -8
- biolib/biolib_binary_format/system_status_update.py +3 -2
- biolib/biolib_binary_format/utils.py +175 -0
- biolib/biolib_docker_client/__init__.py +11 -2
- biolib/biolib_errors.py +36 -0
- biolib/biolib_logging.py +27 -10
- biolib/cli/__init__.py +38 -0
- biolib/cli/auth.py +46 -0
- biolib/cli/data_record.py +164 -0
- biolib/cli/index.py +32 -0
- biolib/cli/init.py +421 -0
- biolib/cli/lfs.py +101 -0
- biolib/cli/push.py +50 -0
- biolib/cli/run.py +63 -0
- biolib/cli/runtime.py +14 -0
- biolib/cli/sdk.py +16 -0
- biolib/cli/start.py +56 -0
- biolib/compute_node/cloud_utils/cloud_utils.py +110 -161
- biolib/compute_node/job_worker/cache_state.py +66 -88
- biolib/compute_node/job_worker/cache_types.py +1 -6
- biolib/compute_node/job_worker/docker_image_cache.py +112 -37
- biolib/compute_node/job_worker/executors/__init__.py +0 -3
- biolib/compute_node/job_worker/executors/docker_executor.py +532 -199
- biolib/compute_node/job_worker/executors/docker_types.py +9 -1
- biolib/compute_node/job_worker/executors/types.py +19 -9
- biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +30 -0
- biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +3 -5
- biolib/compute_node/job_worker/job_storage.py +108 -0
- biolib/compute_node/job_worker/job_worker.py +397 -212
- biolib/compute_node/job_worker/large_file_system.py +87 -38
- 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 +197 -0
- biolib/compute_node/job_worker/utils.py +9 -24
- biolib/compute_node/remote_host_proxy.py +400 -98
- biolib/compute_node/utils.py +31 -9
- biolib/compute_node/webserver/compute_node_results_proxy.py +189 -0
- biolib/compute_node/webserver/proxy_utils.py +28 -0
- biolib/compute_node/webserver/webserver.py +130 -44
- biolib/compute_node/webserver/webserver_types.py +2 -6
- biolib/compute_node/webserver/webserver_utils.py +77 -12
- biolib/compute_node/webserver/worker_thread.py +183 -42
- biolib/experiments/__init__.py +0 -0
- biolib/experiments/experiment.py +356 -0
- biolib/jobs/__init__.py +1 -0
- biolib/jobs/job.py +741 -0
- biolib/jobs/job_result.py +185 -0
- biolib/jobs/types.py +50 -0
- biolib/py.typed +0 -0
- biolib/runtime/__init__.py +14 -0
- biolib/sdk/__init__.py +91 -0
- biolib/tables.py +34 -0
- biolib/typing_utils.py +2 -7
- biolib/user/__init__.py +1 -0
- biolib/user/sign_in.py +54 -0
- biolib/utils/__init__.py +162 -0
- biolib/utils/cache_state.py +94 -0
- biolib/utils/multipart_uploader.py +194 -0
- biolib/utils/seq_util.py +150 -0
- biolib/utils/zip/remote_zip.py +640 -0
- pybiolib-1.2.1890.dist-info/METADATA +41 -0
- pybiolib-1.2.1890.dist-info/RECORD +177 -0
- {pybiolib-0.2.951.dist-info → pybiolib-1.2.1890.dist-info}/WHEEL +1 -1
- pybiolib-1.2.1890.dist-info/entry_points.txt +2 -0
- README.md +0 -17
- biolib/app/app_result.py +0 -68
- biolib/app/utils.py +0 -62
- biolib/biolib-js/0-biolib.worker.js +0 -1
- biolib/biolib-js/1-biolib.worker.js +0 -1
- biolib/biolib-js/2-biolib.worker.js +0 -1
- biolib/biolib-js/3-biolib.worker.js +0 -1
- biolib/biolib-js/4-biolib.worker.js +0 -1
- biolib/biolib-js/5-biolib.worker.js +0 -1
- biolib/biolib-js/6-biolib.worker.js +0 -1
- biolib/biolib-js/index.html +0 -10
- biolib/biolib-js/main-biolib.js +0 -1
- biolib/biolib_api_client/biolib_account_api.py +0 -21
- biolib/biolib_api_client/biolib_large_file_system_api.py +0 -108
- biolib/biolib_binary_format/aes_encrypted_package.py +0 -42
- biolib/biolib_binary_format/module_output.py +0 -58
- biolib/biolib_binary_format/rsa_encrypted_aes_package.py +0 -57
- biolib/biolib_push.py +0 -114
- biolib/cli.py +0 -203
- biolib/cli_utils.py +0 -273
- biolib/compute_node/cloud_utils/enclave_parent_types.py +0 -7
- biolib/compute_node/enclave/__init__.py +0 -2
- biolib/compute_node/enclave/enclave_remote_hosts.py +0 -53
- biolib/compute_node/enclave/nitro_secure_module_utils.py +0 -64
- biolib/compute_node/job_worker/executors/base_executor.py +0 -18
- biolib/compute_node/job_worker/executors/pyppeteer_executor.py +0 -173
- biolib/compute_node/job_worker/executors/remote/__init__.py +0 -1
- biolib/compute_node/job_worker/executors/remote/nitro_enclave_utils.py +0 -81
- biolib/compute_node/job_worker/executors/remote/remote_executor.py +0 -51
- biolib/lfs.py +0 -196
- biolib/pyppeteer/.circleci/config.yml +0 -100
- biolib/pyppeteer/.coveragerc +0 -3
- biolib/pyppeteer/.gitignore +0 -89
- biolib/pyppeteer/.pre-commit-config.yaml +0 -28
- biolib/pyppeteer/CHANGES.md +0 -253
- biolib/pyppeteer/CONTRIBUTING.md +0 -26
- biolib/pyppeteer/LICENSE +0 -12
- biolib/pyppeteer/README.md +0 -137
- biolib/pyppeteer/docs/Makefile +0 -177
- biolib/pyppeteer/docs/_static/custom.css +0 -28
- biolib/pyppeteer/docs/_templates/layout.html +0 -10
- biolib/pyppeteer/docs/changes.md +0 -1
- biolib/pyppeteer/docs/conf.py +0 -299
- biolib/pyppeteer/docs/index.md +0 -21
- biolib/pyppeteer/docs/make.bat +0 -242
- biolib/pyppeteer/docs/reference.md +0 -211
- biolib/pyppeteer/docs/server.py +0 -60
- biolib/pyppeteer/poetry.lock +0 -1699
- biolib/pyppeteer/pyppeteer/__init__.py +0 -135
- biolib/pyppeteer/pyppeteer/accessibility.py +0 -286
- biolib/pyppeteer/pyppeteer/browser.py +0 -401
- biolib/pyppeteer/pyppeteer/browser_fetcher.py +0 -194
- biolib/pyppeteer/pyppeteer/command.py +0 -22
- biolib/pyppeteer/pyppeteer/connection/__init__.py +0 -242
- biolib/pyppeteer/pyppeteer/connection/cdpsession.py +0 -101
- biolib/pyppeteer/pyppeteer/coverage.py +0 -346
- biolib/pyppeteer/pyppeteer/device_descriptors.py +0 -787
- biolib/pyppeteer/pyppeteer/dialog.py +0 -79
- biolib/pyppeteer/pyppeteer/domworld.py +0 -597
- biolib/pyppeteer/pyppeteer/emulation_manager.py +0 -53
- biolib/pyppeteer/pyppeteer/errors.py +0 -48
- biolib/pyppeteer/pyppeteer/events.py +0 -63
- biolib/pyppeteer/pyppeteer/execution_context.py +0 -156
- biolib/pyppeteer/pyppeteer/frame/__init__.py +0 -299
- biolib/pyppeteer/pyppeteer/frame/frame_manager.py +0 -306
- biolib/pyppeteer/pyppeteer/helpers.py +0 -245
- biolib/pyppeteer/pyppeteer/input.py +0 -371
- biolib/pyppeteer/pyppeteer/jshandle.py +0 -598
- biolib/pyppeteer/pyppeteer/launcher.py +0 -683
- biolib/pyppeteer/pyppeteer/lifecycle_watcher.py +0 -169
- biolib/pyppeteer/pyppeteer/models/__init__.py +0 -103
- biolib/pyppeteer/pyppeteer/models/_protocol.py +0 -12460
- biolib/pyppeteer/pyppeteer/multimap.py +0 -82
- biolib/pyppeteer/pyppeteer/network_manager.py +0 -678
- biolib/pyppeteer/pyppeteer/options.py +0 -8
- biolib/pyppeteer/pyppeteer/page.py +0 -1728
- biolib/pyppeteer/pyppeteer/pipe_transport.py +0 -59
- biolib/pyppeteer/pyppeteer/target.py +0 -147
- biolib/pyppeteer/pyppeteer/task_queue.py +0 -24
- biolib/pyppeteer/pyppeteer/timeout_settings.py +0 -36
- biolib/pyppeteer/pyppeteer/tracing.py +0 -93
- biolib/pyppeteer/pyppeteer/us_keyboard_layout.py +0 -305
- biolib/pyppeteer/pyppeteer/util.py +0 -18
- biolib/pyppeteer/pyppeteer/websocket_transport.py +0 -47
- biolib/pyppeteer/pyppeteer/worker.py +0 -101
- biolib/pyppeteer/pyproject.toml +0 -97
- biolib/pyppeteer/spell.txt +0 -137
- biolib/pyppeteer/tox.ini +0 -72
- biolib/pyppeteer/utils/generate_protocol_types.py +0 -603
- biolib/start_cli.py +0 -7
- biolib/utils.py +0 -47
- biolib/validators/validate_app_version.py +0 -183
- biolib/validators/validate_argument.py +0 -134
- biolib/validators/validate_module.py +0 -323
- biolib/validators/validate_zip_file.py +0 -40
- biolib/validators/validator_utils.py +0 -103
- pybiolib-0.2.951.dist-info/LICENSE +0 -21
- pybiolib-0.2.951.dist-info/METADATA +0 -61
- pybiolib-0.2.951.dist-info/RECORD +0 -153
- pybiolib-0.2.951.dist-info/entry_points.txt +0 -3
- /LICENSE → /pybiolib-1.2.1890.dist-info/licenses/LICENSE +0 -0
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import io
|
|
2
|
-
import re
|
|
3
|
-
from zipfile import ZipFile
|
|
4
|
-
|
|
5
|
-
from biolib.validators import validator_utils # type: ignore
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def validate_app_version(yaml_data, yaml_version, zip_file):
|
|
9
|
-
files = [filename.strip('/') for filename in zip_file.namelist()]
|
|
10
|
-
|
|
11
|
-
error_dict = {}
|
|
12
|
-
validate_unsupported_root_level_fields(yaml_data, error_dict, yaml_version)
|
|
13
|
-
validate_output_type(yaml_data, error_dict)
|
|
14
|
-
validate_consumes_stdin(yaml_data, error_dict)
|
|
15
|
-
validate_remote_hosts(yaml_data, error_dict)
|
|
16
|
-
validate_description_file(yaml_data, files, zip_file, error_dict)
|
|
17
|
-
validate_license_file(yaml_data, files, zip_file, error_dict)
|
|
18
|
-
return error_dict
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def validate_output_type(yaml_data, error_dict):
|
|
22
|
-
# output_type is not required
|
|
23
|
-
if 'output_type' in yaml_data.keys():
|
|
24
|
-
output_type = yaml_data['output_type']
|
|
25
|
-
stdout_render_types_choices = [type_tuple[0] for type_tuple in validator_utils.stdout_render_types]
|
|
26
|
-
if output_type not in stdout_render_types_choices:
|
|
27
|
-
error_dict['output_type'] = [
|
|
28
|
-
f'Invalid output_type specified for your app. output_type can be one of {stdout_render_types_choices}'
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def validate_consumes_stdin(yaml_data, error_dict):
|
|
33
|
-
if 'consumes_stdin' in yaml_data.keys():
|
|
34
|
-
consumes_stdin = yaml_data['consumes_stdin']
|
|
35
|
-
if not isinstance(consumes_stdin, bool):
|
|
36
|
-
error_dict['consumes_stdin'] = [
|
|
37
|
-
f'Invalid consumes_stdin specified for your app. consumes_stdin can be true or false'
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def validate_remote_hosts(yaml_data, error_dict):
|
|
42
|
-
if 'remote_hosts' in yaml_data.keys():
|
|
43
|
-
for hostname in yaml_data['remote_hosts']:
|
|
44
|
-
# No error message is returned if the hostname is valid
|
|
45
|
-
hostname_error_message = validator_utils.validate_hostname_and_return_error_message(hostname)
|
|
46
|
-
if hostname_error_message:
|
|
47
|
-
error_dict['remote_hosts'] = hostname_error_message
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def validate_description_file(yaml_data, files, zip_file: ZipFile, error_dict):
|
|
51
|
-
# Only check existence of description file if user specified it
|
|
52
|
-
if 'description_file' in yaml_data.keys():
|
|
53
|
-
description_path = yaml_data['description_file']
|
|
54
|
-
|
|
55
|
-
if description_path not in files:
|
|
56
|
-
error_dict['description_file'] = [
|
|
57
|
-
f'Could not find description file at {description_path}. \
|
|
58
|
-
Please provide a path pointing to a markdown (.md) file'
|
|
59
|
-
]
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
else:
|
|
63
|
-
description_path = 'README.md'
|
|
64
|
-
|
|
65
|
-
if description_path in files:
|
|
66
|
-
# TODO: Discuss this limit, seems very high :sweat:
|
|
67
|
-
# Check if description file is bigger than 100MB, written as 100000000 bytes
|
|
68
|
-
if zip_file.getinfo(description_path).file_size > 100000000:
|
|
69
|
-
error_dict['description_file'] = [f'The description file {description_path} must be less than 100MB']
|
|
70
|
-
|
|
71
|
-
validate_description_images(zip_file.open(description_path), files, zip_file, error_dict)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def validate_license_file(yaml_data, files, zip_file, error_dict):
|
|
75
|
-
if 'license_file' in yaml_data.keys():
|
|
76
|
-
license_path = yaml_data['license_file']
|
|
77
|
-
|
|
78
|
-
if license_path not in files:
|
|
79
|
-
error_dict['license_file'] = [
|
|
80
|
-
f'Could not find license file at {license_path}. Please provide a path pointing to a license file'
|
|
81
|
-
]
|
|
82
|
-
return
|
|
83
|
-
|
|
84
|
-
else:
|
|
85
|
-
license_path = 'LICENSE'
|
|
86
|
-
|
|
87
|
-
if license_path in files:
|
|
88
|
-
# Check if license file is bigger than 100MB, written as 100000000 bytes
|
|
89
|
-
if zip_file.getinfo(license_path).file_size > 100000000:
|
|
90
|
-
error_dict['license_file'] = [f'The license file {license_path} must be less than 100MB']
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def validate_is_open_source(yaml_data, error_dict):
|
|
94
|
-
if 'is_open_source' in yaml_data.keys():
|
|
95
|
-
if not isinstance(yaml_data['is_open_source'], bool):
|
|
96
|
-
error_dict['is_open_source'] = [
|
|
97
|
-
f'Invalid is_open_source specified for your app. is_open_source can be true or false'
|
|
98
|
-
]
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
supported_root_level_fields_base = [
|
|
102
|
-
'arguments',
|
|
103
|
-
'biolib_version',
|
|
104
|
-
'citation',
|
|
105
|
-
'consumes_stdin',
|
|
106
|
-
'description_file',
|
|
107
|
-
'license_file',
|
|
108
|
-
'modules',
|
|
109
|
-
'output_type',
|
|
110
|
-
'remote_hosts',
|
|
111
|
-
]
|
|
112
|
-
|
|
113
|
-
supported_root_level_fields_v1 = [
|
|
114
|
-
'client_side_include',
|
|
115
|
-
]
|
|
116
|
-
|
|
117
|
-
supported_root_level_fields_v2 = [
|
|
118
|
-
'source_files_ignore',
|
|
119
|
-
]
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def validate_unsupported_root_level_fields(yaml_data, error_dict, yaml_version):
|
|
123
|
-
if yaml_version == 1:
|
|
124
|
-
supported_fields = supported_root_level_fields_base + supported_root_level_fields_v1
|
|
125
|
-
else:
|
|
126
|
-
supported_fields = supported_root_level_fields_base + supported_root_level_fields_v2
|
|
127
|
-
|
|
128
|
-
errors = []
|
|
129
|
-
for field in yaml_data.keys():
|
|
130
|
-
if field not in supported_fields:
|
|
131
|
-
errors.append(
|
|
132
|
-
f'The field {field} is not valid'
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
if errors:
|
|
136
|
-
error_dict['unsupported_fields'] = errors
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def validate_description_images(description_path, files, zip_file, error_dict):
|
|
140
|
-
REGEX_MARKDOWN_INLINE_IMAGE = re.compile(r'!\[(?P<alt>.*)\]\((?P<src>.*)\)')
|
|
141
|
-
image_filesize_limit_in_bytes = 5000000
|
|
142
|
-
supported_image_file_types = ('png', 'gif', 'jpg', 'jpeg')
|
|
143
|
-
|
|
144
|
-
description_images = {}
|
|
145
|
-
description_markdown = io.TextIOWrapper(description_path).read()
|
|
146
|
-
|
|
147
|
-
for img_alt, img_src_path in re.findall(REGEX_MARKDOWN_INLINE_IMAGE, description_markdown):
|
|
148
|
-
|
|
149
|
-
if img_src_path in description_images:
|
|
150
|
-
continue
|
|
151
|
-
|
|
152
|
-
if re.match(r'data:.*;base64,', img_src_path):
|
|
153
|
-
error_dict['description_file'] = [
|
|
154
|
-
'The Markdown description does not support base64 images. '
|
|
155
|
-
'Please specify images using their path in the application files: '
|
|
156
|
-
''
|
|
157
|
-
]
|
|
158
|
-
return error_dict
|
|
159
|
-
|
|
160
|
-
if img_src_path not in files:
|
|
161
|
-
if len(img_src_path) > 200:
|
|
162
|
-
img_src_path = img_src_path[:200] + '...'
|
|
163
|
-
error_dict['description_file'] = [
|
|
164
|
-
f'In the Markdown description the image path {img_src_path} does not exist in application files'
|
|
165
|
-
]
|
|
166
|
-
return error_dict
|
|
167
|
-
|
|
168
|
-
extension = img_src_path.split('.')[-1] if '.' in img_src_path else 'png'
|
|
169
|
-
if extension not in supported_image_file_types:
|
|
170
|
-
error_dict['description_file'] = [
|
|
171
|
-
f'In the Markdown description, the image {img_src_path} '
|
|
172
|
-
f'must point to an image of the following types {supported_image_file_types}.'
|
|
173
|
-
]
|
|
174
|
-
return error_dict
|
|
175
|
-
|
|
176
|
-
# Limit image size
|
|
177
|
-
if zip_file.getinfo(img_src_path).file_size > image_filesize_limit_in_bytes:
|
|
178
|
-
error_dict['description_file'] = [
|
|
179
|
-
f'In the Markdown description, the image {img_src_path} is over '
|
|
180
|
-
f'{image_filesize_limit_in_bytes / 1000000} MB which is too large.'
|
|
181
|
-
]
|
|
182
|
-
|
|
183
|
-
return error_dict
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
render_types = [
|
|
2
|
-
('dropdown', 'Dropdown'),
|
|
3
|
-
('file', 'File'),
|
|
4
|
-
('hidden', 'Hidden'),
|
|
5
|
-
('number', 'Number'),
|
|
6
|
-
('radio', 'Radio'),
|
|
7
|
-
('text', 'Text'),
|
|
8
|
-
('text-file', 'Text File'),
|
|
9
|
-
('toggle', 'Toggle'),
|
|
10
|
-
]
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def validate_argument(argument_data):
|
|
14
|
-
error_dict = {}
|
|
15
|
-
key = validate_key(argument_data, error_dict)
|
|
16
|
-
if key is None:
|
|
17
|
-
return error_dict
|
|
18
|
-
|
|
19
|
-
error_dict[key] = {}
|
|
20
|
-
argument_error_dict = error_dict[key]
|
|
21
|
-
|
|
22
|
-
validate_unsupported_argument_fields(key, argument_data, argument_error_dict)
|
|
23
|
-
|
|
24
|
-
# Recursively validate sub_arguments
|
|
25
|
-
sub_arguments = argument_data.get('sub_arguments', {})
|
|
26
|
-
for name, sub_arguments in sub_arguments.items():
|
|
27
|
-
for subargument in sub_arguments:
|
|
28
|
-
errors = validate_argument(subargument)
|
|
29
|
-
if errors:
|
|
30
|
-
if 'sub_arguments' not in error_dict:
|
|
31
|
-
argument_error_dict['sub_arguments'] = [errors]
|
|
32
|
-
else:
|
|
33
|
-
argument_error_dict['sub_arguments'].append(validate_argument(subargument))
|
|
34
|
-
|
|
35
|
-
validate_required(key, argument_data, argument_error_dict)
|
|
36
|
-
type = validate_type(key, argument_data, argument_error_dict)
|
|
37
|
-
|
|
38
|
-
if not type:
|
|
39
|
-
return error_dict
|
|
40
|
-
|
|
41
|
-
validate_description(key, argument_data, type, argument_error_dict)
|
|
42
|
-
|
|
43
|
-
# Just return empty dict if we have no errors
|
|
44
|
-
if error_dict[key]:
|
|
45
|
-
return error_dict
|
|
46
|
-
else:
|
|
47
|
-
return {}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def validate_key(argument_data, error_dict):
|
|
51
|
-
if 'key' not in argument_data.keys():
|
|
52
|
-
error_dict['required'] = [
|
|
53
|
-
f'One of your arguments is missing a key. Please specify a key for each of your arguments'
|
|
54
|
-
]
|
|
55
|
-
return None
|
|
56
|
-
else:
|
|
57
|
-
return argument_data['key']
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def validate_required(key, argument_data, error_dict):
|
|
61
|
-
if 'required' in argument_data.keys():
|
|
62
|
-
|
|
63
|
-
if not isinstance(argument_data['required'], bool):
|
|
64
|
-
error_dict['required'] = [
|
|
65
|
-
f'Invalid value in required specified on {key} argument. required can be true or false'
|
|
66
|
-
]
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def validate_type(key, argument_data, error_dict):
|
|
70
|
-
# Type is optional
|
|
71
|
-
if 'type' in argument_data.keys():
|
|
72
|
-
|
|
73
|
-
render_types_choices = [type_tuple[0] for type_tuple in render_types]
|
|
74
|
-
type = argument_data['type']
|
|
75
|
-
if type not in render_types_choices:
|
|
76
|
-
error_dict['type'] = [
|
|
77
|
-
f'Invalid value {type} in type specified on {key} argument \
|
|
78
|
-
type can be one of {render_types_choices}'
|
|
79
|
-
]
|
|
80
|
-
return ''
|
|
81
|
-
|
|
82
|
-
if type == 'toggle':
|
|
83
|
-
if 'options' not in argument_data:
|
|
84
|
-
error_dict['type'] = [
|
|
85
|
-
f'There must be exactly 2 options ("on" and "off") on arguments of type toggle'
|
|
86
|
-
]
|
|
87
|
-
return ''
|
|
88
|
-
|
|
89
|
-
number_of_options = len(argument_data['options'].keys())
|
|
90
|
-
|
|
91
|
-
if number_of_options != 2:
|
|
92
|
-
error_dict['type'] = [
|
|
93
|
-
f'There must be exactly 2 options ("on" and "off") on arguments of type toggle. Received \
|
|
94
|
-
{number_of_options} options'
|
|
95
|
-
]
|
|
96
|
-
return ''
|
|
97
|
-
|
|
98
|
-
option_names = list(argument_data['options'].keys())
|
|
99
|
-
|
|
100
|
-
if option_names not in (['on', 'off'], ['off', 'on']):
|
|
101
|
-
error_dict['type'] = [
|
|
102
|
-
f'The two options on arguments of type toggle must be named "on" and "off". Received \
|
|
103
|
-
{", ".join(option_names)}'
|
|
104
|
-
]
|
|
105
|
-
return ''
|
|
106
|
-
|
|
107
|
-
return type
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def validate_description(key, argument_data, type, error_dict):
|
|
111
|
-
if 'description' not in argument_data.keys() and type != 'hidden':
|
|
112
|
-
error_dict['argument_description'] = [
|
|
113
|
-
f'Could not find a description for argument {key}. Please provide a description for {key}'
|
|
114
|
-
]
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
supported_argument_fields = [
|
|
118
|
-
'default_value',
|
|
119
|
-
'description',
|
|
120
|
-
'key',
|
|
121
|
-
'key_value_separator',
|
|
122
|
-
'options',
|
|
123
|
-
'required',
|
|
124
|
-
'sub_arguments',
|
|
125
|
-
'type',
|
|
126
|
-
]
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def validate_unsupported_argument_fields(key, argument_data, error_dict):
|
|
130
|
-
for field in argument_data.keys():
|
|
131
|
-
if field not in supported_argument_fields:
|
|
132
|
-
error_dict['unsupported_field'] = [
|
|
133
|
-
f'The argument field {field} on {key} is not valid'
|
|
134
|
-
]
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
from biolib.validators.validator_utils import AllowedYAMLEnvironments, custom_executors, old_to_new_executors_map # type: ignore
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def validate_module(name, module_data, yaml_version):
|
|
7
|
-
error_dict = {}
|
|
8
|
-
name = validate_name(name, error_dict)
|
|
9
|
-
if not name:
|
|
10
|
-
return error_dict
|
|
11
|
-
error_dict[name] = {}
|
|
12
|
-
task_error_dict = error_dict[name]
|
|
13
|
-
|
|
14
|
-
validate_unsupported_task_fields(name, module_data, task_error_dict, yaml_version)
|
|
15
|
-
validate_mappings(name, module_data, task_error_dict, mapping_type='input_files')
|
|
16
|
-
validate_mappings(name, module_data, task_error_dict, mapping_type='output_files')
|
|
17
|
-
validate_mappings(name, module_data, task_error_dict, mapping_type='source_files')
|
|
18
|
-
validate_image(name, module_data, task_error_dict, yaml_version)
|
|
19
|
-
|
|
20
|
-
validate_working_directory(name, module_data, task_error_dict)
|
|
21
|
-
|
|
22
|
-
# Just return empty dict if we have no errors
|
|
23
|
-
if error_dict[name]:
|
|
24
|
-
return error_dict
|
|
25
|
-
else:
|
|
26
|
-
return {}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def validate_working_directory(name, task_data, error_dict):
|
|
30
|
-
if 'working_directory' in task_data:
|
|
31
|
-
if not task_data['working_directory'].startswith('/'):
|
|
32
|
-
error_dict['working_directory'] = [
|
|
33
|
-
f'Wrong path format on working_directory for {name}. Directory path must be an absolute path'
|
|
34
|
-
]
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
if not task_data['working_directory'].endswith('/'):
|
|
38
|
-
error_dict['working_directory'] = [
|
|
39
|
-
f'Wrong path format on working_directory for {name}. Directories must end in a slash: "/dir/sub_dir/"'
|
|
40
|
-
]
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
if '//' in task_data['working_directory']:
|
|
44
|
-
error_dict['working_directory'] = [
|
|
45
|
-
f'Wrong path format on working_directory for {name}. Directories can not have consecutive slashes"'
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def validate_external_app_format(name, app_version_string, error_dict, yaml_version):
|
|
50
|
-
try:
|
|
51
|
-
split_on_slash = app_version_string.split('/')
|
|
52
|
-
if not len(split_on_slash) == 2:
|
|
53
|
-
if yaml_version == 1:
|
|
54
|
-
error_dict['external_app'] = [
|
|
55
|
-
f'Wrong format. Tried to interpret {name} as an external-app; \
|
|
56
|
-
The format should be owner/app:version i.e. "some_user/example_app:2.0.1.'
|
|
57
|
-
]
|
|
58
|
-
else:
|
|
59
|
-
error_dict['image'] = [
|
|
60
|
-
f'Wrong format. Tried to interpret {name} as a biolib app; \
|
|
61
|
-
The format should be owner/app:version i.e. "some_user/example_app:2.0.1.'
|
|
62
|
-
]
|
|
63
|
-
return None, None, None
|
|
64
|
-
|
|
65
|
-
account_handle, app_name_and_version = split_on_slash
|
|
66
|
-
|
|
67
|
-
split_on_colon = app_name_and_version.split(':')
|
|
68
|
-
if not len(split_on_slash) == 2:
|
|
69
|
-
if yaml_version == 1:
|
|
70
|
-
error_dict['task'] = [
|
|
71
|
-
f'Wrong format. Tried to interpret {name} as an external-app; \
|
|
72
|
-
The format should be owner/app:version i.e. "some_user/example_app:2.0.1.'
|
|
73
|
-
]
|
|
74
|
-
else:
|
|
75
|
-
error_dict['image'] = [
|
|
76
|
-
f'Wrong format. Tried to interpret {name} as a biolib app; \
|
|
77
|
-
The format should be owner/app:version i.e. "some_user/example_app:2.0.1.'
|
|
78
|
-
]
|
|
79
|
-
return None, None, None
|
|
80
|
-
|
|
81
|
-
app_name, version = split_on_colon
|
|
82
|
-
return account_handle, app_name, version
|
|
83
|
-
|
|
84
|
-
except:
|
|
85
|
-
if yaml_version == 1:
|
|
86
|
-
error_dict['external_app'] = [
|
|
87
|
-
f'Wrong format. Tried to interpret {name} as an external-app; \
|
|
88
|
-
The format should be owner/app:version i.e. "some_user/example_app:2.0.1.'
|
|
89
|
-
]
|
|
90
|
-
else:
|
|
91
|
-
error_dict['image'] = [
|
|
92
|
-
f'Wrong format. Tried to interpret {name} as an external-app; \
|
|
93
|
-
The format should be owner/app:version i.e. "some_user/example_app:2.0.1.'
|
|
94
|
-
]
|
|
95
|
-
|
|
96
|
-
return None, None, None
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def validate_name(name, error_dict):
|
|
100
|
-
# TODO: Refactor this to use a version of is_alphanumeric that allows control of the error message.
|
|
101
|
-
if not re.match("^[A-Za-z0-9_-]+$", name):
|
|
102
|
-
error_dict[name] = [f'The module name {name} is invalid, it can only contain alphanumeric characters.']
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
if re.search("(--)|(__)|(-_)|(_-)", name):
|
|
106
|
-
error_dict[name] = [f'The module name {name} is invalid, it can not contain consecutive dashes or underscores']
|
|
107
|
-
return
|
|
108
|
-
|
|
109
|
-
if re.match("^(-|_)[A-Za-z0-9_-]+$", name):
|
|
110
|
-
error_dict[name] = [f'The module name {name} is invalid, it can not start with dashes or underscores']
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
if re.match("^[A-Za-z0-9_-]+(-|_)$", name):
|
|
114
|
-
error_dict[name] = [f'The module name {name} is invalid, it can not end with dashes or underscores']
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
return name
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def validate_executor(name, task_data, error_dict):
|
|
121
|
-
if 'executor' not in task_data.keys():
|
|
122
|
-
error_dict['executor'] = [
|
|
123
|
-
'You must define an executor in your module definition; \
|
|
124
|
-
Make sure you follow the format executor_name:version'
|
|
125
|
-
]
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
if task_data['executor'].count(':') != 1:
|
|
129
|
-
error_dict['executor'] = [
|
|
130
|
-
f'Executor {task_data["executor"]} on module {name} is invalid. Please only use ":" to separate to and from paths i.e. "from:to".'
|
|
131
|
-
]
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
executor, version = task_data['executor'].split(':')
|
|
135
|
-
if executor not in old_to_new_executors_map.keys():
|
|
136
|
-
error_dict['executor'] = [
|
|
137
|
-
f'You provided an invalid executor in module {name}; \
|
|
138
|
-
Make sure you follow the format executor_name:version'
|
|
139
|
-
]
|
|
140
|
-
|
|
141
|
-
new_executor_name = old_to_new_executors_map[executor]
|
|
142
|
-
supported_versions = custom_executors[new_executor_name]['versions'] + ['*']
|
|
143
|
-
if version not in supported_versions:
|
|
144
|
-
error_dict['image'] = [
|
|
145
|
-
f'Invalid version for executor {executor} on module {name}. The supported versions for {executor} are {supported_versions}'
|
|
146
|
-
]
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def validate_mappings(name, task_data, error_dict, mapping_type):
|
|
151
|
-
"""
|
|
152
|
-
Shared validation function for input and output mappings.
|
|
153
|
-
"""
|
|
154
|
-
if mapping_type not in task_data:
|
|
155
|
-
error_dict[mapping_type] = [
|
|
156
|
-
f'{mapping_type} field on module {name} is required. Please specify your {mapping_type}.'
|
|
157
|
-
]
|
|
158
|
-
return
|
|
159
|
-
|
|
160
|
-
if not isinstance(task_data[mapping_type], list):
|
|
161
|
-
error_dict[mapping_type] = [
|
|
162
|
-
f'{mapping_type} field on module {name} is invalid. Please format the field as a yaml array.'
|
|
163
|
-
]
|
|
164
|
-
return
|
|
165
|
-
|
|
166
|
-
for mapping in task_data.get(mapping_type):
|
|
167
|
-
mapping_parts = mapping.split(' ')
|
|
168
|
-
if len(mapping_parts) != 3:
|
|
169
|
-
error_dict[mapping_type] = [
|
|
170
|
-
f'{mapping_type} item {mapping} on module {name} is invalid. Please use the format "COPY from_path to_path" i.e. "COPY / /home/biolib/"'
|
|
171
|
-
]
|
|
172
|
-
return
|
|
173
|
-
|
|
174
|
-
if mapping_parts[0] != 'COPY':
|
|
175
|
-
error_dict[mapping_type] = [
|
|
176
|
-
f'{mapping_type} item {mapping} on module {name} is missing the COPY command. Please use the format "COPY from_path to_path" i.e. "COPY / /home/biolib/"'
|
|
177
|
-
]
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
from_path = mapping_parts[1]
|
|
181
|
-
to_path = mapping_parts[2]
|
|
182
|
-
|
|
183
|
-
if '$' in re.sub(r"\$[1-9][0-9]*", "", from_path):
|
|
184
|
-
error_dict[mapping_type] = [
|
|
185
|
-
f'{mapping_type} item {mapping} on module {name} in path "{from_path}" is using an invalid variable. \
|
|
186
|
-
Please only use variables referring to an argument number, where "$1" refers to the first argument \
|
|
187
|
-
i.e. "COPY $1 /home/biolib/$1"'
|
|
188
|
-
]
|
|
189
|
-
return
|
|
190
|
-
|
|
191
|
-
if '$' in re.sub(r"\$[1-9][0-9]*", "", to_path):
|
|
192
|
-
error_dict[mapping_type] = [
|
|
193
|
-
f'{mapping_type} item {mapping} on module {name} in path "{to_path}" is using an invalid variable. \
|
|
194
|
-
Please only use variables referring to an argument number, where "$1" refers to the first argument \
|
|
195
|
-
i.e. "COPY $1 /home/biolib/$1"'
|
|
196
|
-
]
|
|
197
|
-
return
|
|
198
|
-
|
|
199
|
-
# Check that we don't map a directory to a file
|
|
200
|
-
to_path_with_vars_replaced_with_dollar = re.sub("\$[0-9]+", "$", to_path)
|
|
201
|
-
if from_path.endswith('/') and (not to_path.endswith('/') and
|
|
202
|
-
not to_path_with_vars_replaced_with_dollar.endswith('$')):
|
|
203
|
-
error_dict[mapping_type] = [
|
|
204
|
-
f'{mapping_type} item {mapping} on module {name} is invalid. Directories can only map to other directories'
|
|
205
|
-
]
|
|
206
|
-
return
|
|
207
|
-
|
|
208
|
-
if not to_path.startswith('/') and not to_path.startswith('$'):
|
|
209
|
-
error_dict[mapping_type] = [
|
|
210
|
-
f'{mapping_type} item {mapping} on module {name} on path "{to_path}" is invalid. Only absolute paths allowed'
|
|
211
|
-
]
|
|
212
|
-
return
|
|
213
|
-
|
|
214
|
-
if not from_path.startswith('/') and not from_path.startswith('$'):
|
|
215
|
-
error_dict[mapping_type] = [
|
|
216
|
-
f'{mapping_type} item {mapping} on module {name} on path "{from_path}" is invalid. Only absolute paths allowed'
|
|
217
|
-
]
|
|
218
|
-
return
|
|
219
|
-
|
|
220
|
-
if '//' in from_path or '//' in to_path:
|
|
221
|
-
error_dict[mapping_type] = [
|
|
222
|
-
f'{mapping_type} item {mapping} on module {name} is invalid. Directories can not have consecutive slashes'
|
|
223
|
-
]
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def validate_image(name, task_data, error_dict, yaml_version):
|
|
227
|
-
if 'image' not in task_data:
|
|
228
|
-
error_dict['image'] = [
|
|
229
|
-
f'You must define an image to use for module {name}.'
|
|
230
|
-
]
|
|
231
|
-
return
|
|
232
|
-
|
|
233
|
-
image = task_data['image']
|
|
234
|
-
if '://' not in image:
|
|
235
|
-
error_dict['image'] = [
|
|
236
|
-
f'Wrong image format on module {name}. You must define an image using the following format "environment://image_name:version"'
|
|
237
|
-
]
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
environment = image.split('://')[0]
|
|
241
|
-
if environment not in AllowedYAMLEnvironments.values():
|
|
242
|
-
error_dict['image'] = [
|
|
243
|
-
f'Wrong environment on image of module {name}. The environment should be specified before "://" and can be only be one of {AllowedYAMLEnvironments.values()}'
|
|
244
|
-
]
|
|
245
|
-
|
|
246
|
-
if image.startswith(f'{AllowedYAMLEnvironments.BIOLIB_APP.value}://biolib/'):
|
|
247
|
-
# Image is a biolib custom executor
|
|
248
|
-
uri = image.replace(f'{AllowedYAMLEnvironments.BIOLIB_APP.value}://biolib/', '', 1)
|
|
249
|
-
if uri.count(':') != 1:
|
|
250
|
-
error_dict['image'] = [
|
|
251
|
-
f'Missing version on the image of module {name}. A version must be specified at the end of the image like so: "environment://image_name:version"'
|
|
252
|
-
]
|
|
253
|
-
return
|
|
254
|
-
|
|
255
|
-
executor, version = uri.split(':')
|
|
256
|
-
# Check if the executor is supported
|
|
257
|
-
if executor not in custom_executors.keys():
|
|
258
|
-
error_dict['image'] = [
|
|
259
|
-
f'Invalid image name biolib/{executor} for biolib executor on module {name}. The supported biolib executors are {["biolib/" + executor for executor in custom_executors.keys()]}'
|
|
260
|
-
]
|
|
261
|
-
return
|
|
262
|
-
|
|
263
|
-
# Check if the supplied version for the executor is supported
|
|
264
|
-
supported_versions = custom_executors[executor]['versions'] + ['*']
|
|
265
|
-
if version not in supported_versions:
|
|
266
|
-
error_dict['image'] = [
|
|
267
|
-
f'Invalid version for biolib executor {executor} on module {name}. The supported versions for {executor} are {supported_versions}'
|
|
268
|
-
]
|
|
269
|
-
return
|
|
270
|
-
|
|
271
|
-
elif environment == AllowedYAMLEnvironments.BIOLIB_APP.value:
|
|
272
|
-
app_version_string = image.split('://')[1]
|
|
273
|
-
# validate_external_app(name, app_version_string, user, error_dict, yaml_version)
|
|
274
|
-
|
|
275
|
-
elif environment in (AllowedYAMLEnvironments.DOCKERHUB.value, AllowedYAMLEnvironments.LOCAL_DOCKER.value):
|
|
276
|
-
repo_and_tag = image.split('://')[1]
|
|
277
|
-
if not repo_and_tag.count(':', 1):
|
|
278
|
-
error_dict['image'] = [
|
|
279
|
-
f'Invalid docker image on module {name}. A tag must be included in your image with format repo:tag i.e. alpine:latest'
|
|
280
|
-
]
|
|
281
|
-
return
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
# Task fields shared between all versions
|
|
285
|
-
supported_task_fields_base = [
|
|
286
|
-
'working_directory',
|
|
287
|
-
]
|
|
288
|
-
|
|
289
|
-
supported_task_fields_v1 = [
|
|
290
|
-
'executor',
|
|
291
|
-
'path'
|
|
292
|
-
]
|
|
293
|
-
|
|
294
|
-
supported_task_fields_v2 = [
|
|
295
|
-
'image',
|
|
296
|
-
'input_files',
|
|
297
|
-
'output_files',
|
|
298
|
-
'source_files',
|
|
299
|
-
'command',
|
|
300
|
-
]
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
def validate_unsupported_task_fields(name, task_data, error_dict, yaml_version):
|
|
304
|
-
# If we need to validate module fields, i.e. the module is not a string in v1, then it has to be a dict
|
|
305
|
-
if not isinstance(task_data, dict):
|
|
306
|
-
error_dict['unsupported_fields'] = [
|
|
307
|
-
f'Module {name} is the wrong type. Modules can only be a YAML dict in version {yaml_version}']
|
|
308
|
-
return
|
|
309
|
-
|
|
310
|
-
if yaml_version == 1:
|
|
311
|
-
supported_fields = supported_task_fields_base + supported_task_fields_v1
|
|
312
|
-
else:
|
|
313
|
-
supported_fields = supported_task_fields_base + supported_task_fields_v2
|
|
314
|
-
|
|
315
|
-
errors = []
|
|
316
|
-
for field in task_data.keys():
|
|
317
|
-
if field not in supported_fields:
|
|
318
|
-
errors.append(
|
|
319
|
-
f'The module field {field} on {name} is not valid for biolib yaml version {yaml_version}'
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
if errors:
|
|
323
|
-
error_dict['unsupported_fields'] = errors
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
from zipfile import ZipFile
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def validate_zip_file(zip_file_path, app_path):
|
|
5
|
-
try:
|
|
6
|
-
zip_file = ZipFile(zip_file_path)
|
|
7
|
-
except Exception:
|
|
8
|
-
raise Exception('Failed to read source files zip for validation.') from None
|
|
9
|
-
|
|
10
|
-
files = [filename.strip('/') for filename in zip_file.namelist()]
|
|
11
|
-
top_level_files = [filename for filename in files if len(filename.split('/')) == 1]
|
|
12
|
-
|
|
13
|
-
if len(top_level_files) > 1:
|
|
14
|
-
raise Exception('Please place all your source files in a single folder and zip it')
|
|
15
|
-
|
|
16
|
-
# Check if .biolib folder is present
|
|
17
|
-
if f'{top_level_files[0]}/.biolib' in files and f'{top_level_files[0]}/biolib' in files:
|
|
18
|
-
raise Exception('You provided both a biolib and a .biolib folder. Please only provide the .biolib folder')
|
|
19
|
-
|
|
20
|
-
elif f'{top_level_files[0]}/.biolib' in files:
|
|
21
|
-
biolib_folder_path = f'{top_level_files[0]}/.biolib'
|
|
22
|
-
|
|
23
|
-
elif f'{top_level_files[0]}/biolib' in files:
|
|
24
|
-
raise Exception('Your biolib folder appears to be called "biolib" - it must be called ".biolib".')
|
|
25
|
-
|
|
26
|
-
else:
|
|
27
|
-
raise Exception(f'Could not find a .biolib folder in provided application folder {app_path}')
|
|
28
|
-
|
|
29
|
-
# Check if the config file is present
|
|
30
|
-
if f'{biolib_folder_path}/config.yml' in files:
|
|
31
|
-
# TODO: Changed to make sense in CLI. Rewrite this check at some point
|
|
32
|
-
pass
|
|
33
|
-
|
|
34
|
-
elif f'{biolib_folder_path}/config.yaml' in files:
|
|
35
|
-
raise Exception('Your biolib config file has the .yaml file extension - it must be .yml')
|
|
36
|
-
|
|
37
|
-
else:
|
|
38
|
-
raise Exception(
|
|
39
|
-
f'Could not find config.yml file. Please provide a yaml file named config.yml in the .biolib folder'
|
|
40
|
-
)
|