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.
Files changed (262) hide show
  1. biolib/__init__.py +357 -11
  2. biolib/_data_record/data_record.py +380 -0
  3. biolib/_index/__init__.py +0 -0
  4. biolib/_index/index.py +55 -0
  5. biolib/_index/query_result.py +103 -0
  6. biolib/_internal/__init__.py +0 -0
  7. biolib/_internal/add_copilot_prompts.py +58 -0
  8. biolib/_internal/add_gui_files.py +81 -0
  9. biolib/_internal/data_record/__init__.py +1 -0
  10. biolib/_internal/data_record/data_record.py +85 -0
  11. biolib/_internal/data_record/push_data.py +116 -0
  12. biolib/_internal/data_record/remote_storage_endpoint.py +43 -0
  13. biolib/_internal/errors.py +5 -0
  14. biolib/_internal/file_utils.py +125 -0
  15. biolib/_internal/fuse_mount/__init__.py +1 -0
  16. biolib/_internal/fuse_mount/experiment_fuse_mount.py +209 -0
  17. biolib/_internal/http_client.py +159 -0
  18. biolib/_internal/lfs/__init__.py +1 -0
  19. biolib/_internal/lfs/cache.py +51 -0
  20. biolib/_internal/libs/__init__.py +1 -0
  21. biolib/_internal/libs/fusepy/__init__.py +1257 -0
  22. biolib/_internal/push_application.py +488 -0
  23. biolib/_internal/runtime.py +22 -0
  24. biolib/_internal/string_utils.py +13 -0
  25. biolib/_internal/templates/__init__.py +1 -0
  26. biolib/_internal/templates/copilot_template/.github/instructions/general-app-knowledge.instructions.md +10 -0
  27. biolib/_internal/templates/copilot_template/.github/instructions/style-general.instructions.md +20 -0
  28. biolib/_internal/templates/copilot_template/.github/instructions/style-python.instructions.md +16 -0
  29. biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
  30. biolib/_internal/templates/copilot_template/.github/prompts/biolib_app_inputs.prompt.md +11 -0
  31. biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
  32. biolib/_internal/templates/copilot_template/.github/prompts/biolib_run_apps.prompt.md +12 -0
  33. biolib/_internal/templates/dashboard_template/.biolib/config.yml +5 -0
  34. biolib/_internal/templates/github_workflow_template/.github/workflows/biolib.yml +21 -0
  35. biolib/_internal/templates/gitignore_template/.gitignore +10 -0
  36. biolib/_internal/templates/gui_template/.yarnrc.yml +1 -0
  37. biolib/_internal/templates/gui_template/App.tsx +53 -0
  38. biolib/_internal/templates/gui_template/Dockerfile +27 -0
  39. biolib/_internal/templates/gui_template/biolib-sdk.ts +82 -0
  40. biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
  41. biolib/_internal/templates/gui_template/index.css +5 -0
  42. biolib/_internal/templates/gui_template/index.html +13 -0
  43. biolib/_internal/templates/gui_template/index.tsx +10 -0
  44. biolib/_internal/templates/gui_template/package.json +27 -0
  45. biolib/_internal/templates/gui_template/tsconfig.json +24 -0
  46. biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +50 -0
  47. biolib/_internal/templates/gui_template/vite.config.mts +10 -0
  48. biolib/_internal/templates/init_template/.biolib/config.yml +19 -0
  49. biolib/_internal/templates/init_template/Dockerfile +14 -0
  50. biolib/_internal/templates/init_template/requirements.txt +1 -0
  51. biolib/_internal/templates/init_template/run.py +12 -0
  52. biolib/_internal/templates/init_template/run.sh +4 -0
  53. biolib/_internal/templates/templates.py +25 -0
  54. biolib/_internal/tree_utils.py +106 -0
  55. biolib/_internal/utils/__init__.py +65 -0
  56. biolib/_internal/utils/auth.py +46 -0
  57. biolib/_internal/utils/job_url.py +33 -0
  58. biolib/_internal/utils/multinode.py +263 -0
  59. biolib/_runtime/runtime.py +157 -0
  60. biolib/_session/session.py +44 -0
  61. biolib/_shared/__init__.py +0 -0
  62. biolib/_shared/types/__init__.py +74 -0
  63. biolib/_shared/types/account.py +12 -0
  64. biolib/_shared/types/account_member.py +8 -0
  65. biolib/_shared/types/app.py +9 -0
  66. biolib/_shared/types/data_record.py +40 -0
  67. biolib/_shared/types/experiment.py +32 -0
  68. biolib/_shared/types/file_node.py +17 -0
  69. biolib/_shared/types/push.py +6 -0
  70. biolib/_shared/types/resource.py +37 -0
  71. biolib/_shared/types/resource_deploy_key.py +11 -0
  72. biolib/_shared/types/resource_permission.py +14 -0
  73. biolib/_shared/types/resource_version.py +19 -0
  74. biolib/_shared/types/result.py +14 -0
  75. biolib/_shared/types/typing.py +10 -0
  76. biolib/_shared/types/user.py +19 -0
  77. biolib/_shared/utils/__init__.py +7 -0
  78. biolib/_shared/utils/resource_uri.py +75 -0
  79. biolib/api/__init__.py +6 -0
  80. biolib/api/client.py +168 -0
  81. biolib/app/app.py +252 -49
  82. biolib/app/search_apps.py +45 -0
  83. biolib/biolib_api_client/api_client.py +126 -31
  84. biolib/biolib_api_client/app_types.py +24 -4
  85. biolib/biolib_api_client/auth.py +31 -8
  86. biolib/biolib_api_client/biolib_app_api.py +147 -52
  87. biolib/biolib_api_client/biolib_job_api.py +161 -141
  88. biolib/biolib_api_client/job_types.py +21 -5
  89. biolib/biolib_api_client/lfs_types.py +7 -23
  90. biolib/biolib_api_client/user_state.py +56 -0
  91. biolib/biolib_binary_format/__init__.py +1 -4
  92. biolib/biolib_binary_format/file_in_container.py +105 -0
  93. biolib/biolib_binary_format/module_input.py +24 -7
  94. biolib/biolib_binary_format/module_output_v2.py +149 -0
  95. biolib/biolib_binary_format/remote_endpoints.py +34 -0
  96. biolib/biolib_binary_format/remote_stream_seeker.py +59 -0
  97. biolib/biolib_binary_format/saved_job.py +3 -2
  98. biolib/biolib_binary_format/{attestation_document.py → stdout_and_stderr.py} +8 -8
  99. biolib/biolib_binary_format/system_status_update.py +3 -2
  100. biolib/biolib_binary_format/utils.py +175 -0
  101. biolib/biolib_docker_client/__init__.py +11 -2
  102. biolib/biolib_errors.py +36 -0
  103. biolib/biolib_logging.py +27 -10
  104. biolib/cli/__init__.py +38 -0
  105. biolib/cli/auth.py +46 -0
  106. biolib/cli/data_record.py +164 -0
  107. biolib/cli/index.py +32 -0
  108. biolib/cli/init.py +421 -0
  109. biolib/cli/lfs.py +101 -0
  110. biolib/cli/push.py +50 -0
  111. biolib/cli/run.py +63 -0
  112. biolib/cli/runtime.py +14 -0
  113. biolib/cli/sdk.py +16 -0
  114. biolib/cli/start.py +56 -0
  115. biolib/compute_node/cloud_utils/cloud_utils.py +110 -161
  116. biolib/compute_node/job_worker/cache_state.py +66 -88
  117. biolib/compute_node/job_worker/cache_types.py +1 -6
  118. biolib/compute_node/job_worker/docker_image_cache.py +112 -37
  119. biolib/compute_node/job_worker/executors/__init__.py +0 -3
  120. biolib/compute_node/job_worker/executors/docker_executor.py +532 -199
  121. biolib/compute_node/job_worker/executors/docker_types.py +9 -1
  122. biolib/compute_node/job_worker/executors/types.py +19 -9
  123. biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +30 -0
  124. biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +3 -5
  125. biolib/compute_node/job_worker/job_storage.py +108 -0
  126. biolib/compute_node/job_worker/job_worker.py +397 -212
  127. biolib/compute_node/job_worker/large_file_system.py +87 -38
  128. biolib/compute_node/job_worker/network_alloc.py +99 -0
  129. biolib/compute_node/job_worker/network_buffer.py +240 -0
  130. biolib/compute_node/job_worker/utilization_reporter_thread.py +197 -0
  131. biolib/compute_node/job_worker/utils.py +9 -24
  132. biolib/compute_node/remote_host_proxy.py +400 -98
  133. biolib/compute_node/utils.py +31 -9
  134. biolib/compute_node/webserver/compute_node_results_proxy.py +189 -0
  135. biolib/compute_node/webserver/proxy_utils.py +28 -0
  136. biolib/compute_node/webserver/webserver.py +130 -44
  137. biolib/compute_node/webserver/webserver_types.py +2 -6
  138. biolib/compute_node/webserver/webserver_utils.py +77 -12
  139. biolib/compute_node/webserver/worker_thread.py +183 -42
  140. biolib/experiments/__init__.py +0 -0
  141. biolib/experiments/experiment.py +356 -0
  142. biolib/jobs/__init__.py +1 -0
  143. biolib/jobs/job.py +741 -0
  144. biolib/jobs/job_result.py +185 -0
  145. biolib/jobs/types.py +50 -0
  146. biolib/py.typed +0 -0
  147. biolib/runtime/__init__.py +14 -0
  148. biolib/sdk/__init__.py +91 -0
  149. biolib/tables.py +34 -0
  150. biolib/typing_utils.py +2 -7
  151. biolib/user/__init__.py +1 -0
  152. biolib/user/sign_in.py +54 -0
  153. biolib/utils/__init__.py +162 -0
  154. biolib/utils/cache_state.py +94 -0
  155. biolib/utils/multipart_uploader.py +194 -0
  156. biolib/utils/seq_util.py +150 -0
  157. biolib/utils/zip/remote_zip.py +640 -0
  158. pybiolib-1.2.1890.dist-info/METADATA +41 -0
  159. pybiolib-1.2.1890.dist-info/RECORD +177 -0
  160. {pybiolib-0.2.951.dist-info → pybiolib-1.2.1890.dist-info}/WHEEL +1 -1
  161. pybiolib-1.2.1890.dist-info/entry_points.txt +2 -0
  162. README.md +0 -17
  163. biolib/app/app_result.py +0 -68
  164. biolib/app/utils.py +0 -62
  165. biolib/biolib-js/0-biolib.worker.js +0 -1
  166. biolib/biolib-js/1-biolib.worker.js +0 -1
  167. biolib/biolib-js/2-biolib.worker.js +0 -1
  168. biolib/biolib-js/3-biolib.worker.js +0 -1
  169. biolib/biolib-js/4-biolib.worker.js +0 -1
  170. biolib/biolib-js/5-biolib.worker.js +0 -1
  171. biolib/biolib-js/6-biolib.worker.js +0 -1
  172. biolib/biolib-js/index.html +0 -10
  173. biolib/biolib-js/main-biolib.js +0 -1
  174. biolib/biolib_api_client/biolib_account_api.py +0 -21
  175. biolib/biolib_api_client/biolib_large_file_system_api.py +0 -108
  176. biolib/biolib_binary_format/aes_encrypted_package.py +0 -42
  177. biolib/biolib_binary_format/module_output.py +0 -58
  178. biolib/biolib_binary_format/rsa_encrypted_aes_package.py +0 -57
  179. biolib/biolib_push.py +0 -114
  180. biolib/cli.py +0 -203
  181. biolib/cli_utils.py +0 -273
  182. biolib/compute_node/cloud_utils/enclave_parent_types.py +0 -7
  183. biolib/compute_node/enclave/__init__.py +0 -2
  184. biolib/compute_node/enclave/enclave_remote_hosts.py +0 -53
  185. biolib/compute_node/enclave/nitro_secure_module_utils.py +0 -64
  186. biolib/compute_node/job_worker/executors/base_executor.py +0 -18
  187. biolib/compute_node/job_worker/executors/pyppeteer_executor.py +0 -173
  188. biolib/compute_node/job_worker/executors/remote/__init__.py +0 -1
  189. biolib/compute_node/job_worker/executors/remote/nitro_enclave_utils.py +0 -81
  190. biolib/compute_node/job_worker/executors/remote/remote_executor.py +0 -51
  191. biolib/lfs.py +0 -196
  192. biolib/pyppeteer/.circleci/config.yml +0 -100
  193. biolib/pyppeteer/.coveragerc +0 -3
  194. biolib/pyppeteer/.gitignore +0 -89
  195. biolib/pyppeteer/.pre-commit-config.yaml +0 -28
  196. biolib/pyppeteer/CHANGES.md +0 -253
  197. biolib/pyppeteer/CONTRIBUTING.md +0 -26
  198. biolib/pyppeteer/LICENSE +0 -12
  199. biolib/pyppeteer/README.md +0 -137
  200. biolib/pyppeteer/docs/Makefile +0 -177
  201. biolib/pyppeteer/docs/_static/custom.css +0 -28
  202. biolib/pyppeteer/docs/_templates/layout.html +0 -10
  203. biolib/pyppeteer/docs/changes.md +0 -1
  204. biolib/pyppeteer/docs/conf.py +0 -299
  205. biolib/pyppeteer/docs/index.md +0 -21
  206. biolib/pyppeteer/docs/make.bat +0 -242
  207. biolib/pyppeteer/docs/reference.md +0 -211
  208. biolib/pyppeteer/docs/server.py +0 -60
  209. biolib/pyppeteer/poetry.lock +0 -1699
  210. biolib/pyppeteer/pyppeteer/__init__.py +0 -135
  211. biolib/pyppeteer/pyppeteer/accessibility.py +0 -286
  212. biolib/pyppeteer/pyppeteer/browser.py +0 -401
  213. biolib/pyppeteer/pyppeteer/browser_fetcher.py +0 -194
  214. biolib/pyppeteer/pyppeteer/command.py +0 -22
  215. biolib/pyppeteer/pyppeteer/connection/__init__.py +0 -242
  216. biolib/pyppeteer/pyppeteer/connection/cdpsession.py +0 -101
  217. biolib/pyppeteer/pyppeteer/coverage.py +0 -346
  218. biolib/pyppeteer/pyppeteer/device_descriptors.py +0 -787
  219. biolib/pyppeteer/pyppeteer/dialog.py +0 -79
  220. biolib/pyppeteer/pyppeteer/domworld.py +0 -597
  221. biolib/pyppeteer/pyppeteer/emulation_manager.py +0 -53
  222. biolib/pyppeteer/pyppeteer/errors.py +0 -48
  223. biolib/pyppeteer/pyppeteer/events.py +0 -63
  224. biolib/pyppeteer/pyppeteer/execution_context.py +0 -156
  225. biolib/pyppeteer/pyppeteer/frame/__init__.py +0 -299
  226. biolib/pyppeteer/pyppeteer/frame/frame_manager.py +0 -306
  227. biolib/pyppeteer/pyppeteer/helpers.py +0 -245
  228. biolib/pyppeteer/pyppeteer/input.py +0 -371
  229. biolib/pyppeteer/pyppeteer/jshandle.py +0 -598
  230. biolib/pyppeteer/pyppeteer/launcher.py +0 -683
  231. biolib/pyppeteer/pyppeteer/lifecycle_watcher.py +0 -169
  232. biolib/pyppeteer/pyppeteer/models/__init__.py +0 -103
  233. biolib/pyppeteer/pyppeteer/models/_protocol.py +0 -12460
  234. biolib/pyppeteer/pyppeteer/multimap.py +0 -82
  235. biolib/pyppeteer/pyppeteer/network_manager.py +0 -678
  236. biolib/pyppeteer/pyppeteer/options.py +0 -8
  237. biolib/pyppeteer/pyppeteer/page.py +0 -1728
  238. biolib/pyppeteer/pyppeteer/pipe_transport.py +0 -59
  239. biolib/pyppeteer/pyppeteer/target.py +0 -147
  240. biolib/pyppeteer/pyppeteer/task_queue.py +0 -24
  241. biolib/pyppeteer/pyppeteer/timeout_settings.py +0 -36
  242. biolib/pyppeteer/pyppeteer/tracing.py +0 -93
  243. biolib/pyppeteer/pyppeteer/us_keyboard_layout.py +0 -305
  244. biolib/pyppeteer/pyppeteer/util.py +0 -18
  245. biolib/pyppeteer/pyppeteer/websocket_transport.py +0 -47
  246. biolib/pyppeteer/pyppeteer/worker.py +0 -101
  247. biolib/pyppeteer/pyproject.toml +0 -97
  248. biolib/pyppeteer/spell.txt +0 -137
  249. biolib/pyppeteer/tox.ini +0 -72
  250. biolib/pyppeteer/utils/generate_protocol_types.py +0 -603
  251. biolib/start_cli.py +0 -7
  252. biolib/utils.py +0 -47
  253. biolib/validators/validate_app_version.py +0 -183
  254. biolib/validators/validate_argument.py +0 -134
  255. biolib/validators/validate_module.py +0 -323
  256. biolib/validators/validate_zip_file.py +0 -40
  257. biolib/validators/validator_utils.py +0 -103
  258. pybiolib-0.2.951.dist-info/LICENSE +0 -21
  259. pybiolib-0.2.951.dist-info/METADATA +0 -61
  260. pybiolib-0.2.951.dist-info/RECORD +0 -153
  261. pybiolib-0.2.951.dist-info/entry_points.txt +0 -3
  262. /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
- '![Example Alt Text](path/to/image.png)'
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
- )